(Updated on Dec 8, 2017)
First, you know the Safari on iPhone X automatically applies paddings. The contents will be restricted inside the safe area by default.
Fig1. Safari with default paddings – portrait
Fig.2. Safari with default paddings – landscape
From web developer’s perspective, as Apple/ WebKit team mentions, you can exclude the auto-applied paddings to use the whole screen on iPhone X by declaring viewport-fit=cover
within viewport meta element in the HTML.
Fig.3. Safari with viewport-fit=cover – portrait
Fig.4. Safari with viewport-fit=cover – landscape
From app developer’s perspective, WKWebView applies its contentInset
(actually it is a property of webView.scrollView
) as much as the safeAreaInsets
, by default.
Fig.5. WKWebView with default paddings – portrait
Fig.6. WKWebView with default paddings – landscape
When a WKWebView instance loads a webpage which has that declaration, its scroll view (webView.scrollView
) switches the contentInsetAdjustmentBehavior
property from UIScrollViewContentInsetAdjustmentAlways
to UIScrollViewContentInsetAdjustmentNever
. This means that the web view takes amount of the safeAreaInsets
paddings off from the contentInset
, so that your contents go underneath the navigation bar, tool/tab bar, and the notch, at initial state.
Fig.7. WKWebView with viewport-fit=cover – portrait
Fig.8. WKWebView with viewport-fit=cover – landscape
However, this behavior is different from Safari, as Fig.3 and 4. On Safari, the content does not go underneath the navigation bar at initial state, even you are declaring viewport-fit=cover
.
Originally UIView can have its safeAreaInsets
for all four edges, and UIScrollView’s contentInsetAdjustmentBehavior
property also affects four edges. When you have a navigation bar and a tool/tab bar then place a WKWebView
instance as filling the screen with setting the contentInsetAdjustmentBehavior
to UIScrollViewContentInsetAdjustmentNever
, you will see the content goes under the navigation bar and the tool/tab bar like fig.7 and 8. This is what you expect, and this is exactly what happens when you declare viewport-fit=cover
in the HTML with WKWebView.
So why Safari looks different? Is it because Safari’s web view is placed below the navigation bar (so the contentInset.top is always 0)? No, you can tell it is placed filling the whole screen because when you scroll a page, the content goes up underneath the navigation bar, and you can see it through the translucent navigation bar (in portrait).
OK, it seems like – Safari’s web view is placed filling the screen, and it always do have contentInset.top
as much as the navigation bar height, but actually it always has no safeAreaInset.top
which is represented by env(safe-area-inset-top)
, no matter if you declare viewport-fit=cover
or not. In other words, even when you declare viewport-fit=cover
in the HTML, Safari does not take amount of safeAreaInsets.top
off from its contentInset.top
, while it is taking safeAreaInsets
off from other 3 edges (left, right, bottom).
That is why the content does not go under the navigation bar at initial state on Safari even if you declare viewport-fit=cover
.
For example, apple.com home page is declaring viewport-fit=cover
but does not seem to be applying env(safe-area-inset-top)
to its header bar. Safari displays it appropriately. However, on my custom web browser made with WKWebView, the header bar goes under the navigation bar inappropriately and is snapped onto the top edge of the screen (WKWebView). It is because the viewport-fit=cover
declaration makes the web view remove paddings on all four edges. On Safari, the top padding (contentInset.top
) still remains under viewport-fit=cover
condition.
Fig.9. apple.com on Safari – portrait
Fig.10. apple.com on WKWebView – portrait
Therefore, if you implement your custom web browser with WKWebView
, you should do the following things.
- Place a WKWebView instance to fill the screen, spread underneath navigation bar and tool/tab bar.
- Set
webView.scrollView.delegate
, and in itsscrollViewDidChangeAdjustedContentInset
delegate method, checkcontentInsetAdjustmentBehavior
of the scrollView. if it isUIScrollViewContentInsetAdjustmentAlways
(this is the default condition), set thecontentInset
as(0, 0, 0, 0)
. The actualcontentInset
is automatically adjusted tosafeAreaInsets
. If thecontentInsetAdjustmentBehavior
isUIScrollViewContentInsetAdjustmentNever
(this means the webpage hasviewport-fit=cover
declaration), set thecontentInset
as(superView.safeAreaInsets.top, 0, 0, 0)
. The content area of the web view spreads to fill the screen except the top edge, it will not hide the top part of your contents under navigation bar at the initial state. - You should update the
contentInset
values when the device is rotated because the safe area will be changed on rotation. However thesafeAreaInset
is updated after the rotation completes, so that I think it is fine to update yourcontentInset
in the viewController’sviewWillLayoutSubviews
method. - At this point, the content will be displayed appropriately if it is not applied
env(safe-area-inset-top)
. If it is applied, however, the content gets unwanted extra top space becauseenv(safe-area-inset-top)
still returns some value that representssafeAreaInsets.top
even though the content is not overlapped by navigation bar, because WKWebView is placed filling the screen and overlapped by navigation bar. To eliminate the extra space, you need to change the value ofenv(safe-area-inset-top)
by overriding WKWebView’ssafeAreaInsets
getter. On a sub-classed WKWebView, addsafeAreaInsets
getter which returnsUIEdgeInsets
as(0, super.safeAreaInsets.left, super.safeAreaInsets.bottom, super.safeAreaInsets.right)
if itsscrollView.contentInsetAdjustmentBehavior
isUIScrollViewContentInsetAdjustmentNever
. If it isUIScrollViewContentInsetAdjustmentAlways
, return(super.safeAreaInsets.top, super.safeAreaInsets.left, 0, super.safeAreaInsets.right)
. - Modify
webView.scrollView.scrollIndicatorInsets
appropriately for each condition.
With those implementations, the custom web browser behaves like Safari.
Fig.11. Fixed WKWebView – portrait
Fig.12. Fixed WKWebView – landscape
Fig.13. apple.com on Fixed WKWebView
The confusing point for app developers is – WKWebView automatically responds to viewport-fit=cover
declaration and the contentInsetAdjustmentBehavior
property is switched to UIScrollViewContentInsetAdjustmentNever
. This removes auto-applied paddings from all four edges. However, the top edge still needs its padding (contentInset.top
) as much as the amount of safeAreaInsets.top
to display the content avoiding the navigation bar at initial state, even env(safe-area-inset-top)
is and is not applied. So you need to care about it by yourself.
P.S.
Though, WKWebView has a bug on rotation. When it is displaying a web page which has viewport-fit=cover
declaration, the layout is collapsed by rotating device. This is another story.