prepareForSegue **always** creates a new destinationViewController? - ios

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;
}

Related

How to tell when my ViewController becomes top of stack again?

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.

iOS: Sharing state/properties between 3 controllers

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.

How to use segues

I'm creating an app in Xcode that currently consist of a Navigation Controller a Table View Controller and a regular View Controller.
I'm using StoryBoard and have created a segue between the table view and the regular view controller. In the navigation bar I have a button that I've dragged to the view controller in the StoryBoard. When I click at the button, the new View Controller is viewed like it suppose to. I then tried to pass data from the table view controller, see below:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"läggTill"])
{
// Get reference to the destination view controller
AddPlayerViewController *apv = [segue destinationViewController];
[apv.myTextField setText:#"hello"];
}
}
The segue identifier is "läggTill", but the code inside the if-statement is not executing.
Two questions:
What is wrong with this approach?
Is this the best approach when using StoryBoards? Can I pass data via viewWillDisappear?
Can I use segues to pass data back to the table view controller from the view controller?
It makes all the sense in the world to pass your data in the prepareForSegue:sender:. I would not recommend passing data in viewWillDisappear because it just makes it messy and it reduces readability + it becomes harder to keep track.
I think your string comparison is not working! Put an NSLog for your identifier string see on the console. I have a feeling it might not like the "ä" character in comparison.
About your last question, as Gabriel.Massana pointed out, for passing data back, using delegate is the way to go.
Note 1: Another problem I noticed is a possible typo you might have "apv" and "avc".
Note 2: Another reason for it failing is that you are setting the TextField before viewDidLoad gets called on your destination controller. I suggest that pass it as string and in the viewDidLoad of your destination, set the text to your TextField.

Delegate vs Unwind Segue for Passing Data to Parent Scene

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.

Conditional segue based on current view controller

All,
I'm trying to perform segues based on the identity of the currently displayed view controller. Essentially, I've given my VC's storyboard ID's and now want to access these in code. So, essentially I want some logic whereby if existing view controller is first, I want to perform firstSegue and if it's second, I want to perform secondSegue and so on. Also, my VC's are part of a navigation controller and I know that the navigation controller has a property where i can view the present view controller or something like that. But I wasnt sure what it was. Can somebody help me out? Again, I foresee my code being something like:
(IBAction)firstButtonPressed:(id)sender
{ if (presentviewcontroller ==a) // If the current view controller Is A
{
[self performSegueWithIdentifier:#"segueA" sender:self];}
if(storyboard.viewcontroller==b)//If the current view controller is B
{
[self.performSegueWithIdentifier:#"segueB" sender:self];}
}
Can someone help me out with some code?
I'm not sure I understand your setup, but if it's what I think it is, then you don't need to know from which controller the button was pressed. Even though the 2 controllers inherit from a common ancestor, they are still separate instances, and only the segue that is connected to that instance will be called. So, each of your controllers could have a segue with the same identifier, lets say button1Segue. Then the code in AncestorController could just be:
-(IBAction)firstButtonPressed:(UIButton *)sender{
[self performSegueWithIdentifier:#"button1Segue" sender:self];
}
The correct segue will be performed because whichever instance's button was pressed, only that instance's segue will go.
If you were to set different tags for each view in interface builder, you could use the following
if (self.view.tag == 1) {
NSLog(#"something");
}
I successfully use this method in my app for different views, and it works well.

Resources