Since iOS 6, unwind segues have been available to navigate up the scene hierarchy. I am trying to decide on the cleaner/better/preferred/more maintainable method for passing data to a parent view controller. There are some questions that address this from the technical perspective (e.g. "if I have an unwind do I still need a delegate") but I can't find much that addresses the questions of pros/cons.
Option 1: use a delegate.
Done by passing in the parent view controller as a delegate adhering to a protocol.
Child calls the protocol method to return the data.
If data validation is required by the Parent, return value/dict required to allow child to handle error.
Overhead: Protocol definition and one method in the parent (for data validation and receipt).
Option 2: use an unwind segue
Done by calling the unwind segue from the child.
Child adds a segue on its scene by dragging a button or the storyboard itself to Exit and naming the segue so it can be with performSegueWithIdentifier:sender
Parent implements returnFromSegueName (user named method linked to that segue) to grab the data from the child.
Data validation though can only be implemented by also implementing canPerformUnwindSegueAction:fromViewController:withSender
Data validation failure will require another property on the Child as this method only accepts a BOOL for return value.
Overhead: Two methods, an extra property, plus Storyboard shenanigans.
Overall, delegates are feeling like the cleaner way to go, but perhaps also antiquated. Am I wrong to be leaning in that direction?
I realize now that this isn't truly an answerable question other than to say that neither approach is wrong - they both have their pros and cons. After having tackled both for a week and done more reading on the subject I can at least quantify why you might want to use either an unwind segue or delegates for working between view controllers.
Coupling
Both models are roughly equally (loosely) coupled. Under the hood, an unwind segue is just a delegate where iOS has done the work of wiring it up for you. For delegates, the parent knows about and conforms to the child protocol. For unwind segues, the parent has to be wired to the child on the storyboard for unwind and needs to know the properties of the child to extract the return data. However, if you're new to delegates and just want to get some data out of a child view, unwind segues are probably less intimidating than using protocols with delegates.
Flexibility
Unwind segues are only a good choice if the sole purpose of the child-to-parent interaction is to return data. There does not appear to be a way to cancel an unwind segue in progress. So if the parent has to do any data validation, or if the child needs more than one interaction with the parent, the only way to do this is to have a delegate where multiple methods can be called back to the parent.
Maintainability
If the type or other aspects of the data being returned changes, it will be easier to update the unwind segue as all you have to do is to update the code in your unwind segue to look at the new properties. For the protocol/delegate approach, you will have to update the protocol in the child and the implementation in the parent. However, the simplicity of the unwind segue comes at the cost that you may easily miss places in parent view controllers that require updating because you don't have the compiler checking your contract (the protocol).
The Winner
There isn't one. Which way you go depends on your data needs, comfort level with protocols (they look more intimidating on first glance than they should), complexity of your application, and long term maintenance needs.
For my purposes, I wound up using delegates because my child had to make more than one call back to the parent in some cases. However, in a few instances where I had many pieces of data to pass back, I adopted what I learned from the unwind segue and simply used properties in the child from which the parent could extract the needed information. I also used this as a convenient path for the parent to provide error information to the child. I don't mix and match unwind segues with delegates in the program for consistency with a programming partner, but there's no reason you couldn't do that if you wanted to.
I was very skeptical of storyboards, but I decided to dive in and use them on a new project. I was amazed at the ease with which you can communicate between the two view controllers. When you perform a performSegueWithIdentifier you get a handle to the new ViewController. You can set any exposed properties you want in that new viewController very cleanly and nicely.
Here is an example:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Student *student = [self.students objectAtIndex:indexPath.row + [self rowAdjuster]];
[[segue destinationViewController] setStudent:student];
}
}
It is very nice and neat. No special protocol that you need to track or maintain.
And Then coming back (I have an IBAction connected to a button in my detail view) You can once again get a nice clean reference to the viewController to which you are returning, and act upon that viewController.
- (IBAction)returnWithStudent:(UIStoryboardSegue *)segue {
UIViewController *vc = [segue sourceViewController];
if ([vc isKindOfClass:[ AddStudentViewController class]]) {
AddStudentViewController *addViewController = (AddStudentViewController *)vc;
if (addViewController.student != nil) {
if ([addViewController hasTakenPhoto]) {
[PhotoHelpers saveImageForStudent:addViewController.student];
}
[StudentController updateStudent:addViewController.student];
}
}
}
Also the segue logic control is nice. One can perform logic checks in shouldPerformSegue which are quite handy.
I've seen lots of junky code that uses protocols of the "send something back to caller" that are really poor at coupling classes. It makes a three-way arrangement-- viewController1 -> protocol -> viewController2, whereas segues make a nice arrangement of viewController1->viewController2.
The segue is a nice way to cleanly and uniquely couple the two classes. I'd strongly recommend it.
Related
Evening, my question is full about theory.
I understood reading from Apple developer documentation that is better to use the Delegates Pattern to keep track of some object attributes. In this way we can access the delegate without access to the object. (I really didn't get the reason of this choice)
I also understood that is better to define: protocolDelegate: class
and when we are declaring the delegate inside the class it's better to use the weak word to prevent some "kind of problem cycle". (??)
So, while I was playing a bit with code, I've discovered that you can't pass a weak delegate between two view controllers, because of course, when you change the controller, the weak delegate is going to be deleted because is a weak thing (or at least this is what I understood).
So, I have to choose between 2 options:
make the delegate "strong" deleting the weak key.
or pass the object in the segue and keep the delegate as weak.
I have a lot of confusion, can you clear my mind? :D
The cycle you're referring to is called a retain cycle.
Let's use a concrete example to clear this up: say you've got a UIViewController which has a UITableView. The view controller has a strong reference to the table view. The view controller now wants to act as the delegate to the table view.
Now, if the table view would have a strong reference to its delegate, we would have the following situation: the view controller has a strong reference to the table view, and the table view in turn would have a strong reference back to the view controller. Thus neither can ever get deallocated.
To break this cycle, references to delegates are usually weak. This allows the retain count of the view controller to drop to 0 eventually, which can in turn release the table view.
Your classes that want to use delegates should also follow this pattern and use weak references to their delegates. You should thus pass the required references via your segue.
I will concentrate on the first part of your question, since the previous answers have covered the rest pretty well.
Consider the following situation: you have a class that handles some kind of network connection - it sends a request to a server and gets a response. Outside of this class there is a viewController that has a button that triggers the request and a view which presents the response to the user.
Basically, the network handling class should be able to get some message from the viewController (button pressed) on one hand and pass the viewController the response on the other. So there should be bidirectional communication between the two classes. While the passing of the buttonPressed message to the network handling class is pretty obvious, the reverse part (passing the response) is a bit more tricky, because the network handling class should not be aware of who created it and who calls it (good OO practices and memory leaks prevention).
That's the point where the delegate pattern comes in. It allows an object to pass data to whoever is interested in it without knowing anything about the recipient. The class that passes the response only knows some 'delegate' and not another class. In addition you can take out the network handling class as is and put it in another project. Because it isn't supposed to know any other class from its original project, only some 'delegate', it can be put into another project without any modifications.
I hope it can help you to get the reason of the choice.
I think pass the object with segue, Segues are a very important part of using Storyboards in Xcode. We can go over the different types of seguesanother time, but this one shows you how to use the “Show” segue, as well as how to pass data between the two view controllers to customize the second one with whatever custom data is required.
You can easily use segues example; Under below you can send currentstring to destinationViewController inside sentstring , also ShowSegue is your segue identifier
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowSegue" {
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.sentstring = currentstring
}
}
}
Navigation between viewcontrollers maintain stack of viewcontrollers.
For example aVC is firstviewcontroller then top of stack will be aVC,
now when you push or show another viewcontroller say bVC then now top of statck is bVC. So stack looks like,
aVC -> bVC(top)
now you push another cVC then,
aVC -> bVC -> cVC(top).
So top of stack is always visible to user.
at current situation, aVC and bVC and cVC are not deallocate. they are in memory. But if you pop or dismiss cVC, then it will deallocate from memory and now your top of stack looka like,
aVC -> bVC(top).
So viewcontrollers live in stack till they are not popped or removed. So, they are strog reference by default.
Segue is nothing but you can say that they are graphical representation of push or pop operation.
another thing is that delegate should be weak that because it can create retain cycle if they are strong.
you can called delegate as representative in general sense.
Now, if you are using segue, send your object in prepareForsegue and it will manage everything else.
I'm trying to learn Swift and I'm trying to develop the famous note application.
There is an array bound to a tableview and another view for adding notes.
At second view textfieldshouldreturn event triggers a segue and goes back to tableview.
I wanted to learn if this is the right way. Because by doing this way I'm manipulating a variable in another view controller. I'm not a MVC master but I felt like it is wrong. Here is my code snippet:
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.performSegueWithIdentifier("backSegue", sender: self)
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "backSegue"){
let navController = segue.destinationViewController as UINavigationController;
let myController = navController.topViewController as NotesTableViewController;
if(self.ourTextField?.text != nil || self.ourTextField?.text != ""){
myController.notes.append(self.ourTextField?.text ?? "");
}
}
}
Thank you.
Your question is not really about prepareForSegue but the relationship between view controllers. The reason that your design "feels wrong" is that it is. The problem is that your note writing view controller knows too much about the view controller that is using it because it is directly manipulating a variable from the calling view controller. In order to directly manipulate the variable, it must know the class of the caller.
Why is this a problem? It makes your note writing view controller less reusable. If you write the note writing view controller correctly, then you could reuse it in other apps. To make it reusable, you need to decouple the note writing view controller from the caller - it must not know who exactly is calling it.
So the question becomes, how do I pass data back to the caller if I don't know who called me? The answer is delegation.
Delegation works like this:
You create a protocol which describes a method or methods that the implementor of that protocol will implement. In your case, you could use a protocol like NoteWriterDelegate that implements the method takeNote(note: String).
protocol NoteWriterDelegate {
func takeNote(note: String)
}
Define this in the file along with your note writing view controller.
Your note writer will have an optional pointer to the delegate:
weak var delegate: NoteWriterDelegate?
You need to declare your first view controller as a NoteWriterDelegate:
class ViewController: UITableViewController, NoteWriterDelegate
And then implement the required method in your first view controller:
func takeNote(note: String) {
notes.append(note)
}
When you call prepareForSegue in preparation for moving to the note writing view controller, you pass yourself as the delegate:
destinationViewController.delegate = self
In the note writing view controller, when you have a note to pass back to the caller, you call takeNote on the delegate:
delegate?.takeNote(self.ourTextField?.text ?? "")
By doing it this way, your note writer only knows that it is talking to a NoteWriterDelegate. If you want to reuse this in the future, you just drop your note writer class into another project, implement the delegate, and it works without you having to touch the code in the note writer class.
I would recommend passing data via prepareForSegue in most cases. It's pretty simple to set up and easy to understand.
However, I would recommend never updating UI elements (labels, text fields, etc.) on the destination view directly. In my opinion, this is bad coupling that creates a lot of problems.
Instead, create a property or properties on the destination view controller that the caller can set in prepareForSegue to pass data to it. These should be special purpose properties used exclusively for passing data. The destination view controller is then in charge of using the data in these properties to update its UI or internal state.
Delegation is a valid approach, but I find it to be overkill for most situations. It requires more setup and is more abstract. This abstraction isn't needed in a lot of view controller relationships. If you discover you need to reuse a view controller, you can always refactor to use delegation later.
I do not believe that the prepareSegue is the ideal way for passing data between view controllers...at least not directly.
I share your concerns about using prepareForSegue to pass values between view controllers. The source view controller shouldn’t know anything about the destination view controller (and the other way around, for that matter). Ideally view controllers should be separate islands with no visibility into one another.
To address the coupling that storyboards seem to encourage, I’ve often used some form of the mediator pattern to pass data between view controllers. Here is a pretty good blog post on how to implement a version of this pattern around storyboards: http://coding.tabasoft.it/ios/mediator-pattern-in-swift/ . As always, this pattern may not be the best fit for all situations, but I feel it has been a good solution in a lot of my past projects.
Basically, how the mediator pattern would work within the storyboard paradigm is that in each view controller’s prepareForSegue method, the the segue object is passed to the mediator object. The view controller doesn’t care what’s inside or where the navigation is going next; it just knows it’s about to not be visible. The mediator, which has just been passed the segue object (containing the source and destination view controllers), is then responsible for passing data between the source and destination view controllers.
Using this pattern, each view controller is blissfully unaware of the existence of the other. The mediator class, on the other hand, must know about the relationships between the view controllers (and the view controllers' interfaces) in the navigation path. Obviously if the navigation changes, or the view controllers themselves change, the mediator class will need to adjust. Each view controller, however, need not have any dependence on each other, and therefore need not be updated to to accommodate changes in the navigation path or changes to the other view controllers along that navigation path.
It is not 'the' right way, but it is a right way. Especially in storyboard applications.
Here is an alternative way of passing value and calling the view.
var myNewVC = NewViewController()
myNewVC.data = self
navigationController?.presentViewController(myNewVC, animated: true, completion: nil)
When using a UINavigationController, when the user is "diving in deeper" (pushing yet another controller on the stack), I have an opportunity to handle that in
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
But how do I handle the opposite? When the user presses the back button, and a controller becomes the top controller again, I'd like it to potentially update some state because the controllers on the stack may have changed some things I want to reflect in the now visible controller.
Or, by analog, when I use modal segues to present new controllers, I get to pick a method that is called as an unwind segue when the presented controller exits. How can I do the same with navigation stack managed controllers?
(feel free to put a better title on this)
It turns out that you can disambiguate based on the response to isMovingToParentViewController. If it is YES your controller has just been placed topmost on the stack. If it is NO, your controller is returning to topmost, another push on top of it being popped. Example:
-(void)viewWillAppear:(BOOL)animated{
if (self.isMovingToParentViewController == NO) { // returning from even higher controller
[self updateForChangesThatMayHaveHappenedInSubController];
}
[super viewWillAppear:animated];
}
You can use the viewWillAppear: method to update the ui before the view becomes visible. If you want to pass data back up the chain, you should assign yourself as the delegate to your child and call an update function on the delegate before popping.
To have many clients (viewControllers in this case) update their views in response to a change of some shared data, you should use NSNotifications, or you should have the viewControllers observe certain values on the shared data-object (KVO).
ViewController should be as autistic as possible, meaning that they know all about the interface of downstream viewControllers, but have absolutely no idea about what viewController is upstream (talking back to an upstream viewController is usually done through delegation, and only to signal events that might indicate some change in viewController hierarchy, not in shared data state).
Checkout out the stanford lectures by Paul Hegarty, he explains this much better then I can.
I have a form and it gives users an advance mode. I've already googled and looked around at different SO questions (sharing data between controllers, protocols, and passing data between segues) but I'm wondering if there's a better way.
Is there a way for me to have some sort of "master controller" that holds all the data while going back and forth between 3 different controllers?
If I can just hold the data for the second controller and allow my user to make that quick advance edit in the third while keeping it's data intact, that'll do for now.
Thanks in advance
Here's a quick walkthrough of my app:
FirstViewController: User selects an option
SecondViewController: User does some editing while storing that option
(*Optional)ThirdViewControl: User one more quick edit using a web view
*xcode5/iOS7
If you are passing data from one view controller to the next and you are using segues, then call a method on the next view controller in line from prepareForSegue. For example, when segueing between ViewController1 and ViewController2, add this code to ViewController1 and repeat as necessary in other view controllers:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ViewController2 *viewController = segue.destinationViewController;
[viewController configureWithSomeState:self.someState];
}
This code assumes that someState is a property defined on ViewController1.
EDIT: One thing you could do--although I do not like this approach because it forces your VCs to have special knowledge about their parent controller--is to derive UINavigationController, then in your storyboard, use the new class for your navigation controller. Store the state in the derived navigation controller and access it from each VC like this:
DerivedNavigationController *navigationController = (DerivedNavigationController *)self.navigationController;
navigationController.someState...;
Typically I'd put information that is used throughout the application in the application delegate where everybody has access to it.
Another possibility is to implement a singleton data management class to hold it for you.
In this case it seems like the application is really a pretty linear flow, so I'd just pass the selection from vc1 to vc2 and then again to vc3 in the respective prepareForSegue calls.
I Just realized that the following code always creates a new TagsFeedViewController. Is this the default behavior of segues? Is there a way to configure iOS to not create a new destinationViewController every time?
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showSearchResult"]) {
TagsFeedViewController *destViewController = segue.destinationViewController;
destViewController.query = query;
}
}
Segues use whichever view controllers are provided to their – initWithIdentifier:source:destination: methods. It's not the segue that creates the destination view controller, but the storyboard. From the docs:
Normally, view controllers in a storyboard are instantiated and
created automatically in response to actions defined within the
storyboard itself.
So you have some options:
Subclass UIStoryboard. Probably a bad idea. The public interface for UIStoryboard has only three methods; the "actions defined within the storyboard itself" aren't public, and I don't think there's enough information available to let you do the job right.
Make your destination view controller a singleton. Also a bad idea. Aside from the general badness that singletons bring with them, you shouldn't need to keep a view controller that has no views and no child view controllers around. And making your view controller class a singleton just to fool UIStoryboard into using a particular instance of your view controller class seems kinda icky.
Subclass UIStoryboardSegue. If you create your own segues, you can do what you like in – initWithIdentifier:source:destination:, including ignoring the provided destination view controller and using the one you want instead. This still seems like working against the framework, and that's usually a poor plan, but if you absolutely must use a particular instance of your destination view controller this seems like a better way to go.
Go with the flow. Best option. Think about the reason that you're hoping to segue to an existing view controller. Consider whether there might be better ways to accomplish what you want without having to subvert the framework. For example, do you want to use an existing view controller because it already has some particular state? Maybe it'd be better to maintain that state in your model and not in the view controller.
Yes, This is the default behavior for segues. See this post for more information.
You can prevent the creation of the controller by handling the shouldPerformSegueWithIdentifier:sender: message.
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
if ([identifier isEqualToString:#"showSearchResult"]) {
return [self.results count] > 0;
}
return YES;
}