Mar*_*ith 49 objective-c ios ios8 wkwebview
我正在尝试在iOS 8及更高版本下运行时用WKWebView实例替换动态分配的UIWebView实例,我找不到确定WKWebView内容大小的方法.
我的Web视图嵌入在更大的UIScrollView容器中,因此我需要确定Web视图的理想大小.这将允许我修改其框架以显示其所有HTML内容,而无需在Web视图中滚动,并且我将能够为滚动视图容器设置正确的高度(通过设置scrollview.contentSize).
我尝试过sizeToFit和sizeThatFits但没有成功.这是我的代码,它创建一个WKWebView实例并将其添加到容器scrollview:
// self.view is a UIScrollView sized to something like 320.0 x 400.0.
CGRect wvFrame = CGRectMake(0, 0, self.view.frame.size.width, 100.0);
self.mWebView = [[[WKWebView alloc] initWithFrame:wvFrame] autorelease];
self.mWebView.navigationDelegate = self;
self.mWebView.scrollView.bounces = NO;
self.mWebView.scrollView.scrollEnabled = NO;
NSString *s = ... // Load s from a Core Data field.
[self.mWebView loadHTMLString:s baseURL:nil];
[self.view addSubview:self.mWebView];
Run Code Online (Sandbox Code Playgroud)
这是一个实验性的didFinishNavigation方法:
- (void)webView:(WKWebView *)aWebView
didFinishNavigation:(WKNavigation *)aNavigation
{
CGRect wvFrame = aWebView.frame;
NSLog(@"original wvFrame: %@\n", NSStringFromCGRect(wvFrame));
[aWebView sizeToFit];
NSLog(@"wvFrame after sizeToFit: %@\n", NSStringFromCGRect(wvFrame));
wvFrame.size.height = 1.0;
aWebView.frame = wvFrame;
CGSize sz = [aWebView sizeThatFits:CGSizeZero];
NSLog(@"sizeThatFits A: %@\n", NSStringFromCGSize(sz));
sz = CGSizeMake(wvFrame.size.width, 0.0);
sz = [aWebView sizeThatFits:sz];
NSLog(@"sizeThatFits B: %@\n", NSStringFromCGSize(sz));
}
Run Code Online (Sandbox Code Playgroud)
这是生成的输出:
2014-12-16 17:29:38.055 App[...] original wvFrame: {{0, 0}, {320, 100}}
2014-12-16 17:29:38.055 App[...] wvFrame after sizeToFit: {{0, 0}, {320, 100}}
2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits A: {320, 1}
2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits B: {320, 1}
Run Code Online (Sandbox Code Playgroud)
sizeToFit调用无效,sizeThatFits始终返回高度1.
小智 75
我想我已经阅读了关于这个主题的每一个答案,我所拥有的只是解决方案的一部分.大部分时间我都在尝试实现@davew所描述的KVO方法,偶尔会有效,但大部分时间都在WKWebView容器的内容下留下了空白区域.我还实现了@David Beck建议,并将容器高度设为0,从而避免了容器高度大于内容容量时出现问题的可能性.尽管如此,我偶尔会有空白.所以,对我来说,"contentSize"观察者有很多缺陷.我没有很多网络技术的经验,所以我无法回答这个解决方案的问题,但我看到如果我只在控制台中打印高度但不对它做任何事情(例如调整约束),它会跳到某个数字(例如5000),然后转到最高数字之前的数字(例如2500 - 结果是正确的数字).如果我将高度约束设置为我从"contentSize"获得的高度,它将自己设置为它获得的最高数字,并且永远不会调整大小到正确的 - 这也是@David Beck评论所提到的.
经过大量实验,我找到了一个适合我的解决方案:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
self.containerHeight.constant = height as! CGFloat
})
}
})
}
Run Code Online (Sandbox Code Playgroud)
当然,正确设置约束以使scrollView根据containerHeight约束调整大小非常重要.
事实证明didFinish导航方法在我想要的时候永远不会被调用,但是设置了document.readyState步骤,下一个(document.body.offsetHeight)会在正确的时刻被调用,返回正确的高度数字.
dav*_*vew 21
您可以使用键值观察(KVO)......
在您的ViewController中:
- (void)viewDidLoad {
...
[self.webView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)dealloc
{
[self.webView.scrollView removeObserver:self forKeyPath:@"contentSize" context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (object == self.webView.scrollView && [keyPath isEqual:@"contentSize"]) {
// we are here because the contentSize of the WebView's scrollview changed.
UIScrollView *scrollView = self.webView.scrollView;
NSLog(@"New contentSize: %f x %f", scrollView.contentSize.width, scrollView.contentSize.height);
}
}
Run Code Online (Sandbox Code Playgroud)
这样可以节省JavaScript的使用,让您在所有更改中保持循环.
And*_*huk 13
我最近不得不自己处理这个问题.最后,我正在使用Chris McClenaghan提出的解决方案的修改.
实际上,他最初的解决方案非常好,它适用于大多数简单的情况.但是,它只适用于带有文本的页面.它可能也适用于具有静态高度的图像的页面.但是,当您拥有使用max-height和max-width属性定义大小的图像时,它肯定不起作用.
这是因为在加载页面后可以调整这些元素的大小.所以,实际上,返回的高度onLoad总是正确的.但它只对那个特定的实例是正确的.解决方法是监控body高度的变化并对其进行响应.
document.bodyvar shouldListenToResizeNotification = false
lazy var webView:WKWebView = {
//Javascript string
let source = "window.onload=function () {window.webkit.messageHandlers.sizeNotification.postMessage({justLoaded:true,height: document.body.scrollHeight});};"
let source2 = "document.body.addEventListener( 'resize', incrementCounter); function incrementCounter() {window.webkit.messageHandlers.sizeNotification.postMessage({height: document.body.scrollHeight});};"
//UserScript object
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let script2 = WKUserScript(source: source2, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
//Content Controller object
let controller = WKUserContentController()
//Add script to controller
controller.addUserScript(script)
controller.addUserScript(script2)
//Add message handler reference
controller.add(self, name: "sizeNotification")
//Create configuration
let configuration = WKWebViewConfiguration()
configuration.userContentController = controller
return WKWebView(frame: CGRect.zero, configuration: configuration)
}()
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let responseDict = message.body as? [String:Any],
let height = responseDict["height"] as? Float else {return}
if self.webViewHeightConstraint.constant != CGFloat(height) {
if let _ = responseDict["justLoaded"] {
print("just loaded")
shouldListenToResizeNotification = true
self.webViewHeightConstraint.constant = CGFloat(height)
}
else if shouldListenToResizeNotification {
print("height is \(height)")
self.webViewHeightConstraint.constant = CGFloat(height)
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个解决方案是迄今为止我能想到的最优雅的解决方案.但是,你应该注意两件事.
首先,在加载您的URL之前,您应该设置shouldListenToResizeNotification为false.当加载的URL可以快速改变时,需要这种额外的逻辑.发生这种情况时,由于某种原因来自旧内容的通知可能与来自新内容的通知重叠.为了防止这种行为,我创建了这个变量.它确保一旦我们开始加载新内容,我们就不再处理来自旧内容的通知,并且我们仅在加载新内容后继续处理调整大小通知.
但最重要的是,您需要了解这一点:
如果您采用此解决方案,则需要考虑如果将您的大小更改WKWebView为通知报告的大小以外的任何内容 - 将再次触发通知.
小心这一点,因为很容易进入无限循环.例如,如果您决定通过使高度等于报告的高度+一些额外的填充来处理通知:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let responseDict = message.body as? [String:Float],
let height = responseDict["height"] else {return}
self.webViewHeightConstraint.constant = CGFloat(height+8)
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,因为我在报告的高度上添加了8,在完成此操作后,我的大小body将会更改,并且通知将再次发布.
警惕这种情况,否则你应该没事.
如果您发现此解决方案有任何问题,请告诉我 - 我自己也依赖它,所以最好知道是否有一些我没有发现的故障!
luh*_*iya 11
为我工作
extension TransactionDetailViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.webviewHeightConstraint.constant = webView.scrollView.contentSize.height
}
}
}
Run Code Online (Sandbox Code Playgroud)
You can also got content height of WKWebView by evaluateJavaScript.
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[webView evaluateJavaScript:@"Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight)"
completionHandler:^(id _Nullable result, NSError * _Nullable error) {
if (!error) {
CGFloat height = [result floatValue];
// do with the height
}
}];
}
Run Code Online (Sandbox Code Playgroud)
小智 6
您需要等待webview完成加载.这是我使用的一个工作示例
然后在webview完成加载后,您就可以确定所需的高度
func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) {
println(webView.scrollView.contentSize.height)
}
Run Code Online (Sandbox Code Playgroud)
大多数答案都使用“document.body.offsetHeight”。
这隐藏了身体的最后一个对象。
我通过使用 KVO 观察器侦听 WKWebview“contentSize”中的更改,然后运行以下代码克服了这个问题:
self.webView.evaluateJavaScript(
"(function() {var i = 1, result = 0; while(true){result =
document.body.children[document.body.children.length - i].offsetTop +
document.body.children[document.body.children.length - i].offsetHeight;
if (result > 0) return result; i++}})()",
completionHandler: { (height, error) in
let height = height as! CGFloat
self.webViewHeightConstraint.constant = height
}
)
Run Code Online (Sandbox Code Playgroud)
这不是最漂亮的代码,但它对我有用。
请尝试以下方法.无论您在何处实例化WKWebView实例,都要添加类似于以下内容的内容:
//Javascript string
NSString * source = @"window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});";
//UserScript object
WKUserScript * script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
//Content Controller object
WKUserContentController * controller = [[WKUserContentController alloc] init];
//Add script to controller
[controller addUserScript:script];
//Add message handler reference
[controller addScriptMessageHandler:self name:@"sizeNotification"];
//Create configuration
WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc] init];
//Add controller to configuration
configuration.userContentController = controller;
//Use whatever you require for WKWebView frame
CGRect frame = CGRectMake(...?);
//Create your WKWebView instance with the configuration
WKWebView * webView = [[WKWebView alloc] initWithFrame:frame configuration:configuration];
//Assign delegate if necessary
webView.navigationDelegate = self;
//Load html
[webView loadHTMLString:@"some html ..." baseURL:[[NSBundle mainBundle] bundleURL]];
Run Code Online (Sandbox Code Playgroud)
然后添加一个类似于以下类的方法,该类遵循WKScriptMessageHandler协议来处理消息:
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
CGRect frame = message.webView.frame;
frame.size.height = [[message.body valueForKey:@"height"] floatValue];
message.webView.frame = frame;}
Run Code Online (Sandbox Code Playgroud)
这适合我.
如果文档中包含的文本不仅仅是文本,则可能需要像这样包装javascript以确保加载所有内容:
@"window.onload=function () { window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});};"
Run Code Online (Sandbox Code Playgroud)
注意:此解决方案不涉及对文档的持续更新.
小智 5
我发现 hlung here 的答案,如下扩展 WKWebView 对我来说是最简单和最有效的解决方案:
https://gist.github.com/pkuecuekyan/f70096218a6b969e0249427a7d324f91
他的评论如下:
“很好!对我来说,我没有设置 webView.frame,而是设置了 autolayout internalContentSize。”
他的代码如下:
import UIKit
import WebKit
class ArticleWebView: WKWebView {
init(frame: CGRect) {
let configuration = WKWebViewConfiguration()
super.init(frame: frame, configuration: configuration)
self.navigationDelegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return self.scrollView.contentSize
}
}
extension ArticleWebView: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.readyState", completionHandler: { (_, _) in
webView.invalidateIntrinsicContentSize()
})
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
44643 次 |
| 最近记录: |