How to present view controller properly? - ios

I have a pretty simple application with a couple of UIViewControllers (say VC_A and VC_B). Every screen has a button that allows to switch to another screen (no UINavigation is used).
App schedules a local notification, which, when expired, should present another view controller (VC_N - no matter what screen is active at the moment).
The problem is that sometimes application throws:
'NSInternalInconsistencyException', reason: 'Attempting to begin a modal transition from
<VC_A: 0x2021e0> to <VC_N: 0xf84b970> while a transition is already in progress.
Wait for viewDidAppear/viewDidDisappear to know the current transition has completed'
What is the proper way to implement such behaviour?

A. Use UIViewController's new presentViewController:animated:completion to present all three controllers instead of the old modal method.
B. Create a Boolean flag and initialize it to NO.
C. Before any view controller presentation, check for this flag. If YES, set to NO. And then present the VC. In the completion block, set the flag to YES again.
D. The app should ignore this flag when present the initial view controller whether VC_A or VC_B.
One pitfall to this is that if a button got pressed or the local notification expired while already a view controller was in a transition state, then the new VC won't get presented. One can improve upon this logic to present it after if needed.

When the timer expires don't call the view presentation in that timer handler method. Instead try putting the view presentation call in a separate method and use [self performSelector: withObject: afterDelay:] to call that method (delay could be 0.1]. This should get the view presentation done when current transitions complete.

Related

Observer for monitoring when view controllers' viewDidAppear have been executed

Summary of question
UINavigationControllerDelegate:didShowViewContoller makes it possible to get notified whenever any view controller has been displayed (as opposed to being loaded), provided its within the context of a navigation stack.
I want to know if such observation is possible for all view controllers if there isn't a navigation stack.
More background
I have an app where view controllers can suddenly appear based upon timers and local notifications firing, thus their appearance is effectively random.
If one VC triggers and gets displayed at the same time as another was in the process of getting displayed then there can be an issue (if you're experienced with iOS you'll be aware if one VC pushes another from within its viewDidLoad, rather than its viewDidAppear you will get an "attempting to present X on Y whose view is not in the window hierarchy" error)
How I solve this is I have a list of VCs to display and they get displayed by a view controller co-ordinator which implements UINavigationControllerDelegate's didShowViewContoller and doesn't display the new VC until didShowViewController has been invoked.
This works perfectly.
But now my problem is I want to do a similar thing for an app that isn't using a navigation controller, and thus I can't use UINavigationControllerDelegate:didShowViewController to observe globally when a view controller has been displayed. Does anybody know of another elegant mechanism for doing so?

popViewController while pushViewController is animating resulting in corrupted navigation bar

I have a situation in my app where a navigation controller pushViewController:animated:YES is triggered by a user. The user can also trigger a popViewController:animated:YES by tapping another control. The intent is that the popViewController is the undo or inverse of the pushViewController.
However, if the user triggers the popViewController:animated:YES while the pushViewController animation is still happening, I get a message logged to the console:
2014-08-22 08:26:36.601 MyApp[22380:60b] nested pop animation can result in corrupted navigation bar
2014-08-22 08:26:36.960 MyApp[22380:60b] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
And indeed, the navigation bar does get corrupted: the back button is no longer visible. I have to go do something else then come back to the affected page in my app to get it working properly again.
What can I do to avoid this race condition? My first thought was protect the popViewController call with a check to see if a current navigation animation is already occurring, and waiting for that to finish (or even canceling the popViewController call completely). However, I haven't been able to find a way of detecting that an action is already occurring.
There are two solutions to this problem. I suggest you to implement first one.
1 . Avoid user interaction on multiple controls at a time by setting exclusive touch to them.
Set exclusiveTouch property to YES for those controls if they shares superView(parent view) else you will have to set this property YES to their parent views.
2 . Implement UINavigationControllerDelegate protocol in that view controller where user is tapping multiple controls at a time.
– navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
Set a flag when first delegate method gets called & reset it in second. Use this flag on every push/pop operation.

iOS Container View Controller - using PerformSelector after transitionFromViewController

We have a container view controller and want to be able to call "PerformSelector" on one of the "sub" view controllers in that container, right after starting a transition, i.e.
[self navigateSubViewControllerTo:newSubViewController];
... some time later, elsewhere in the stack, a selector will be performed on the top visible VC
[subViewController performSelector:#selector(foo)];
The call to transitionFromViewController happens in navigateSubViewController.Unfortunately, since transitionFromViewController happens asynchronously, we are finding that the performSelector call gets applied to the "before" sub view controller, not newSubViewController. I.e. it is happening before the transition happens.
Any thoughts on how to have performSelect not happen until the sub view controller transition happens?
You can just call performSelector in the completion block of
transitionFromViewController:toViewController:duration:options:animations:completion:
UIViewController provides a callback beginAppearanceTransition:animated: which is exactly for this purpose. Simply implement it in your subviewcontrollers and your good to go ;)

Design patterns for handling UILocalNotifications (how to know where user is within branching app)

I have an app which uses UILocalNotifications (though the question would also be relevant to PushNotifications). When the user receives a local notification, depending on the state of the app and the choice they make in the notification alert, I want to present a specific viewController. The issue is they may currently be in a "pushed" viewController through a segue or in a modal ViewController and I am not currently tracking where the user is within the possible branches.
I am wondering if there is any sort of design pattern or standard way of handling a mixed presentation mode app like this? Particularly if there is any built-in way to determine which is/are the active ViewController(s) without my having to setup a breadcrumb system. This would matter, for example, if the user is already viewing the viewController to be called as a result of the localNotification.
I realize my need/explanation is a little muddled. I'll edit and update this if anyone needs more info.
OK - I think I have a solution through the Apple docs View Controller Programming Guide for iOS: Presenting View Controllers from Other View Controllers
Each view controller in a chain of presented view controllers has
pointers to the other objects surrounding it in the chain. In other
words, a presented view controller that presents another view
controller has valid objects in both its presentingViewController and
presentingViewController properties. You can use these relationships
to trace through the chain of view controllers as needed. For example,
if the user cancels the current operation, you can remove all objects
in the chain by dismissing the first presented view controller. In
other words, dismissing a view controller dismisses not only that view
controller but also any view controllers it presented.
So I can cancel any chain of modal viewControllers by dismissing the first modal viewController in the chain.
I can determine if there is currently a modal viewController being presented using:
self.window.rootViewController.presentedViewController
If the property is null then there no modal viewController is currently being presented. And for completeness (hackishness..) I can test is the presentedViewController has a presentedViewController, etc.:
self.window.rootViewController.presentedViewController.presentedViewController

Get Notified of popViewController

I need to save my data by calling a method I already have when a viewController is popped using the back button created by the UINavigationController.
Is there a way to get a delegate callback or a notification I didn't see anything in the documentation?
In your viewWillDisappear method, you can check the property:
[self isMovingFromParentViewController]
to find out if the view is disappearing as a result of being popped off the stack or not.
You will be notified that the view will be disappearing, with the view controller method viewWillDisappear:, however, this will be called each time the view is moved offscreen, whether that means the controller is popped or a new controller is pushed, or whatever else may cause your view to disappear.
Perhaps a better design would be to save your data in your controllers dealloc method. Normally, a navigation controller is the owner of a view pushed into it's stack, so popping it usually causes it to deallocate. This isn't always the case though and depends on how you've written your app.

Resources