How to handle prepareForSegue from another NavigationController? - ios

I call performSegueWithIdentifier like that based on design reason.
- (void)showViewWithSegueIdentifier:(NSString *)segueIdentifier {
[self.navigationController.navigationController.topViewController performSegueWithIdentifier:segueIdentifier sender:self];
}
when I try to handle prepareForSegue method, it is still not called.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSLog(#"Call");
}
Is it possible to handle prepareForSegue from another controller?

I believe you can do what you want, although most of the time it is not a good idea. However, you may have a good reason.
To figure out why it isn't work I would look at what self.navigationController.navigationController.topViewController really is. If it is nil nothing will happen and it won't crash. Guessing your navigation controller doesn't have a navigation controller.
try
UIViewController *viewController = self.navigationController.navigationController.topViewController;
NSLog(#"This is my view controller: %#", viewController);
[viewController performSegueWithIdentifier:segueIdentifier sender:self];
and see if there is actually a viewController.
Edit:
If it is not nil there is also a chance that topViewController would return the UIViewController that called it. You could add this log right under the "This is my view controller" log and see if it is the same as above.
NSLog(#"This called prepareForSegue: %#", self);
The thing that doesn't add up though is you didn't mention the app crashing, you just said it didn't work. Meaning that whatever you are calling prepareForSegue on must have the same segue identifier. I believe view controllers get really mad if you call a segue that isn't there and crash the app at least that is what I remember. However, it has been a while sense I did that and could be wrong.
One last thing I would look at is the viewDidLoad: of the controller you expect to perform the segue and log it out and see if it matches the "This is my view controller" log.
I hope that helps and good luck. =)

Related

trouble understanding Obj-C MVC: exit and unwindSegue

Created a button to exit from a second ViewController to the previous ViewController. I've read in tutorials that this should be by using the "EXIT" in the view controller. (control+click+drag the button to EXIT).
I have created an "exit" button on this second controller, and a method on the ViewController class to hook up with the control+click+drag exit segue. This method is completely empty:
- (IBAction) unwindToMainMenu:(UIStoryboardSegue *) unwindSegue
{
}
And yet, when I press the button it returns back to the previous ViewController as I intended, but I was expecting nothing should happen (as the method has no content).
I assume the StoryboardSegue object passed into this method is this EXIT that I dragged the button into, but there's no code to handle that object.
EDIT:
Also, I am not returning anything, yet the declaration of the method says it should return an IBAction. Why is the compiler not complaining?
Can you help me understand what am I missing?
Welcome to stackoverflow,
Firstly, IBAction is just a way to say 'you can drag to this' for Xcode, it's not really like a return type, i just consider it void.
Secondly, it is working as intended :D Basically you can now call this method like you have with hooking it up with the storyboard or you can manually call it like:
[self performSegueWithIdentifier:#"unwindToMainMenu" sender:self];
and all of the variables that you have set in your current view controller will be passed back to the source view controller that you're unwinding to.
e.g
-(IBAction)prepareForUnwind:(UIStoryboardSegue *)segue {
if ([segue.identifier isEqualToString:#"unwindToMainMenu"]) {
VcExample *vcExample= (VcExample *)segue.sourceViewController;
NSString *string = vcExample.aVariableNameYoureInterestedInFromTheVCYouUnwoundFromLolThisIsLong;
}
}
The unwind segue is just a built in feature, if you hook that button up to it, it will segue

Pushing a new VC into the navigationController seems to have no effect

This problem sounds quite basic but I don’t understand what I am overlooking.
I am trying to push a new view controller into a navigation controller, however the topViewController remains unaffected.
#import "TNPViewController.h"
#interface TNCViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
#implementation TNCViewController
-(void)userDidSelectNewsNotification:(NSNotification*)note
{
TNPViewController *nextViewController = [[TNPViewController alloc] init];
[[self navigationController] pushViewController:nextViewController animated:YES];
UIViewController *test = [[self navigationController] topViewController];
}
The test shows an instance of TNCViewController instead of TNPViewController. How is this possible?
UPDATE
Thanks for everyone's participation. The method name indicating notifications is a red herring. I found the problem, as Stuart had mentioned previously but deleted later on. (As I have high reputation score, I still can see his deleted post).
My initial unit test was this:
-(void)testSelectingNewsPushesNewViewController
{
[viewController userDidSelectNewsNotification:nil];
UIViewController *currentTopVC = navController.topViewController;
XCTAssertFalse([currentTopVC isEqual:viewController], #"New viewcontroller should be pushed onto the stack.");
XCTAssertTrue([currentTopVC isKindOfClass:[TNPViewController class]], #"New vc should be a TNPViewController");
}
And it failed. Then I set a breakpoint and tried the test instance above and it still was showing the wrong topviewcontroller.
At least the unit test works if I change
[[self navigationController] pushViewController:nextViewController animated:YES];
to
[[self navigationController] pushViewController:nextViewController animated:NO];
A better solution is to use an ANIMATED constant for unit tests to disable the animations.
This doesn't really answer your question about why your navigationController is not pushing your VC. But it is a suggestion about another possible approach.
You could instead add a new VC on the Storyboard and simply activate the segue when the userDidSelectNewsNotification method is activated. Then change the information accordingly to the event in the VC, specially since you are initializing it every time anyway.
This is something of a stab in the dark, but the issue is hard to diagnose without more information.
I see you're trying to push the new view controller in response to a notification. Are you sure this notification is being handled on the main thread? UI methods such as pushing new view controllers will fail (or at least behave unpredictably) when not performed on the main thread. This may also go some way to explaining the odd behaviour of topViewController returning an unexpected view controller instance.*
Ideally, you should guarantee these notifications are posted on the main thread, so they will be received on that same thread. If you cannot guarantee this (for example if you're not responsible for posting the notifications elsewhere in your code), then you should dispatch any UI-related code to the main thread:
- (void)userDidSelectNewsNotification:(NSNotification *)note
{
dispatch_async(dispatch_get_main_queue(), ^{
TNPViewController *nextViewController = [[TNPViewController alloc] initWithNibName:#"TNPViewController" bundle:nil];
[self.navigationController pushViewController:nextViewController animated:YES];
});
}
Also, it appears you are not initialising TNPViewController using the designated initialiser (unless in your subclass you are overriding init and calling through to initWithNibName:bundle: from there?). I wouldn't expect this to cause the transition to fail entirely, but may result in your view controller not being properly initialised.
In general, you might be better creating your view controllers in a storyboard and using segues to perform your navigation transitions, as #Joze suggests in his answer. You can still initiate these storyboard segues in code (e.g. in response to your notification) with performSegueWithIdentifier:, but again, be sure to do so on the main thread. See Using View Controllers in Your App for more details on this approach.
*I originally wrote an answer trying to explain the unexpected topViewController value as being a result of deferred animated transitions. While it is true that animated transitions are deferred, this does not prevent topViewController from being set to the new view controller immediately.

Open different ViewControllers from the same TableViewCell (Like facebook notifications)

Here's the issue, I'm trying to make an app that receives updates for different data in the same TableView (like facebook, lets say), that's not the issue however. All data has the same format, the thing is when you click on a row I want to open the corresponding view, I can identify the correct view to display. I try to use the following.
NavigationController.PushViewController (DestinationViewController, false);
When it gets triggered the objects that I created on the storyboard for the DestinationViewController appear to be null, and all the "glue" code is on the ViewDidLoad or ViewWillAppears (which is supposed to have every object instantiated)
I'm using Tab Bar Navigation Model.
PS: If I use the app and enter to my DestinationViewController it works.
So who knows an approach or has an example of something like this, or something that will get me near the place i want to go?
If you use
NavigationController.PushViewController (DestinationViewController, false);
I believe you need to allocate the view controller before you use it with
DestinationVC *vc = [DestinationVC alloc] init];
Correct me if im wrong everyone.
Also if you are using storyboards and have control dragged to different views, you should use the method
[self.navigationController performSegueWithIdentifier:#"" sender:self];
Then you can use the prepareForSegue method.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"whatYouNamedItInTheStoryboard"]) {
// do stuff for this segue
} else if ([segue identifier] isEqual.......) {
}
}
Hopefully this helps your problem.
You might want to post an image of your storyboard.
I solve the issue kinda like Michael and Paul, says.
Declare a public Attribute on the ViewController.
Then passed the current ViewController to the table source, on the RowSelected Method i put the selected value on the public property of the viewController
And in the PrepareForSegue method i check which type of data it is so i can assign the appropiate segue on the segue.DestinationViewController and pass the data.
I don't know if this is the best approach but, it works good.

Drawing in segue vs writing code to transition views - why does data sometimes not transfer?

Let's say I have a UILabel on ViewControllerA and and UITextField on ViewControllerB. I want to go to ViewControllerB and input text then press a button to go back to ViewControllerA. The UILabel should now read whatever was typed in the UITextField.
I was able to accomplish the above by using NSUserDefaults and also using delegation. I am using Storyboards to do this. My question is about the segues used in the storyboards.
It seems when using delegation I must go to and from the storyboard with code and not visually connect the view controllers with a segue in order for the data to transfer. Here is the code when I press a button on my ViewController A:
- (IBAction)pressFirstButton:(id)sender {
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
RBViewController2 *vc2 = [sb instantiateViewControllerWithIdentifier:#"ViewController2"];
[vc2 setDelegate:self];
[self presentViewController:vc2 animated:YES completion:NULL];
}
when passing the data back from ViewControllerB to ViewControllerA I do this:
- (IBAction)buttonSegueBackTo1:(id)sender {
NSString *sendThis = self.textFieldVC2.text;
[self.delegate passTextFieldInput:sendThis];
[self dismissViewControllerAnimated:YES completion:NULL];
}
No segue has been drawn between the view controllers and everything works fine. If I don't write this same code, and draw in a segue, the data won't pass backwards. However when I try passing data like this using NSUserDefaults I don't have to write the code to go to and from the view controllers. Instead I can simply connect the view controllers with a drawn segue. The weird thing is, if I'm trying to pass the data in using NSUserDefaults when manually coding the view controllers (an not drawing the segue) the data doesn't transfer.
I'm thinking maybe instead of writing the code in the -(IBAction) pressFirstButton:(id) sender method, I should be putting the code in the prepareForSegue method.
My question is why do drawn segues sometime cause data to be lost? Must all delegation be done without drawn segues? If NSUserDefaults require segues to transfer properly and delegation require code to transfer properly then if I have a view that requires both, it seems that NSUserDefaults will trump the delegation b/c the manual segue being used "resets" the view and only the NSUserDefaults data remains.
Normal segues (any other than unwind segues) ALWAYS create new view controllers. So, if you're using anything other than an unwind segue to go back to A, you're really not "going back", you're creating a new ViewControllerA. Unwind segues aren't normally used in a case like you're presenting, just going back one controller, but you could.
This situation also isn't a good place to use user defaults. The Apple recommended way, is to use delegation, like you do in the code your question. The way you show, is probably the best way to do it, rather than using a segue to go back. You certainly could use a segue to go forward though, and in that case you would implement prepareForSegue: so you can set yourself as the delegate and/or pass any data forward.
Your question is a little dense to parse but I think you are asking if there is a way to get pass data through segues. The answer is yes, and much better than the workarounds you are trying.
In your button you would call:
[self performSegueWithIdentifier:#"scrollerSegue" sender:self];
This will trigger the method below and it is here that you can set the data on the destination viewController. You can set abstract properties on the destination viewController but you can't populate an UIKit elements (labels, imageViews, etc.) because they don't exist yet. Instead set properties and then in viewWillAppear in the destination viewController, do the set up as needed. (alternatively, instead of passing data, you could just set the delegate and then call methods on the delegate to get the data as needed).
For getting data back, using the delegate and calling methods on it seems to be the Apple recommended way of doing things.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"scrollerSegue"])
{
ScrollViewController * target = segue.destinationViewController;
target.assetsArray = self.assetsArray;
target.delegate = self;
}
}

Popping ViewControllers when using delegation

I'm a bit weak in my Objective C I'll admit, my ultimate goal is pass data from ViewController3 back to ViewController1. Actually, that part is already done and successful. However when calling [self.navigationController popToRootViewControllerAnimated:YES] I get EXC_BAD_ACCESS.
ViewController1 <ViewController2Delegate>
- (void) didAddEventLocation:(Event *)event {
NSLog(#"Event name = %#", event.name); //Shows name successfully
}
ViewController2 <ViewController3Delegate>
- (void) didAddEvent:(Event *)event {
[self.delegate didAddEventLocation:event];
}
ViewController3
[self.delegate didAddEvent:event];
[self.navigationController popToRootViewControllerAnimated:YES];
Sorry for the poorly formatted code, just trying to simplify. Doing [self.navigationController popViewControllerAnimated:YES] has no problem, however it only takes me to ViewController2. I know I'm doing something very wrong here, but can't quite place my finger on how to resolve it. Let me know if I need to clarify.
Use Zombies to hunt down what is giving you the EXC_BAD_ACCESS. Some object has been released and is now being called on when you are popping back to the root view controller.
Try this link:
How do I set up NSZombieEnabled in Xcode 4?
Looks like, that one of your you controllers(first in my opinion) is deallocated. In VC3 method check that self.navigationController exists. Then you have to check all his VCs. I think that nothing holding first VC. Problem may be solved by using(for example) addChildViewController method of your navigation controller, or if smth will have a reference to your controllers.
Also, you can use NSNotificationCenter to send some information from one instance to another if you have problems with path between them.
HTH!

Resources