WKWebView 需要在 load() 之后延迟设置其 scrollView 的 contentOffset 和 zoomScale
WKWebView requires delay to set its scrollView's contentOffset and zoomScale after load()
我写了一些代码来保存 WKWebView 的滚动视图的 contentOffset
和 zoomScale
,因此它们可以在 webView 加载后恢复。我发现设置那些 scrollView 属性只能使用延迟(例如 DispatchQueue.main.asyncAfter()
。为什么这是必要的?有没有更好的方法来实现这个?
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var contentOffset = CGPoint.zero
var zoomScale: CGFloat = 1.0
lazy var webView: WKWebView = {
let wv = WKWebView(frame: CGRect.zero)
wv.translatesAutoresizingMaskIntoConstraints = false
wv.allowsBackForwardNavigationGestures = true
wv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
wv.navigationDelegate = self
return wv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reload()
}
@IBAction func refreshTapped(_ sender: UIBarButtonItem) {
reload()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Without some delay, the restoration doesn't happen!
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
self.webView.scrollView.setZoomScale(self.zoomScale, animated: false)
self.webView.scrollView.setContentOffset(self.contentOffset, animated: false)
})
}
private func reload() {
contentOffset = webView.scrollView.contentOffset
zoomScale = webView.scrollView.zoomScale;
webView.load(URLRequest(url: URL(string: "https://www.google.com")!))
}
}
根据 AJ B 建议更新
(抱歉重复)
import UIKit
import WebKit
class ViewController: UIViewController {
private static let keyPath = "webView.scrollView.contentSize"
private static var kvoContext = 0
private var contentOffset: CGPoint?
private var zoomScale: CGFloat?
private var lastContentSize: CGSize?
private var repeatedSizeCount = 0
lazy var webView: WKWebView = {
let wv = WKWebView(frame: CGRect.zero)
wv.translatesAutoresizingMaskIntoConstraints = false
wv.allowsBackForwardNavigationGestures = true
wv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return wv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addObserver(self, forKeyPath: ViewController.keyPath, options: .new, context: &ViewController.kvoContext)
reload()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeObserver(self, forKeyPath: ViewController.keyPath)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let newSize = change?[.newKey] as? CGSize, context == &ViewController.kvoContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
print("Observed: \(NSStringFromCGSize(newSize))")
// Nothing to restore if these are nil
guard let offset = contentOffset, let zoom = zoomScale else { return }
guard let lastSize = lastContentSize, lastSize == newSize else {
print("Waiting for size to settle")
lastContentSize = newSize
return
}
repeatedSizeCount += 1
guard repeatedSizeCount >= 4 else {
print("Size repeated \(repeatedSizeCount) time(s)")
return
}
print("Settled - set saved zoom and offset")
contentOffset = nil
zoomScale = nil
lastContentSize = nil
repeatedSizeCount = 0
webView.scrollView.setZoomScale(zoom, animated: false)
webView.scrollView.setContentOffset(offset, animated: false)
}
@IBAction func refreshTapped(_ sender: UIBarButtonItem) {
contentOffset = webView.scrollView.contentOffset
zoomScale = webView.scrollView.zoomScale;
reload()
}
private func reload() {
print("Reload: \(NSStringFromCGSize(webView.scrollView.contentSize))")
webView.load(URLRequest(url: URL(string: "https://www.google.com")!))
}
}
打印以下内容:
Reload: {781, 1453}
Observed: {781, 1453}
Waiting for size to settle
Observed: {320, 595}
Waiting for size to settle
Observed: {320, 595}
Size repeated 1 time(s)
Observed: {320, 595}
Size repeated 2 time(s)
Observed: {320, 595}
Size repeated 3 time(s)
Observed: {320, 595}
Settled - set saved zoom and offset
Observed: {781, 1453}
Observed: {781, 1453}
Observed: {781, 1453}
我正在做类似的事情,试图在加载后获取内容高度,然后将 webview 的容器调整为该高度。我为此苦苦挣扎了一段时间,我发现最好的方法是观察 WKWebView 的 ScrollView 内容高度,当内容高度以相同的大小重复出现时,您就知道它已完全加载。这有点老套,但对我来说一直有效。我想知道是否有人也知道更好的解决方案。
//This was an example html string that would demonstrate the issue
var html = "<html><body><p>We're no strangers to love You know the rules and so do I A full commitment's what I'm thinking of You wouldn't get this from any other guy I just want to tell you how I'm feeling Gotta make you understand Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you We've known each other for so long Your heart's been aching but you're too shy to say it Inside we both know what's been going on We know the game and we're gonna play it And if you ask me how I'm feeling Don't tell me you're too blind to see Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you We've known each other for so long Your heart's been aching but you're too shy to say it Inside we both know what's been going on We know the game and we're gonna play it I just want to tell you how I'm feeling Gotta make you understand Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you</p><p><img src=\"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg\" width=\"\" height=\"\" style=\"display:block;height:auto;max-width:100%;width:100%;\"></p></body></html>"
我注入了一些 javascript 来发送准备状态和 dom 加载的事件,并打印了当时的大小以及 didFinish 导航功能。这是打印的内容:
// Adjusting the container height in didFinish navigation function
started navigation
committed navigation
content height = 0.0
Javascript: Ready state change interactive | content height = 0.0
Javascript: DOM content loaded | content height = 0.0
Javascript: Ready state change complete | content height = 0.0
ended navigation content height = 0.0 (didFinish navigation)
content size observed = 1638.0
height constraint = Optional(0.0)
content size observed = 691.666666666667
height constraint = Optional(0.0)
content size observed = 1171.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
// Adjusting the container height after content size repeats itself KVO
started navigation
committed navigation
content height = 0.0
Javascript: Ready state change interactive | content height = 0.0
Javascript: DOM content loaded | content height = 0.0
Javascript: Ready state change complete | content height = 0.0
ended navigation content height = 0.0 (didFinish navigation)
content size observed = 1638.0
height constraint = Optional(1.0)
content size observed = 691.666666666667
height constraint = Optional(1.0)
content size observed = 691.666666666667
height constraint = Optional(1.0)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
//default flag is 2
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"scrollView:%.f", scrollView.contentOffset.y);
self.flag--;
if (self.flag == 0) {
[self jump];
}
}
- (void)jump{
if ([self.urlString isEqualToString:self.webView.URL.absoluteString]) {
CGFloat offsetY = 0;
offsetY = [[NSUserDefaults standardUserDefaults] floatForKey:@"history"];
if (offsetY) {
self.markLabel.hidden = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[self.webView.scrollView setContentOffset:CGPointMake(0, offsetY)];
});
}
}
}
希望对您有所帮助。
我在didFinish
上设置了两个参数minimumZoomScale
和setZoomScale
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.scrollView.minimumZoomScale = self.store.zoom
self.scrollView.setZoomScale(self.store.zoom, animated: true)
}
我发现,至少对于内容偏移,我可以通过 JavaScript.
设置它而不会延迟地在 webView(_, didFinish:)
中设置它。
换句话说,替换
self.webView.scrollView.setContentOffset(self.contentOffset, animated: false)
与
self.webView.evaluateJavaScript("window.scrollTo(\(self.contentOffset.minX), \(self.contentOffset.minY));)
我写了一些代码来保存 WKWebView 的滚动视图的 contentOffset
和 zoomScale
,因此它们可以在 webView 加载后恢复。我发现设置那些 scrollView 属性只能使用延迟(例如 DispatchQueue.main.asyncAfter()
。为什么这是必要的?有没有更好的方法来实现这个?
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var contentOffset = CGPoint.zero
var zoomScale: CGFloat = 1.0
lazy var webView: WKWebView = {
let wv = WKWebView(frame: CGRect.zero)
wv.translatesAutoresizingMaskIntoConstraints = false
wv.allowsBackForwardNavigationGestures = true
wv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
wv.navigationDelegate = self
return wv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reload()
}
@IBAction func refreshTapped(_ sender: UIBarButtonItem) {
reload()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Without some delay, the restoration doesn't happen!
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
self.webView.scrollView.setZoomScale(self.zoomScale, animated: false)
self.webView.scrollView.setContentOffset(self.contentOffset, animated: false)
})
}
private func reload() {
contentOffset = webView.scrollView.contentOffset
zoomScale = webView.scrollView.zoomScale;
webView.load(URLRequest(url: URL(string: "https://www.google.com")!))
}
}
根据 AJ B 建议更新 (抱歉重复)
import UIKit
import WebKit
class ViewController: UIViewController {
private static let keyPath = "webView.scrollView.contentSize"
private static var kvoContext = 0
private var contentOffset: CGPoint?
private var zoomScale: CGFloat?
private var lastContentSize: CGSize?
private var repeatedSizeCount = 0
lazy var webView: WKWebView = {
let wv = WKWebView(frame: CGRect.zero)
wv.translatesAutoresizingMaskIntoConstraints = false
wv.allowsBackForwardNavigationGestures = true
wv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return wv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addObserver(self, forKeyPath: ViewController.keyPath, options: .new, context: &ViewController.kvoContext)
reload()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeObserver(self, forKeyPath: ViewController.keyPath)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let newSize = change?[.newKey] as? CGSize, context == &ViewController.kvoContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
print("Observed: \(NSStringFromCGSize(newSize))")
// Nothing to restore if these are nil
guard let offset = contentOffset, let zoom = zoomScale else { return }
guard let lastSize = lastContentSize, lastSize == newSize else {
print("Waiting for size to settle")
lastContentSize = newSize
return
}
repeatedSizeCount += 1
guard repeatedSizeCount >= 4 else {
print("Size repeated \(repeatedSizeCount) time(s)")
return
}
print("Settled - set saved zoom and offset")
contentOffset = nil
zoomScale = nil
lastContentSize = nil
repeatedSizeCount = 0
webView.scrollView.setZoomScale(zoom, animated: false)
webView.scrollView.setContentOffset(offset, animated: false)
}
@IBAction func refreshTapped(_ sender: UIBarButtonItem) {
contentOffset = webView.scrollView.contentOffset
zoomScale = webView.scrollView.zoomScale;
reload()
}
private func reload() {
print("Reload: \(NSStringFromCGSize(webView.scrollView.contentSize))")
webView.load(URLRequest(url: URL(string: "https://www.google.com")!))
}
}
打印以下内容:
Reload: {781, 1453}
Observed: {781, 1453}
Waiting for size to settle
Observed: {320, 595}
Waiting for size to settle
Observed: {320, 595}
Size repeated 1 time(s)
Observed: {320, 595}
Size repeated 2 time(s)
Observed: {320, 595}
Size repeated 3 time(s)
Observed: {320, 595}
Settled - set saved zoom and offset
Observed: {781, 1453}
Observed: {781, 1453}
Observed: {781, 1453}
我正在做类似的事情,试图在加载后获取内容高度,然后将 webview 的容器调整为该高度。我为此苦苦挣扎了一段时间,我发现最好的方法是观察 WKWebView 的 ScrollView 内容高度,当内容高度以相同的大小重复出现时,您就知道它已完全加载。这有点老套,但对我来说一直有效。我想知道是否有人也知道更好的解决方案。
//This was an example html string that would demonstrate the issue
var html = "<html><body><p>We're no strangers to love You know the rules and so do I A full commitment's what I'm thinking of You wouldn't get this from any other guy I just want to tell you how I'm feeling Gotta make you understand Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you We've known each other for so long Your heart's been aching but you're too shy to say it Inside we both know what's been going on We know the game and we're gonna play it And if you ask me how I'm feeling Don't tell me you're too blind to see Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you We've known each other for so long Your heart's been aching but you're too shy to say it Inside we both know what's been going on We know the game and we're gonna play it I just want to tell you how I'm feeling Gotta make you understand Never gonna give you up, never gonna let you down Never gonna run around and desert you Never gonna make you cry, never gonna say goodbye Never gonna tell a lie and hurt you</p><p><img src=\"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg\" width=\"\" height=\"\" style=\"display:block;height:auto;max-width:100%;width:100%;\"></p></body></html>"
我注入了一些 javascript 来发送准备状态和 dom 加载的事件,并打印了当时的大小以及 didFinish 导航功能。这是打印的内容:
// Adjusting the container height in didFinish navigation function
started navigation
committed navigation
content height = 0.0
Javascript: Ready state change interactive | content height = 0.0
Javascript: DOM content loaded | content height = 0.0
Javascript: Ready state change complete | content height = 0.0
ended navigation content height = 0.0 (didFinish navigation)
content size observed = 1638.0
height constraint = Optional(0.0)
content size observed = 691.666666666667
height constraint = Optional(0.0)
content size observed = 1171.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
content size observed = 2772.0
height constraint = Optional(0.0)
// Adjusting the container height after content size repeats itself KVO
started navigation
committed navigation
content height = 0.0
Javascript: Ready state change interactive | content height = 0.0
Javascript: DOM content loaded | content height = 0.0
Javascript: Ready state change complete | content height = 0.0
ended navigation content height = 0.0 (didFinish navigation)
content size observed = 1638.0
height constraint = Optional(1.0)
content size observed = 691.666666666667
height constraint = Optional(1.0)
content size observed = 691.666666666667
height constraint = Optional(1.0)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
content size observed = 691.666666666667
height constraint = Optional(691.666666666667)
//default flag is 2
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"scrollView:%.f", scrollView.contentOffset.y);
self.flag--;
if (self.flag == 0) {
[self jump];
}
}
- (void)jump{
if ([self.urlString isEqualToString:self.webView.URL.absoluteString]) {
CGFloat offsetY = 0;
offsetY = [[NSUserDefaults standardUserDefaults] floatForKey:@"history"];
if (offsetY) {
self.markLabel.hidden = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[self.webView.scrollView setContentOffset:CGPointMake(0, offsetY)];
});
}
}
}
希望对您有所帮助。
我在didFinish
minimumZoomScale
和setZoomScale
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.scrollView.minimumZoomScale = self.store.zoom
self.scrollView.setZoomScale(self.store.zoom, animated: true)
}
我发现,至少对于内容偏移,我可以通过 JavaScript.
设置它而不会延迟地在webView(_, didFinish:)
中设置它。
换句话说,替换
self.webView.scrollView.setContentOffset(self.contentOffset, animated: false)
与
self.webView.evaluateJavaScript("window.scrollTo(\(self.contentOffset.minX), \(self.contentOffset.minY));)