Thursday, 8 November 2018

When implementing custom view controller presentations, where to apply presented view's constraints?

I've read through all of Apple's documentation about custom modal presentations and I couldn't find this answer. When presenting a view controller using a custom animation, we can return 3 things (if the transition is not interactive; interactive can return 4): a presentation controller and two animation controllers (one for present and one for dismiss).

In both the presentation controller and the present animation controller, none of Apple's code includes constraints. The extent of the discussion about frames is Apple's recommendation to set the frame of the presented view controller's view in the present animation controller (below):

// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;

...and that's it.

The problem I have is that the double-height status bar is not being recognized by these presented view controllers in the simulator (and the devices that I own). To be more precise, no single solution works in all simulators--the solutions that work in the new simulators (like iPhone 8) don't work in the older simulators (like iPhone 5). If I let Apple handle the presentation using a default UIKit animation, the double-height status bar is handled fine by the presented view controller; therefore, I can assume that the constraints in the presented view controller are not the issue.

So I turned to the presentation controller's containerViewDidLayoutSubviews and containerViewWillLayoutSubviews methods, without any luck:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.frame = containerView!.bounds
}

The code above only works in the simulator the first time a double-height status bar is introduced; from there on, this method becomes unresponsive. To verify this:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.frame = containerView!.bounds
    print("did layout")
}

the above method stops printing to console after the first introduction of the double-height status bar. But if I change the task to something that doesn't change the size of the presented view's frame, like, say, changing its background color:

override func containerViewDidLayoutSubviews() {
    super.containerViewDidLayoutSubviews()
    presentedViewController.view.backgroundColor = UIColor.blue
    print("did layout")
}

the method never breaks. For whatever reason, altering the size of the presented view's frame in this method breaks the method after the first toggle of the double-height status bar. I would love an explanation of this behavior. Unlikely a bug, but seems like one.

Regardless, Apple says that if auto layout is correctly used, the programmer need do nothing! So my question is, where or how are we supposed to give the presented view controller's view constraints so that it can adapt to its temporary container? Because, IMO, that is the problem. The presented view controller is owned by the transition's container view, which is a temporary view provided by UIKit that we don't have much dominion over. If we can anchor the presented view to that container, all problems are solved. But I've never seen Apple do this or even talk about this.

Note: Explicitly anchoring the presented view to the container view anywhere--in the present animator controller (before the animation or in its completion handler), in the presentation controller, or in an adaptive delegate method--does not produce consistent results as mentioned earlier.



from When implementing custom view controller presentations, where to apply presented view's constraints?

No comments:

Post a Comment