WKWebView scroll indicators follow-up

This commit is contained in:
Shadowfacts 2022-01-30 21:47:53 -05:00
parent 06c3804796
commit 049185b9eb
2 changed files with 46 additions and 2 deletions

View File

@ -6,6 +6,8 @@ metadata.shortDesc = "3 out of 5 stars, would swizzle again"
metadata.slug = "wkwebview-scroll-indicators" metadata.slug = "wkwebview-scroll-indicators"
``` ```
**Update: Since this post was published, the situation has changed and the workaround presented here is no longer valid. See the [follow-up](/2022/wkwebview-scroll-indicators-again/).**
Here's a stupid bug I ran into recently: if you've got a WKWebView in an iOS app and it shows something that's not a normal webpage[^1], the scroll indicator appearance switching doesn't work. What I mean by that is, while the indicators appear correctly (with a dark color) when the system is in light mode, they do not take on a light color when dark mode is enabled. This renders the scroll indicators invisible against dark backgrounds, which can be annoying if you're using the web view to display potentially lengthy content. Here's a stupid bug I ran into recently: if you've got a WKWebView in an iOS app and it shows something that's not a normal webpage[^1], the scroll indicator appearance switching doesn't work. What I mean by that is, while the indicators appear correctly (with a dark color) when the system is in light mode, they do not take on a light color when dark mode is enabled. This renders the scroll indicators invisible against dark backgrounds, which can be annoying if you're using the web view to display potentially lengthy content.
[^1]: I say this because the only way I've tested it is by generating some HTML and giving it to `loadHTMLString(_:baseURL:)`. It's entirely possible this is not a factor. [^1]: I say this because the only way I've tested it is by generating some HTML and giving it to `loadHTMLString(_:baseURL:)`. It's entirely possible this is not a factor.
@ -74,14 +76,15 @@ private func swizzleWKWebView() {
if let originalIMP = originalIMP { if let originalIMP = originalIMP {
let original = unsafeBitCast(originalIMP, to: (@convention(c) (WKWebView, Selector) -> Void).self) let original = unsafeBitCast(originalIMP, to: (@convention(c) (WKWebView, Selector) -> Void).self)
original(self, selector) original(self, selector)
} else {
os_log(.error, "Missing originalIMP for -[WKWebView _updateScrollViewBackground], did WebKit change?")
} }
self.scrollView.indicatorStyle = .default self.scrollView.indicatorStyle = .default
} as (@convention(block) (WKWebView) -> Void)) } as (@convention(block) (WKWebView) -> Void))
originalIMP = class_replaceMethod(WKWebView.self, selector, imp, "v@:") originalIMP = class_replaceMethod(WKWebView.self, selector, imp, "v@:")
if originalIMP == nil {
os_log(.error, "Missing originalIMP for -[WKWebView _updateScrollViewBackground], did WebKit change?")
}
} }
``` ```

View File

@ -0,0 +1,41 @@
```
metadata.title = "Re-Fixing WKWebView Scroll Indicators"
metadata.tags = ["swift"]
metadata.date = "2022-01-30 21:23:42 -0400"
metadata.shortDesc = "Swizzling wasn't worth it, I should have just waited a few more weeks."
metadata.slug = "wkwebview-scroll-indicators-again"
```
As my luck would have it, just a few weeks after I published my [last post](/2022/wkwebview-scroll-indicators/) on this topic, the iOS 15.4 beta came out which broke that hack and once again made my scroll indicators invisible in dark mode.
<!-- excerpt-end -->
Some time ago, a bug was filed against WebKit because setting `scrollIndicatorStyle` on a web view's scroll view was broken on iOS 15. The fix for this bug landed in iOS 15.4 and it subtly changed the behavior of WKScrollView when it comes to the indicator style.
The bug was fixed by tracking whether the web view client has overriden the scroll indicator style and, if so, blocking the web view from resetting it internally. Unfortunately, it [does this](https://github.com/WebKit/WebKit/blob/1dbd34cf01d8b5aedcb8820b13cb6553ed60e8ed/Source/WebKit/UIProcess/ios/WKScrollView.mm#L247) by checking if the new indicator style is not `.default`. So, even if you set it to `.default` to make it automatically switch based on system appearance, the scroll view will interpret that to mean it the indicator style hasn't been overriden and continue erroneously setting it based on background color (or, in my case, the non-opaqueness of the web view).
The solution is simple, if annoying. You need to check the current user interface style and select the appropriate scroll indicator style yourself.
```swift
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateScrollIndicatorStyle()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateScrollIndicatorStyle()
}
private func updateScrollIndicatorStyle() {
guard #available(iOS 15.4, *) else {
// different workaround pre-iOS 15.4
return
}
if traitCollection.userInterfaceStyle == .dark {
webView.scrollView.indicatorStyle = .white
} else {
webView.scrollView.indicatorStyle = .black
}
}
```
And, if you, like me were previously using the old, swizzling workaround, you need to disable in on iOS 15.4. If the old workaround remains active, studiously setting the indicator style to `.default` whenever WebKit would override it, it would merely undo all of our hard work.