I have created a custom container controller that works similarly to a UIPageViewController
so that I could implement some custom transitions & data source logi
So as is often the way with SO, you spend hours fruitlessly working on a problem, and then right after you post the question, you find the answer...
I looked at the appearance callbacks of UINavigationController
's built-in interactivePopGestureRecognizer
to see what happens when the cancelling the transition, and it appears that my assumption about what appearance methods should be called was wrong.
For a view controller that is successfully transitioned to, the following callbacks are received (as you'd expect):
transition
finished
viewWillAppear: ----------> viewDidAppear:
But for a view controller transition that is unsuccessful (i.e. the transition was cancelled), the view controller receives the following callbacks:
transition immediately
cancelled followed by
viewWillAppear: ----------> viewWillDisappear: ----------> viewDidDisappear:
So we get a bit more of those dodgy view appearance semantics; I'm not sure how a view can disappear if it hasn't yet appeared! Oh well... I suppose it's slightly better than a view saying it will appear, and then nothing happening.
So, returning to my CustomTransitionContext
's transitionCompletionHandler
code, the solution looks like this:
...
// Register a completion handler to run when the context's completeTransition: method is called.
__weak CustomContainerController *weakSelf = self;
__weak CustomTransitionContext *weakContext = context;
context.transitionCompletionHandler = ^(BOOL complete) {
if (complete) {
// End the appearance transitions we began earlier.
[oldVC endAppearanceTransition];
[newVC endAppearanceTransition];
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:weakSelf];
} else {
// Before ending each appearance transition, begin an
// appearance transition in the opposite direction.
[newVC beginAppearanceTransition:NO animated:[weakContext isAnimated]];
[newVC endAppearanceTransition];
[oldVC beginAppearanceTransition:YES animated:[weakContext isAnimated]];
[oldVC endAppearanceTransition];
[newVC removeFromParentViewController];
[oldVC didMoveToParentViewController];
}
}
....
So now the appearance callbacks look like this:
oldVC viewWillDisappear:
newVC viewWillAppear:
// ... some time later transition is cancelled ...
newVC viewWillDisappear:
newVC viewDidDisappear:
oldVC viewWillAppear:
oldVC viewDidAppear:
Also, subsequent transitions now trigger the callbacks as expected (since we're closing off all transitions with calls to endAppearanceTransition
).