(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
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
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
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
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
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.
webView.scrollView.delegate, and in its
scrollViewDidChangeAdjustedContentInsetdelegate method, check
contentInsetAdjustmentBehaviorof the scrollView. if it is
UIScrollViewContentInsetAdjustmentAlways(this is the default condition), set the
(0, 0, 0, 0). The actual
contentInsetis automatically adjusted to
safeAreaInsets. If the
UIScrollViewContentInsetAdjustmentNever(this means the webpage has
viewport-fit=coverdeclaration), set the
(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
contentInsetvalues when the device is rotated because the safe area will be changed on rotation. However the
safeAreaInsetis updated after the rotation completes, so that I think it is fine to update your
contentInsetin the viewController’s
- 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 because
env(safe-area-inset-top)still returns some value that represents
safeAreaInsets.topeven 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 of
env(safe-area-inset-top)by overriding WKWebView’s
safeAreaInsetsgetter. On a sub-classed WKWebView, add
safeAreaInsetsgetter which returns
(0, super.safeAreaInsets.left, super.safeAreaInsets.bottom, super.safeAreaInsets.right)if its
UIScrollViewContentInsetAdjustmentNever. If it is
(super.safeAreaInsets.top, super.safeAreaInsets.left, 0, super.safeAreaInsets.right).
webView.scrollView.scrollIndicatorInsetsappropriately 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.
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.