support previous objective broken before allowsinlinemediaplayback ios objective-c ios8 wkwebview

previous - wkwebview before ios11



¿Cómo determinar el tamaño del contenido de un WKWebView? (10)

Creo que leí todas las respuestas sobre este tema y todo lo que tenía era parte de la solución. La mayor parte del tiempo pasé tratando de implementar el método KVO como lo describe @davew, que en ocasiones funcionó, pero la mayor parte del tiempo dejó un espacio en blanco debajo del contenido de un contenedor WKWebView. También implementé la sugerencia de @David Beck e hice que la altura del contenedor fuera 0, evitando así la posibilidad de que el problema ocurra si la altura del contenedor es mayor que la del contenido. A pesar de eso tuve ese espacio en blanco ocasional. Entonces, para mí, el observador "contentSize" tuvo muchos defectos. No tengo mucha experiencia con las tecnologías web, por lo que no puedo responder cuál fue el problema con esta solución, pero vi que si solo imprimo altura en la consola pero no hago nada con ella (por ejemplo, redimensionar las restricciones), salta a algún número (por ejemplo, 5000) y luego va al número anterior al más alto (por ejemplo, 2500, que resulta ser el correcto). Si configuro la restricción de altura a la altura que obtengo de "contentSize", se establece en el número más alto que obtiene y nunca se redimensiona al correcto, lo que, de nuevo, lo menciona el comentario de @David Beck.

Después de muchos experimentos, he logrado encontrar una solución que me funciona:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { self.webView.evaluateJavaScript("document.body.offsetHeight", completionHandler: { (height, error) in self.containerHeight.constant = height as! CGFloat }) } }) }

Por supuesto, es importante establecer las restricciones correctamente para que scrollView cambie de tamaño de acuerdo con la restricción containerHeight.

Resulta que el método de navegación Finish nunca se llama cuando quería, pero después de configurar document.readyState step, se llama al siguiente ( document.body.offsetHeight ) en el momento correcto, devolviéndome el número correcto para la altura.

Estoy experimentando con la sustitución de una instancia asignada dinámicamente de UIWebView con una instancia de WKWebView cuando se ejecuta con iOS 8 y más reciente, y no puedo encontrar una manera de determinar el tamaño del contenido de un WKWebView.

Mi vista web está incrustada dentro de un contenedor UIScrollView más grande y, por lo tanto, debo determinar el tamaño ideal para la vista web. Esto me permitirá modificar su marco para mostrar todo su contenido HTML sin la necesidad de desplazarme dentro de la vista web, y podré establecer la altura correcta para el contenedor de vista de desplazamiento (configurando scrollview.contentSize).

He intentado sizeToFit y sizeThatFits sin éxito. Aquí está mi código que crea una instancia de WKWebView y la agrega al contenedor 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];

Aquí hay un método experimental de 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)); }

Y aquí está la salida que se genera:

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}

La llamada sizeToFit no tiene efecto y sizeThatFits siempre devuelve una altura de 1.


He probado la vista de desplazamiento KVO y he intentado evaluar javascript en el documento, usando clientHeight , offsetHeight , etc.

Lo que finalmente me funcionó es: document.body.scrollHeight . O use el scrollHeight de su elemento más superior, por ejemplo, un contenedor div .

Escucho los cambios de propiedad de WKWebview loading usando KVO:

[webview addObserver: self forKeyPath: NSStringFromSelector(@selector(loading)) options: NSKeyValueObservingOptionNew context: nil];

Y entonces:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if(object == self.webview && [keyPath isEqualToString: NSStringFromSelector(@selector(loading))]) { NSNumber *newValue = change[NSKeyValueChangeNewKey]; if(![newValue boolValue]) { [self updateWebviewFrame]; } } }

La implementación de updateWebviewFrame :

[self.webview evaluateJavaScript: @"document.body.scrollHeight" completionHandler: ^(id response, NSError *error) { CGRect frame = self.webview.frame; frame.size.height = [response floatValue]; self.webview.frame = frame; }];


Intenta lo siguiente. Siempre que cree una instancia de su instancia de WKWebView, agregue algo similar a lo siguiente:

//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]];

Luego, agregue un método similar al siguiente al que cada clase obedece el protocolo WKScriptMessageHandler para manejar el mensaje:

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { CGRect frame = message.webView.frame; frame.size.height = [[message.body valueForKey:@"height"] floatValue]; message.webView.frame = frame;}

Esto funciona para mi

Si tiene más de texto en su documento, es posible que deba ajustar el javascript de esta manera para asegurarse de que todo esté cargado:

@"window.onload=function () { window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});};"

NOTA: Esta solución no aborda las actualizaciones continuas del documento.


La mayoría de las respuestas están utilizando "document.body.offsetHeight".

Esto oculta el último objeto del cuerpo.

Superé este problema usando un observador KVO que escucha los cambios en "contentSize" de WKWebview y luego ejecuto este código:

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 } )

No es el código más bonito posible, pero funcionó para mí.


Necesita agregar demora, funciona para mí, en lugar de soluciones con JS arriba:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { print(size: webView.scrollView.contentSize) }) }


Podría usar Key-Value Observing (KVO) ...

En su 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); } }

Esto ahorraría el uso de JavaScript y lo mantendría informado sobre todos los cambios.


Probé la versión de Javascript en UITableViewCell, y funciona perfectamente. Sin embargo, si quieres ponerlo en el scrollView. No sé por qué, la altura puede ser más alta pero no puede ser más corta. Sin embargo, encontré una solución UIWebView aquí. https://.com/a/48887971/5514452

También funciona en WKWebView. Creo que el problema se debe a que WebView necesita un retransmisión, pero de alguna manera no se reducirá y solo se podrá ampliar. Necesitamos restablecer la altura y definitivamente se redimensionará.

Edición: reinicio la altura del marco después de establecer la restricción porque en algún momento no funcionará debido a que la altura del marco se establece en 0.

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.webView.frame.size.height = 0 self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in let webViewHeight = height as! CGFloat self.webViewHeightConstraint.constant = webViewHeight self.webView.frame.size.height = webViewHeight }) } }) }


Tienes que esperar a que la webview termine de cargar. Aquí hay un ejemplo de trabajo que utilicé.

La función de contenido cargado de WKWebView nunca se llama

Luego, una vez que se haya cargado la vista web, puede determinar las alturas que necesita por

func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) { println(webView.scrollView.contentSize.height) }


Tuve que lidiar con este problema yo mismo recientemente. Al final, estaba usando una modificación de la solución propuesta por Chris McClenaghan .

En realidad, su solución original es bastante buena y funciona en los casos más simples. Sin embargo, solo funcionó para mí en páginas con texto. Probablemente también funciona en páginas con imágenes que tienen una altura estática. Sin embargo, definitivamente no funciona cuando tienes imágenes cuyo tamaño está definido con los atributos de max-height max-width .

Y esto se debe a que esos elementos pueden redimensionarse después de que se carga la página. Entonces, en realidad, la altura devuelta en onLoad siempre será correcta. Pero solo será correcto para esa instancia en particular. La solución es controlar el cambio de la altura del body y responder a él.

Monitor de redimensionamiento del document.body Cuerpo

var 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) } } }

Esta solución es, con mucho, la más elegante que pueda encontrar. Hay, sin embargo, dos cosas que debes tener en cuenta.

En primer lugar, antes de cargar su URL, debe establecer shouldListenToResizeNotification en false . Esta lógica adicional es necesaria para los casos en que la URL cargada puede cambiar rápidamente. Cuando esto ocurre, las notificaciones de contenido antiguo por alguna razón pueden superponerse con las del contenido nuevo. Para prevenir tal comportamiento, creé esta variable. Asegura que una vez que comencemos a cargar nuevo contenido ya no procesemos la notificación de la anterior y solo reanudaremos el procesamiento de notificaciones de cambio de tamaño después de que se cargue el nuevo contenido.

Lo más importante, sin embargo, debe ser consciente de esto:

Si adopta esta solución, debe tener en cuenta que si cambia el tamaño de su WKWebView a otro que no sea el tamaño notificado por la notificación, la notificación se activará nuevamente.

Tenga cuidado con esto, ya que es fácil entrar en un bucle infinito. Por ejemplo, si decide manejar la notificación haciendo que su altura sea igual a la altura informada + algún relleno adicional:

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) }

Como puede ver, debido a que estoy agregando 8 a la altura informada, una vez hecho esto, el tamaño de mi body cambiará y la notificación se publicará nuevamente.

Esté alerta a tales situaciones y de lo contrario debería estar bien.

Y, por favor, avíseme si descubre algún problema con esta solución. Confío en mí mismo, por lo que es mejor saber si hay fallas que no he detectado.


Usando la respuesta de @ Andriy y esta respuesta, pude establecer get height of contentSize en WKWebView y cambiar su altura.

Aquí está el código swift 4 completo:

var neededConstraints: [NSLayoutConstraint] = [] @IBOutlet weak var webViewContainer: UIView! @IBOutlet weak var webViewHeight: NSLayoutConstraint! { didSet { if oldValue != nil, oldValue.constant != webViewHeight.constant { view.layoutIfNeeded() } } } lazy var webView: WKWebView = { var source = """ var observeDOM = (function(){ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver, eventListenerSupported = window.addEventListener; return function(obj, callback){ if( MutationObserver ){ // define a new observer var obs = new MutationObserver(function(mutations, observer){ if( mutations[0].addedNodes.length || mutations[0].removedNodes.length ) callback(); }); // have the observer observe foo for changes in children obs.observe( obj, { childList:true, subtree:true }); } else if( eventListenerSupported ){ obj.addEventListener(''DOMNodeInserted'', callback, false); obj.addEventListener(''DOMNodeRemoved'', callback, false); } }; })(); // Observe a specific DOM element: observeDOM( document.body ,function(){ window.webkit.messageHandlers.sizeNotification.postMessage({''scrollHeight'': document.body.scrollHeight,''offsetHeight'':document.body.offsetHeight,''clientHeight'':document.body.clientHeight}); }); """ let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true) let controller = WKUserContentController() controller.addUserScript(script) controller.add(self, name: "sizeNotification") let configuration = WKWebViewConfiguration() configuration.userContentController = controller let this = WKWebView(frame: .zero, configuration: configuration) webViewContainer.addSubview(this) this.translatesAutoresizingMaskIntoConstraints = false this.scrollView.isScrollEnabled = false // constraint for webview when added to it''s superview neededConstraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[web]|", options: [], metrics: nil, views: ["web": this]) neededConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[web]|", options: [], metrics: nil, views: ["web": this]) return this }() override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) _ = webView // to create constraints needed for webView NSLayoutConstraint.activate(neededConstraints) let url = URL(string: "https://www.awwwards.com/")! let request = URLRequest(url: url) webView.load(request) } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if let body = message.body as? Dictionary<String, CGFloat>, let scrollHeight = body["scrollHeight"], let offsetHeight = body["offsetHeight"], let clientHeight = body["clientHeight"] { webViewHeight.constant = scrollHeight print(scrollHeight, offsetHeight, clientHeight) } }