I have 2 viewcontrollers and I want to send a notification out from one to the other and have a label changed based on the name of the notification (pressing a UIButton). I just started using segues and found they are a very useful way to get from one view to another. The problem I am facing is that using a segue (modal at the moment), the second view controller is not receiving the notification. I believe that segues release and create new view controllers when used, not sure. Is there any way around this?
Thanks!
I think using NSNotification is not the right method for passing data from one VC to another. Like Rog said, use prepareForSegue and use a property on the destination VC:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Always check to make sure you are handling the right Segue
if ([segue.identifier isEqualToString:#"mySegue"]) {
MyOtherViewController *movc = segue.destinationViewController;
movc.myProperty = #"MyValue";
}
}
Use prepareForSegue for going-to a segue, and use the delegate pattern for coming back. Within prepareForSegue, set the originating viewController as the delegate of the destination viewController. When returning, have the destination viewController call it's delegate (the original viewController) for whatever method you implement.
Related
I have 2 viewcontrollers, with a segue between them. My main viewcontroller is called ViewController. in my second viewcontroller called PreferencesViewController I have a button that has to change some values in my main ViewController. But it doesn't...
I have set up a delegate protocol in my PreferencesViewController.h like I saw on a few examples on Stack Overflow (yes I did google it ;) ).
I also have imported the PreferencesViewController.h file to my main ViewController, aswell I told the viewcontroller to be the delegate: <PreferencesViewControllerDelegate>
I also implemented the method in my implementation of the main ViewController. But this method doesn't get called when I press the button. I implemented [self.delegate methodExample]; in to my IBAction
I've read a lot about setting the delegate. Where do I need to do this? do I need to create and alloc/init a instance of my PreferenceViewController? I tried this and then told the instance instance.delegate = self but that didn't work also...
Hope someone can help me out!
You're correct that you need to set the delegate. The easiest place to do this when using segues is in the source view controller (ViewController, in this case) using he prepareForSegue:sender: method. The segue object passed to this method has a reference to the destination view controller; you probably want to do something like:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Your preferences segue identifier"]) {
PreferencesViewController *preferencesViewController =
segue.destinationViewController;
preferencesViewController.delegate = self;
}
}
Since you're using segues, the system is doing the alloc/init of the new view controller for you; this method is called by the system to let you do any hooking up of the new view controller before it's displayed.
i initialize tables, data etc in my main ViewController. For more settings, i want to call another Viewcontroller with:
DateChangeController *theController = [self.storyboard instantiateViewControllerWithIdentifier:#"dateChangeController"];
[self presentViewController:theController animated:YES completion:^{NSLog(#"View controller presented.");}];
And some clicks later i return with a segue (custom:)
NSLog(#"Scroll to Ticket");
[self.sourceViewController presentModalViewController:self.destinationViewController animated:YES];
My Problem:
After returning to my main ViewController, viewDidLoad is called (everytime).I read, that the ARC releasing my mainView after "going" to the other ViewController and calling the viewDidUnload Method, but i want to keep all my data and tables i initialize at the beginning..
Any solution? Thank you very much!
The problem is that you are doing this:
main view controller ->
date change controller ->
a *different* main view controller
In other words, although in your verbal description you use the words "returning to my main ViewController", you are not in fact returning; you are moving forward to yet another instance of this main view controller every time, piling up all these view controllers on top of one another.
The way to return to an existing view controller is not to make a segue but to return! The return from presentViewController is dismissViewController. You do not use a segue for that; you just call dismissViewController. (Okay, in iOS 6 you can in fact use a segue, but it is a very special and rather complicated kind of segue called an Unwind or Exit segue.)
If you do that, you'll be back at your old view controller, which was sitting there all along waiting for your return, and viewDidLoad will not be called.
So, this was a good question for you to ask, because the double call of viewDidLoad was a sign to you that your architecture was all wrong.
I think you're taking the wrong approach - viewDidLoad is supposed to be called when it is called - it's a notification to you that the view is being refreshed or initially loaded. What you want to do is move that table initialization code somewhere else, or, at least, set a Boolean variable so that it is only called once. Would it work to create an object that has your table data when viewDidLoad is first called, then to check it to see if it's already been called?
I have a stack of UIViewController subclasses. Each modifies a NSManagedObject model. Many of them also present their own modal view controllers.
I need to save changes to the NSManagedObjectContext when a user either 'pops' the view controller or pushes the next view controller.
Currently, I'm hiding the default back button and setting my own UIBarButtonItem with a target of self and a custom action.
This works okay, but ideally, I want to use the default back button and run code before the pop. Is there a way I can run my own code before the pop?
(I'd prefer not to put code into viewWillDisappear as persisting to disk can be expensive and this method can also be triggered by modals being displayed by view controller.) Can it be done?
You can do it in viewDidDisappear, after checking that self is either 1) the second last element in self.navigationController.viewControllers (the case where the next VC just got pushed) or 2) self.navigationController is nil (the self VC just got popped).
Yes.. Navigation controller has a delegate which indicates when a view controller popped or pushed.. You can use that to do your task...
Add following method in your code:
- (void) viewWillDisappear:(BOOL)animated{
//your code here
}
I use viewWillDissappear to make any changes persistant.
If required i Use viewWillAppear to recoginze any changes (reload the data) that may have taken place while other puhed view controlers did their work.
For pop check isMovingFromParent in viewWillDisappear
func viewWillDisappear(_ animation:Bool){
super.viewWillDisappear(animation);
if isMovingFromParent {
// your code here
}
}
The normal way to open another screen from within a FirstVC screen, so one can close it again is like this:
SecondVC *secondVC = [[SecondVC alloc] initWithNibName:#"SecondVC" bundle:nil];
secondVC.delegate = self; //needed to dismiss
[self presentModalViewController: secondVC animated: YES];
while the SecondVC.m has to import a protocol that declares the method called to close the SecondVC
So I always have to create a protocol file SecondVCProtocol.h which basically looks like this:
#protocol SecondVCProtocol <NSObject>
-(void)secondVCDidFinish;
#end
Then in SecondVC.m I need to import this SecondVCProtocol.h file and now can finally call
[self.delegate secondVCDidFinish]
I have just completed another Android app and beeing back in the iOS world, I find this rather cumbersome. - needing to define such a protocol in a separate file & needing to use a delegate - all just to do the most normal task like closing a screen...
Isn't there an easier less complex way or is this just the way it has to be done?
for example like [self dismiss] in SecondVC - no delegate, no protocol - wouldn't his be really nice?
Many thanks!
You can just call
dismissViewControllerAnimated:completion:
on the presented viewcontroller, although it is not exactly best practice.
From Apple's documentation:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, it automatically forwards the message to the
presenting view controller.
Also from Apple's documentation though (http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html)
When it comes time to dismiss a presented view controller, the
preferred approach is to let the presenting view controller dismiss
it. In other words, whenever possible, the same view controller that
presented the view controller should also take responsibility for
dismissing it. Although there are several techniques for notifying the
presenting view controller that its presented view controller should
be dismissed, the preferred technique is delegation.
What you describe is not the easiest pattern. Actually you should do something very similar to what you suggested would be nice. When SecondVC is ready to be dismissed it just calls, for example:
[self dismissViewControllerAnimated:YES completion:NULL];
From the UIViewController documentation:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
Let's say that I have 2 UIViewControllers on a stack within a UINavigationController. In the "parent" we call "[self.navigationController pushViewController:childViewController animated:YES];" upon some user action and in the "child" we call "[self.navigationController popViewControllerAnimated:YES];" upon some user action.
How can we recognize within the parent that we just got back?
Is there some "event" driven method that can recognize that this popViewControllerAnimated action was called from the child?
It seems like you're using this child controller as a modal in that it can be 'dismissed'. If that is the case, try to follow Apple's patterns that they use for UIAlertViews.
If that is the case, I'd do either of the following to implement a delegate pattern(delegate vs block is a huge debate that I will not get into here) so the owner(the one that pushes the child on) knows when its dismissed:
Create a protocol (ChildControllerDelegate), have one method in it childControllerWasDismissed:(ChildController *)
add a block property(make sure its a copy property, not retain) to the ChildController
You'll then want to call the delegate method or block on viewDidDisappear. If you want finer grain control, have a delegate method or block that corresponds viewWillDisappear / viewDidDisappear.
I'd successfully resolved this by setting navigationController?.delegate = self and then implementing this method to determine whether the current view controller is shown again after a pop.
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if viewController == self {
// we got back
} else {
// some other controller was pushed
}
}
There's a few way to hint at that. What you can do, is call the popViewControllerAnimated from the parent. You can do that by passing a block to the child controller that would then execute the said block and thus popping would be done by the parent controller.
You can also use the UINavigationController delegate to be notified when a UIViewController will be dismissed:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
This method will let you know which VC will be shown and you can check if the current (not yet popped) VC is the child you were looking for.
You can also do some trick with - (void)viewWillAppear: but this might require some hacks.
First read this, it will help you understand what is going on with view controllers.
Then implement viewWillAppear: and viewDidAppear: in your parent view controller to log a message.