Why doesn't backgrounding and re-opening an app call viewDidAppear? - ios

I have 2 view controllers, vc1 and vc2. A modal segue is invoked from vc1 when I want to load vc2. Say I background the app when vc2 is showing. Why isn't viewDidAppear called when the app is re-opened to the view that was left off? How else am I able to detect every time vc2 appears?

You could register for the UIApplicationDidBecomeActiveNotification in VC2 and call viewDidAppear from there. Do this in your viewDidLoad of VC2:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(somethingThatWillCallViewDidAppear:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
As rmaddy says below, make sure to remove the observer in dealloc or viewDidUnload.

Related

navigationController popToRootViewControllerAnimated:YES does not call viewWillAppear

If I run, within IOS not swift,
[[navigationController popViewControllerAnimated:YES] viewWillAppear:YES]
it calls viewWillAppear on the previous VC.
I need to jump back to the root VC and have that ViewWillAppear called; however,
[[navigationController popToRootViewControllerAnimated:YES] viewWillAppear:YES] gives me a coding error.
Is there a way to do this without applicationWillEnterForeground: and applicationDidBecomeActive: methods in my appDelegate
I have tried within init
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground:)
name: UIApplicationWillEnterForegroundNotification
object:nil];
And in body
-(void) willEnterForeground:(Notification*)NotificationCenter
{
(self viewWillAppear:YES);
}
This doesn't seem to call viewWillAppear either.
Your code seems quite incorrect...
First, you should never call viewWillAppear - that is the system notifying your controller that the view will appear.
Second, just calling:
[self.navigationController popViewControllerAnimated:YES];
will navigate to the previous view controller in the stack, or:
[self.navigationController popToRootViewControllerAnimated:YES];
will navigate to the Root view controller.
In both cases, viewWillAppear is called by the system when the view, well, will appear.

Dealing with the same NSNotification in multiple view controllers

I'm using the below notifications to reload ViewControllerA when my app comes back from background mode. It works correctly, but the applicationEnteredForeground: method gets called every time when I open the app again. For example if I close the app when ViewControllerB or ViewControllerC is on the screen and open it again the method will be called despite the viewDidLoad of ViewControllerB doesn't contain applicationEnteredForeground: method. I would like to know that how could solve this issue? My goal is to use applicationEnteredForeground: only when ViewControllerA was on the screen before I closed the app.
As a possible solution I would just remove the NSNotificationCenter in the viewDidDisappear, but since the observer is in the viewDidLoad it won't work when the user navigates back, because viewDidLoad won't be called again. Is there any fix for this?
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)applicationEnteredForeground:(NSNotification *)notification {
// do stuff...
}
You should remove ViewController A's event listener on viewWillDisappear and add it in viewWillAppear. That way, VC A will only be listening when it is the visible view controller.
You can check if a view controller is on screen by checking the window property of it's view. It will work in most standard cases.
- (void)applicationEnteredForeground:(NSNotification *)notification
{
if (self.view.window == nil) {
// Not on screen
return;
}
// do stuff...
}

How to pass delegate between ViewControllers

I have three View Controllers. Let's call them BaseVC, firstVC and secondVC. FirstVC is presented modally by BaseVC. SecondVC is pushed by firstVC. There is a one button on each firstVC and secondVC. By clicking them, I want to dismiss the current VC and let BaseVC do something. So I created a protocol, let BaseVC obey it, and set BaseVC as firstVC's delegate. When I set secondVC's delegate from firstVC, breakpoint show it succeeding. However when I call delegate from secondVC, it shows _delegate is nil.
Is it because delegate is always a weak property? So how could I pass delegate between View Controllers or is there any other way to solve this problem?
You can use postNotification while dismissing the VC and add the observer on baseVC to do some operation.
You could use [self.navigationController dismissViewControllerAnimated:YES completion:nil]; in button action to dismiss the view controller.
Before this you need to post the notification [[NSNotificationCenter defaultCenter] postNotificationName:#"NotificaitonBaseVC" object:nil]; and add the observer in baseVC's viewDidLoad method as follows
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomeOperation:) name:#"NotificaitonBaseVC" object:nil];

Removing NSNotification in a UIViewController

So I have a VC that i register for a notification in viewDidAppear: Even when the VC is not the main focus, as when I push another VC on the stack, I still want that VC to receive the notification. But after that VC is no longer needed, i.e. it is popped off the stack, I want to remove it as an observer for that notification.
Where do I do that? viewDid/WillUnload: are no more, and i tried dealloc, but it never gets called. So this seems to mean that the NotificaitonCenter will retain the VC, and it will never get released when it is popped off the stack.
In the dealloc method. That is the established convention nowadays.
The NotificationCenter is not retaining the VC. Where did you get that impression?
You should put the code in the dealloc method of the UIViewController
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
If you are using Swift then you can put it in the deinit method of the UIViewController
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

How to perform segue after current presentation is completed

I have a situation in my app where I need to dismiss a modal view controller via unwind segue, then right after that present a different modal view controller. To do so, I am simply delaying the call to present the new view controller by 1.0 seconds. This may not work all the time, perhaps if it takes longer to dismiss for some reason, the second view controller won't be able to appear because a transition is occurring. I just ran into this situation, although it did work in this case. This was logged:
Warning: Attempt to present on while a presentation is in progress!
I am looking for a better solution. I wondered if it would be possible to throw up the new view controller after the first one is fully dismissed, via a callback, but there is no performSegueWithIdentifier that has a completion block.
How can I dismiss a modal view controller then present a new one afterwards, always ensuring there will be no conflict?
This is my current solution:
[self performSegueWithIdentifier:#"Unwind Segue" sender:self]; //dismiss modal or pushed VC
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[tabBarController performSegueWithIdentifier:#"Show New VC" sender:self]; //present new modal VC
});
It took me awhile to figure this one out myself. iOS 7 introduced the transitionCoordinator which is only present on a UIViewController during an animation. In short, you must register your completion block in another thread on the main queue (which will run after the animation kicks off).
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// ... prepare your for segue
dispatch_async(dispatch_get_main_queue(), ^{
[self.transitionCoordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
// Segue animation complete
}];
});
}
What you can also do is add a notification on the new controller's viewDidAppear function using NSNotificationCenter. Then, you attach a listener to the notification and when you see the notification show up, you do your next transition.
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:#"transitioned to new controller!" object:nil]];
}
elsewhere, just add this to listen to the notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedNotification:) name:#"transitioned to new controller!" object:nil];
Don't forget to stop listening after you receive your notification (unless you desire to remain observing) for this notification, otherwise you will continue observing all notifications throughout your application:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"transitioned to new controller!" object:nil];

Resources