I have been fighting the following problem for a couple days now. I cannot find anything in the documentation about what I am doing wrong. I have crawled through the debugger and have dumped all sorts of info to the console, but cannot get my head around how to fix what I'm seeing.
My top level view controller is a subclass of UINavigationController. This contains a subclass of UIPageViewController which will be used to visualize my data. It also contains a subclass of UITableViewController for setting app settings. This "options" view is "linked" to the page view controller via a push segue. At this point, everything worked fine.
Problems started when I wanted to differentiate the visual transition between the various ways of visualizing the data (a horizontal scroll in the page view controller) and between bringing in the option view. So, I created a custom animation to "stretch in" the options controller from the top of the screen (on push) or "roll up" the options controller to the top of the screen (on pop). I set this animation on the navigation controller using the navigationController:animationControllerFor:operation:from:to: method.
Before displaying the options controller everything seems to work fine during device rotation. After displaying/dismissing the options controller, the layout of the data visualization controller breaks down during rotation. It displays correctly in the orientation that the device was in when the options controller was dismissed, but played out incorrectly in the opposite orientation.
It appears that the navigation controller (or possibly the page view controller) has forgotten how to handle accounting for the height of the status, navigation, and toolbar.
The following shows debugging screenshots illustrates the issue on a 5s simulator
- Initial state. Note that the content view (red) is below the status, navigation, and tool bars.
- Rotated the orientation to horizontal. So far everything is ok. The debug console shows the size the view is being "rotated" to (568x212), the before/after heights of the status, navigation, and tool bars, the screen size, the frame rectangle, and the layer position. I have not shown it here, but the layer anchor point never changes from (0.5, 0.5).
Note that the target rotation size is set using the following rules:
- rot_width(568) = old_frame_height(460) + sum_of_bar_heights(108)
- rot_height(212) = old_frame_width(320) - sum_of_bar_heights(108)
And the resulting frame size is set using the following rules:
- new_frame_width(568) = rot_width(568)
- new_frame_height(256) = rot_height(212) + change_in_bar_height(108-64)
And the resulting frame origin (0,64) is offset from the top of the screen by the sum of the status bar(20) and the navigation bar(44).
- Rotated the orientation back to vertical. Again, everything is ok. The debug console adds the same information as above for the "reverse" rotation. The rotation size and resulting frame size follow the same rules as above.
- Pushed the option editor controller view onto the navigation stack using a custom animation. The debug console adds the same information as above for the (currently not visible) content view as above. Note that nothing has changed.
- Popped the option editor controller view off of the navigation stack using a custom animation. The content view from above returns to view. The debug console adds the same information as above. Again, nothing has changed.
Note, The stacking order is different from before the option editor was made visible.
This stack re-ordering does NOT hold when using the default transition animator (by returning nil from pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted)
- Repeat of step 1. This is where things being to go "wonky." The content view no longer fits snuggly against the navigation and tool bars. The debug console shows that everything going into the rotation looks the same as in step 1.
BUT, the result of the rotation is different from step 1. The rules seem to have changed. The resulting frame size is no longer adjusted to account for changes in status, navigation, and toolbar heights and the frame origin does not change from what it was before the rotation.
- Repeat of step 2. It appears that everything is fixed, but that is only because it is playing by the new resizing rules.
My animator class is shown (in its entirety, except for the probes used to produce the debugging info shown above) here:
class OptionsViewAnimator: NSObject, UIViewControllerAnimatedTransitioning
{
var type : UINavigationControllerOperation
init(_ type : UINavigationControllerOperation)
{
self.type = type
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
{
return 0.35
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
{
if self.type == .push { showOptions(using:transitionContext) }
else if self.type == .pop { hideOptions(using:transitionContext) }
}
func showOptions(using context: UIViewControllerContextTransitioning)
{
let src = context.viewController(forKey: .from)!
let srcView = context.view(forKey: .from)!
let dstView = context.view(forKey: .to)!
var dstEnd = context.finalFrame(for: context.viewController(forKey: .to)!)
let barHeight = (src.navigationController?.toolbar.frame.height) ?? 0.0
dstEnd.size.height += barHeight
dstView.frame = dstEnd
dstView.layer.position = dstEnd.origin
dstView.layer.anchorPoint = CGPoint(x:0.0,y:0.0)
dstView.transform = dstView.transform.scaledBy(x: 1.0, y: 0.01)
UIApplication.shared.keyWindow!.insertSubview(dstView, aboveSubview: srcView)
UIView.animate(withDuration: 0.35, animations:
{
dstView.transform = .identity
}
) {
(finished)->Void in
context.completeTransition( !context.transitionWasCancelled )
}
}
func hideOptions(using context: UIViewControllerContextTransitioning)
{
let dst = context.viewController(forKey: .to)!
let srcView = context.view(forKey: .from)!
let dstView = context.view(forKey: .to)!
let dstEnd = context.finalFrame(for: context.viewController(forKey: .to)!)
dstView.frame = dstEnd
srcView.layer.position = dstEnd.origin
srcView.layer.anchorPoint = CGPoint(x:0.0,y:0.0)
srcView.transform = .identity
UIApplication.shared.keyWindow!.insertSubview(dstView, belowSubview: srcView)
UIView.animate(withDuration: 0.35, animations:
{
srcView.transform = srcView.transform.scaledBy(x: 1.0, y: 0.01)
}
) {
(finished)->Void in
context.completeTransition( !context.transitionWasCancelled )
}
}
}
Thanks for any/all help. mike