How to perform segue after current presentation is completed - ios

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

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.

How to dismiss multiple view controllers which have been presented not pushed?

Scenario:
I need to show 3 or more popups one after the other on button click in each popup. I have created a different viewcontroller and xib files for each popup. So for displaying each popup I have used presentViewController instead of pushViewController.
That is, I have used this:
[self presentPopupViewController:searchPopUpView animationType:0];
instead of
[self.navigationController pushViewController:searchPopUpView animated:YES];
For dismissing a popup, the following code has been written:
[self dismissPopupViewControllerWithanimationType:0];
Issue:
The popups are displaying perfectly, but the background gets darker and darker whenever a popup shows up. After all popups have been dismissed I have to finally click on the blank screen to remove those darker parts. How to overcome this issue?
I think you are using MJPopupViewController to show pop-up.
If it is so, Then try this.
Suppose there is a controllerA from which you want to show a pop-up controller popupControllerB.
Then in your controllerA add Notifications Observer
Code to write in controllerA :
// Add Notification Observer when your view initialise.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(dismissPopup) name:#"DISMISS_POPUP" object:nil];
In viewWillDisappear remove the notifications observer
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
This method will be called when you Post-notification from your popupControllerB
-(void)dismissPopup {
[self dismissPopupViewControllerWithanimationType:MJPopupViewAnimationFade];
}
And In popupControllerB, Where you want to dismiss the Pop-up, write this code.
[[NSNotificationCenter defaultCenter] postNotificationName:#"DISMISS_POPUP" object:nil];
Above line of code will call a method written in your controllerA and dismiss the pop-up properly.
If you want to dismiss presented UIViewControllers you can use this code. I have used this approach to dismiss presentedViewControllers. It will dismiss all your presentedViewControllers on your rootViewController.
UIViewController* presVC = self.window.rootViewController;
while (presVC) {
UIViewController* temp = vc.presentingViewController;
if (!temp.presentedViewController) {
[vc dismissViewControllerAnimated:NO completion:^{}];
break;
}
vc = temp;
}

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

NSNotification is being called multiple times from UITabBarController

I have a UITabBarController, which has 4 tabs. Each one of those tabs is a separate UIViewController. I have objects on each one of those 4 VC's that use NSNotification's to perform actions upon the press of a certain object. The 4 VC's all respond to the notification in the same way because it is a similar object on each page. When this object is pressed it presents a view onto the current view controller. The problem is that if I move to any of the other 3 tabs now that view is also on their VC. That is because the notification is being responded to on all 4 tabs when it is pressed on any of the VC's. I am needing it to only respond to the VC that the user is currently on and not any of the others that are in the tab bar.
Is there a way to get this to work properly? Maybe a threshold where you can set how many times the notification can perform its selector after being called? That way I could set it to 1 and at any given time if that notification is called the selector can only be called 1 time.
The type of object implementation that I'm using requires me to use NSNotification's so there is no way to change how I interact.
edit:
This viewDidLoad method is on the top level VC for the 4 VC's in my tab bar. All 4 of them either use this directly or inherit from it.
- (void) viewDidLoad
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didSelectItemFromCollectionView:) name:#"didSelectItemFromCollectionView" object:nil];
}
Action Handler:
- (void) didSelectItemFromCollectionView:(NSNotification *)notification
{
NSDictionary *cellData = [notification object];
if (cellData)
{
NewVC *pushToVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PushToVC"];
[self.navigationController pushViewController:pushToVC animated:YES];
}
}
Each of the 4 VC's is a UITableViewController and have cells with an object that can be pressed. This NSNotificationCenter action is what allows the operation to work.
You must have implemented the NSNotificationCenter's -addObserver:selector:name:object: method in the -viewDidLoad of every viewController
Example:
- (void)viewDidLoad
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
Instead of having this in -viewDidLoad, move it within -viewWillAppear and implement removeObserver:name:object: in -viewWillDisappear.
This way, only the viewController that is currently on will respond to the notification.
Example:
- (void)viewWillAppear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] removeObserver:self
name:#"TestNotification"
object:nil];
}
- (void)doSomething:(NSNotification *)userInfo
{
//...
//if you push a viewController then the following is all you need
[self.navigationController pushViewController:vcSomething
animated:YES];
//however.... if you're instead presenting a viewController modally then
//you should implement "-removeObserver:name:object: in this method as well
//[NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
//[self presentViewController:vcSomething
// animated:YES
// completion:nil];
//OR... in the completion parameter as:
//[self presentViewController:vcSomething
// animated:YES
// completion:^{
// [NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
// }];
}
EDIT:
You (#Jonathan) commented:
I really appreciate your answer and it has helped me out a lot! I
actually ran into one more scenario where this issue occur's and I'm
not sure how to figure it out. Right now I have a VC that presents
another VC modally. Each one has observers for the same
NSNotification. Everything performs perfectly well when I'm in the
modally presented VC, but once I dismiss that VC and return to the
underlying one I have the same issue where the notification is being
called multiple times. Do you have an idea for a solution in this
case?
Now... regarding this...
FIRSTLY... NOTE:
Multiple -addObserver:selector:name:object: will register the specified notification multiple times (means... same notification being registered for N times will call invoke the target selector N times)
Presenting a ViewController (call it Child) from, say, Parent viewController will NOT invoke the -viewWillDisappear: of the Parent
where as...
Dismissing the Child viewController will still invoke -viewWillAppear: of the Parent
This creates an imbalance in the logic and if not handled (as per the commented lines in the code example of the doSomething method above), it results in the Parent registering for the notification multiple times (as it's -viewWillAppear: method is called more often than not -viewWillDisappear:)
also see: similar question
it should be called only once so that it never gets called again
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(methodName:) name:#"name" object:nil];
});

How can I dismiss view controller or unwind from it from NotificationCenter observer?

I'm new to iOS development so I feel like I'm missing some fundamental knowledge here.
I have view controller (VC) that displays some information that comes form the server. My VC has two notification center observers - one for successful response from server and one for error.
I added observers like this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(getPlaceInfoFailed:)
name:kGetPlaceInfoRequestDidFailBlockNotification
object:nil];
In case of error I want to display alert and navigate to previous view controller. Let's name them view controllers A and B.
I'm using storyboards.
My problem is that when I return to A UI is completely messed up - navigation bar has random stuff, my table view has garbage and etc. Eventually app crashes.
Here is how I tried to dismiss my VC B
- (void)getPlaceInfoFailed:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(), ^{
//[self.navigationController popViewControllerAnimated:YES];
//[self dismissViewControllerAnimated: YES completion: nil];
//[self.presentingViewController dismissViewControllerAnimated: YES completion: nil];
//[self.presentedViewController dismissViewControllerAnimated: YES completion: nil];
//[self performSegueWithIdentifier:#"exitSegue" sender:self];
});
}
Version [self performSegueWithIdentifier:#"exitSegue" sender:self]; works when I invoke it, for example, in button action handler.
But not in observer :( As you can see I tried to invoke performSegue in UI thread - no difference.
What am I doing wrong ?
Thank you!!!
What I would suggest is just dismiss the VC when the notification fires and handle the UI mishap in viewWillAppear of your parent controller/presenting controller - VC A.
This code should be in B if I am not wrong
- (void)getPlaceInfoFailed:(NSNotification *)notification {
[self dismissViewControllerAnimated: YES]; or try
[self dismissModalViewControllerAnimated:YES]; //dep'd in ios 6.
}
Looks like my problem was that I was calling performSegueWithIdentifier before viewDidAppear and that why it didn't work.
How I can handle two asynchronous events - view creation and requesting server I don't know yet.

Resources