This is the first part of a two-part change that addresses the behavior outlined in https://github.com/material-components/material-components-ios/issues/4104. The second part will expose a similar behavior in the AppBar component.
This PR introduces a new top safe area inset behavior to the flexible header view, gated behind the runtime flag inferTopSafeAreaInsetFromViewController
.
When this behavior is enabled, the flexible header will no longer attempt to infer the top safe area inset from the device's insets using MDCDeviceTopSafeAreaInset
. Instead, it will infer the top safe area inset from the view controller ancestry of the flexible header view controller.
This new behavior addresses a number of issues related to safe area insets, namely:
- https://github.com/material-components/material-components-ios/issues/4104 (incorrect insets when shown in a popover or modal dialog on iPad).
- Flexible headers can now be used in app extensions.
- The flexible header now properly reacts to status bar visibility changes.
New feature: top safe area guide
This change introduces a new layout guide that can be used to position content within the flexible header view while respecting the top safe area insets. The topSafeAreaGuide
API is available on the MDCHeaderView instance of the flexible header view controller and supports iOS 8 and up. An example of its usage to align a view to the bottom of the flexible header's top safe area guide:
[NSLayoutConstraint constraintWithItem:self.titleLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.fhvc.headerView.topSafeAreaGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]
The new layout guide works whether inferTopSafeAreaInsetFromViewController
is enabled or disabled.
Additional information
If using MDCFlexibleHeaderContainerViewController
, this new behavior is now enabled by default.
The new behavior (and all of the other "new" behaviors) have been enabled on every FlexibleHeader example.
Results
A screenshot of the result of this API once enabled on the App Bar:
Architecture
This change builds off of the foundation introduced in 80fd217f766e0f5be6a3bc6bef5923f231721f9d. As part of that change, we made it possible to modify a given view controller's top layout guide such that it equalled the top safe area insets + the flexible header height. Unfortunately, that change was still limited by the fact that the flexible header always assumed that it was placed as a full-screen view.
With the new inferTopSafeAreaInsetFromViewController
behavior, we are able to extract the top safe area inset from our view controller ancestry instead. We can then combine this extracted value with our flexible header's height before ultimately writing that to the target top layout guide. This flow is roughly shown in the following diagram:
In order to implement this flow we needed to implement two things:
- A mechanism by which we could infer the root-most ancestral view controller from which we could extract safe area insets.
- A mechanism by which we could be notified every time that root view controller's safe area insets changed.
The first mechanism is implemented by fhv_inferTopSafeAreaSourceViewController
as a simple ancestral traversal. We walk up the view controller tree from the flexible header's parent until we run out of ancestors. There is some nuance to this algorithm, in that we need to ensure that our ancestor isn't our topLayoutGuideViewController
or we risk creating an infinite loop. If we encounter such a situation we attempt to break the loop by walking up the topLayoutGuideViewController's ancestry instead, but at this point it's very likely we won't have a viable ancestor and we'll throw an assertion if/when we can't find a viable ancester.
With an ancestor in hand, we then determine whether to KVO observe topLayoutGuide
or safeAreaInsets
based on whether we're running pre-iOS 11 or iOS 11+, respectively.
KVO then informs us of any changes to the top safe area insets, which we can then propagate through the flexible header view and eventually on to the top layout guide (if topLayoutGuideAdjustmentEnabled
is also enabled).
cla: yes [FlexibleHeader] app:Catalog