¿Cómo determinar el tamaño del contenido de un UIWebView?

116

Tengo un contenido UIWebViewdiferente (de una sola página). Me gustaría averiguar CGSizeel contenido para cambiar el tamaño de las vistas de mis padres de manera adecuada. -sizeThatFits:Desafortunadamente, lo obvio solo devuelve el tamaño de fotograma actual de webView.

Ortwin Gentz
fuente
1
No estoy seguro, pero quizás las respuestas a esta pregunta ayuden: stackoverflow.com/questions/745160/…
Greg

Respuestas:

250

Resultó que mi primera conjetura al utilizar -sizeThatFits:no estaba del todo mal. Parece funcionar, pero solo si el marco de webView se establece en un tamaño mínimo antes del envío -sizeThatFits:. Después de eso, podemos corregir el tamaño de marco incorrecto según el tamaño de ajuste. Esto suena terrible, pero en realidad no es tan malo. Como hacemos ambos cambios de fotogramas uno tras otro, la vista no se actualiza y no parpadea.

Por supuesto, tenemos que esperar hasta que se haya cargado el contenido, por lo que colocamos el código en el -webViewDidFinishLoad:método delegado.

Obj-C

- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
    CGRect frame = aWebView.frame;
    frame.size.height = 1;
    aWebView.frame = frame;
    CGSize fittingSize = [aWebView sizeThatFits:CGSizeZero];
    frame.size = fittingSize;
    aWebView.frame = frame;

    NSLog(@"size: %f, %f", fittingSize.width, fittingSize.height);
}

Swift 4.x

func webViewDidFinishLoad(_ webView: UIWebView) {
    var frame = webView.frame
    frame.size.height = 1
    webView.frame = frame
    let fittingSize = webView.sizeThatFits(CGSize.init(width: 0, height: 0))
    frame.size = fittingSize
    webView.frame = frame
}

Debo señalar que hay otro enfoque (gracias @GregInYEG) usando JavaScript. No estoy seguro de qué solución funciona mejor.

De dos soluciones hacky, me gusta más esta.

Ortwin Gentz
fuente
Probé tu enfoque. El problema que experimenté es que no hay desplazamiento. ¿Alguna sugerencia?
prueba el
1
Descubrí que el problema con el desplazamiento se debe a la altura modificada de la vista web. ¿Tengo que cambiar los tamaños de UIView?
prueba el
No estoy seguro de cuál es tu problema. ¿Quizás puedas publicar una nueva pregunta con algún código o descripción?
Ortwin Gentz
@testing, @Bogatyr: Correcto, si su vista UIWebViewes más alta que la vista principal, debe insertarla en un archivo UIScrollView.
Ortwin Gentz
7
si estoy dando webView.scalesPageToFit = YES; entonces solo esta solución funciona ... gracias por la solución ..
SP
92

Tengo otra solución que funciona muy bien.

Por un lado, el enfoque y la solución de Ortwin solo funciona con iOS 6.0 y posterior, pero no funciona correctamente en iOS 5.0, 5.1 y 5.1.1, y por otro lado hay algo que no me gusta y no puedo entender. con el enfoque de Ortwin, es el uso del método [webView sizeThatFits:CGSizeZero]con el parámetro CGSizeZero: Si lee la documentación oficial de Apple sobre estos métodos y su parámetro, dice claramente:

La implementación predeterminada de este método devuelve la porción de tamaño del rectángulo de límites de la vista. Las subclases pueden anular este método para devolver un valor personalizado basado en el diseño deseado de cualquier subvista. Por ejemplo, un objeto UISwitch devuelve un valor de tamaño fijo que representa el tamaño estándar de una vista de conmutador y un objeto UIImageView devuelve el tamaño de la imagen que se muestra actualmente.

Lo que quiero decir es que es como si se hubiera encontrado con su solución sin ninguna lógica, porque al leer la documentación, el parámetro pasado [webView sizeThatFits: ...]debería tener al menos el deseado width. Con su solución, el ancho deseado se establece en el webViewmarco de antes de llamar sizeThatFitscon unCGSizeZero parámetro. Así que mantengo que esta solución funciona en iOS 6 por "casualidad".

Imaginé un enfoque más racional, que tiene la ventaja de funcionar para iOS 5.0 y posterior ... Y también en situaciones complejas donde más de un webView (con su propiedad webView.scrollView.scrollEnabled = NOestá incrustado en un scrollView.

Aquí está mi código para forzar el diseño del webView la deseada widthy obtener el heightconjunto correspondiente de nuevo a webViewsí mismo:

Obj-C

- (void)webViewDidFinishLoad:(UIWebView *)aWebView
{   
    aWebView.scrollView.scrollEnabled = NO;    // Property available in iOS 5.0 and later 
    CGRect frame = aWebView.frame;

    frame.size.width = 200;       // Your desired width here.
    frame.size.height = 1;        // Set the height to a small one.

    aWebView.frame = frame;       // Set webView's Frame, forcing the Layout of its embedded scrollView with current Frame's constraints (Width set above).

    frame.size.height = aWebView.scrollView.contentSize.height;  // Get the corresponding height from the webView's embedded scrollView.

    aWebView.frame = frame;       // Set the scrollView contentHeight back to the frame itself.
}

Swift 4.x

func webViewDidFinishLoad(_ aWebView: UIWebView) {

    aWebView.scrollView.isScrollEnabled = false
    var frame = aWebView.frame

    frame.size.width = 200
    frame.size.height = 1

    aWebView.frame = frame
    frame.size.height = aWebView.scrollView.contentSize.height

    aWebView.frame = frame;
}

Tenga en cuenta que en mi ejemplo, el webViewfue incorporado en una costumbre scrollViewque tiene otro webViews... Todos estos webViewstuvieron su webView.scrollView.scrollEnabled = NO, y el último trozo de código que tenía que añadir es el cálculo de la heightde la contentSizede mi costumbre scrollViewincrustación de éstos webViews, pero fue tan fácil como la suma de mi webView's frame.size.heightcalculada con el truco descrito anteriormente ...

Fenómenos
fuente
3
Esto es mucho más elegante que la solución aceptada y no se basa en un comportamiento indocumentado e indefinido. Es mucho menos hackear que insertar javascript. Ojalá pudiera votar esto 64 veces.
bugloaf
Eso es exactamente lo que intenté explicar. Gracias. Y su gran ventaja es que parece funcionar en todas las versiones de iOS.
Fenómenos
La solución se ve realmente bien, pero tenga cuidado si todavía está apuntando a iOS 4.x. -[UIWebView scrollView]está disponible solo en iOS 5.0 o posterior.
Ortwin Gentz
2
Probé esto y una solución similar y resultó que esto solo funciona, si la vista web o la vista en la que está incrustada, ya están en la pantalla. Si intenta esto antes de agregar la vista web a la jerarquía de vistas, el resultado será la altura del tamaño manual.
1 de
1
la otra solución no funciona todas las veces, no sé por qué, ¡pero esto funciona! esto debe etiquetarse como la respuesta correcta.
Vassily
37

Resucitando esta pregunta porque encontré que la respuesta de Ortwin solo funciona la MAYORÍA del tiempo ...

El webViewDidFinishLoadmétodo se puede llamar más de una vez, y el primer valor devuelto por sizeThatFitses solo una parte del tamaño final que debería ser. Entonces, por cualquier motivo, la próxima llamada a sizeThatFitswhen webViewDidFinishLoadfires again devolverá incorrectamente el mismo valor que antes. Esto sucederá aleatoriamente para el mismo contenido como si fuera algún tipo de problema de concurrencia. Quizás este comportamiento haya cambiado con el tiempo, porque estoy compilando para iOS 5 y también he descubierto quesizeToFit funciona de la misma manera (¿aunque anteriormente esto no funcionaba?)

Me he decidido por esta sencilla solución:

- (void)webViewDidFinishLoad:(UIWebView *)aWebView
{        
    CGFloat height = [[aWebView stringByEvaluatingJavaScriptFromString:@"document.height"] floatValue];
    CGFloat width = [[aWebView stringByEvaluatingJavaScriptFromString:@"document.width"] floatValue];
    CGRect frame = aWebView.frame;
    frame.size.height = height;
    frame.size.width = width;
    aWebView.frame = frame;
}

Rápido (2.2):

func webViewDidFinishLoad(webView: UIWebView) {

    if let heightString = webView.stringByEvaluatingJavaScriptFromString("document.height"),
        widthString = webView.stringByEvaluatingJavaScriptFromString("document.width"),
        height = Float(heightString),
        width = Float(widthString) {

        var rect = webView.frame
        rect.size.height = CGFloat(height)
        rect.size.width = CGFloat(width)
        webView.frame = rect
    }
}

Actualización: He descubierto que, como se menciona en los comentarios, esto no parece detectar el caso en el que el contenido se ha reducido. No estoy seguro si es cierto para todo el contenido y la versión del sistema operativo, pruébalo.

Kieran Harper
fuente
¿Ha incluido imágenes u otros activos en su página web? Eso podría provocar el despido adicional del delegado. Aparte de eso, su solución solo funciona cuando no da webView.scalesPageToFit = YEScomo @Sijo mencionado en el comentario anterior.
Ortwin Gentz
Sí, probablemente sea eso: su solución funciona bien en mis casos de solo texto. Pero sizeThatFits no da el valor correcto durante la llamada final del delegado es el problema ... muy extraño. ¿Cómo podemos saber qué llamada será la última para no intentar usar sizeThatFits hasta entonces?
Kieran Harper
Si en la primera llamada de delegado obtiene el resultado correcto de sizeThatFits, ¿no puede simplemente ignorar las siguientes llamadas?
Ortwin Gentz
Sí, pero es al revés :) La primera llamada de delegado puede resultar en un tamaño incorrecto que se ajusta, luego ese valor se mantiene. Es como si sizeThatFits estuviera modificando de alguna manera la vista web para que cualquier otra llamada a sizeThatFits diera el mismo resultado, o si lo que está haciendo es la configuración del marco mientras aún se está cargando.
Kieran Harper
1
Esta solución tampoco funciona correctamente. Una vez que configure una página más grande y luego configure una más pequeña, siempre se le devolverá el valor más grande. Parece que extiende "perezosamente" las vistas internas. ¿Alguna solución para esto?
Legoless
23

En Xcode 8 y iOS 10 para determinar la altura de una vista web. puedes obtener altura usando

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    CGFloat height = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] floatValue];
    NSLog(@"Webview height is:: %f", height);
}

O para Swift

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }
Hardik Thakkar
fuente
1
Intente encontrar usando el tamaño del contenido de la vista web .. myWebView.scrollView.contentSize.height
Hardik Thakkar
8

Una solución simple sería simplemente usar, webView.scrollView.contentSizepero no sé si esto funciona con JavaScript. Si no se utiliza JavaScript, esto funciona con seguridad:

- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
    CGSize contentSize = aWebView.scrollView.contentSize;
    NSLog(@"webView contentSize: %@", NSStringFromCGSize(contentSize));
}
OemerA
fuente
3

AFAIK puede usar [webView sizeThatFits:CGSizeZero]para averiguar el tamaño del contenido.

Rengers
fuente
3

¡Esto es raro!

He probado las soluciones tanto sizeThatFits:y [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]están NO trabajando para mí.

Sin embargo, encontré una manera fácil e interesante de obtener la altura correcta del contenido de la página web. Actualmente, lo usé en mi método delegado scrollViewDidScroll:.

CGFloat contentHeight = scrollView.contentSize.height - CGRectGetHeight(scrollView.frame);

Verificado en el Simulador / Dispositivo iOS 9.3, ¡buena suerte!

EDITAR:

Antecedentes: el contenido html se calcula mediante mi variable de cadena y la plantilla de contenido HTTP, cargada por método loadHTMLString:baseURL:, no hay scripts JS registrados allí.

Itachi
fuente
3

Para iOS10, obtenía el valor 0 (cero), document.heightasí que document.body.scrollHeightes la solución para obtener la altura del documento en Webview. El problema también se puede resolver para width.

iAnkit
fuente
2

Estoy usando una UIWebViewque no es una subvista (y, por lo tanto, no forma parte de la jerarquía de ventanas) para determinar los tamaños del contenido HTML UITableViewCells. Descubrí que el desconectado UIWebViewno informa su tamaño correctamente con -[UIWebView sizeThatFits:]. Además, como se menciona en https://stackoverflow.com/a/3937599/9636 , debe establecer el UIWebView's frame heighten 1 para obtener la altura adecuada.

Si la UIWebViewaltura del 'es demasiado grande (es decir, lo tiene configurado en 1000, pero el tamaño del contenido HTML es solo 500):

UIWebView.scrollView.contentSize.height
-[UIWebView stringByEvaluatingJavaScriptFromString:@"document.height"]
-[UIWebView sizeThatFits:]

Todos devuelven un valor heightde 1000.

Para resolver mi problema en este caso, utilicé https://stackoverflow.com/a/11770883/9636 , que debidamente voté a favor. Sin embargo, solo uso esta solución cuando mi UIWebView.frame.widthes el mismo que el -[UIWebView sizeThatFits:] width.

Heath Borders
fuente
1
Probé todos los ejemplos en este hilo, y usar "document.height" para recuperar la altura del documento es el más confiable. Se podría agregar HTML adicional fuera de un contenedor (como DIV), lo que hace que getElementById (...) no sea confiable en todas las pruebas que he realizado. document.height funciona de maravilla.
John Rogers
2

Ninguna de las sugerencias aquí me ayudó con mi situación, pero leí algo que me dio una respuesta. Tengo un ViewController con un conjunto fijo de controles de IU seguido de un UIWebView. Quería que toda la página se desplazara como si los controles de la interfaz de usuario estuvieran conectados al contenido HTML, por lo que desactivo el desplazamiento en UIWebView y luego debo configurar correctamente el tamaño del contenido de una vista de desplazamiento principal.

El consejo importante resultó ser que UIWebView no informa su tamaño correctamente hasta que se muestra en la pantalla. Entonces, cuando cargo el contenido, configuro el tamaño del contenido a la altura de pantalla disponible. Luego, en viewDidAppear actualizo el tamaño del contenido de scrollview al valor correcto. Esto funcionó para mí porque estoy llamando a loadHTMLString en contenido local. Si está utilizando loadRequest, es posible que también deba actualizar contentSize en webViewDidFinishLoad, dependiendo de la rapidez con que se recupere el html.

No hay parpadeo, porque solo se cambia la parte invisible de la vista de desplazamiento.

Eli Burke
fuente
Me temo que es pura suerte que la vista web se cargue lo suficientemente rápido como para terminar en viewDidAppear. Probablemente esto solo funcione si la vista aparece animada, por lo que la animación es lo suficientemente larga como para cargar el contenido. Prefiero confiar en webViewDidFinishLoadel enfoque seguro.
Ortwin Gentz
2

Si su HTML contiene contenido HTML pesado como iframe (es decir, Facebook, Twitter, Instagram), la solución real es mucho más difícil, primero envuelva su HTML:

[htmlContent appendFormat:@"<html>", [[LocalizationStore instance] currentTextDir], [[LocalizationStore instance] currentLang]];
[htmlContent appendFormat:@"<head>"];
[htmlContent appendString:@"<script type=\"text/javascript\">"];
[htmlContent appendFormat:@"    var lastHeight = 0;"];
[htmlContent appendFormat:@"    function updateHeight() { var h = document.getElementById('content').offsetHeight; if (lastHeight != h) { lastHeight = h; window.location.href = \"x-update-webview-height://\" + h } }"];
[htmlContent appendFormat:@"    window.onload = function() {"];
[htmlContent appendFormat:@"        setTimeout(updateHeight, 1000);"];
[htmlContent appendFormat:@"        setTimeout(updateHeight, 3000);"];
[htmlContent appendFormat:@"        if (window.intervalId) { clearInterval(window.intervalId); }"];
[htmlContent appendFormat:@"        window.intervalId = setInterval(updateHeight, 5000);"];
[htmlContent appendFormat:@"        setTimeout(function(){ clearInterval(window.intervalId); window.intervalId = null; }, 30000);"];
[htmlContent appendFormat:@"    };"];
[htmlContent appendFormat:@"</script>"];
[htmlContent appendFormat:@"..."]; // Rest of your HTML <head>-section
[htmlContent appendFormat:@"</head>"];
[htmlContent appendFormat:@"<body>"];
[htmlContent appendFormat:@"<div id=\"content\">"]; // !important https://stackoverflow.com/a/8031442/1046909
[htmlContent appendFormat:@"..."]; // Your HTML-content
[htmlContent appendFormat:@"</div>"]; // </div id="content">
[htmlContent appendFormat:@"</body>"];
[htmlContent appendFormat:@"</html>"];

Luego agregue el manejo x-update-webview-height -scheme en su shouldStartLoadWithRequest :

    if (navigationType == UIWebViewNavigationTypeLinkClicked || navigationType == UIWebViewNavigationTypeOther) {
    // Handling Custom URL Scheme
    if([[[request URL] scheme] isEqualToString:@"x-update-webview-height"]) {
        NSInteger currentWebViewHeight = [[[request URL] host] intValue];
        if (_lastWebViewHeight != currentWebViewHeight) {
            _lastWebViewHeight = currentWebViewHeight; // class property
            _realWebViewHeight = currentWebViewHeight; // class property
            [self layoutSubviews];
        }
        return NO;
    }
    ...

Y finalmente agregue el siguiente código dentro de su layoutSubviews :

    ...
    NSInteger webViewHeight = 0;

    if (_realWebViewHeight > 0) {
        webViewHeight = _realWebViewHeight;
        _realWebViewHeight = 0;
    } else {
        webViewHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"content\").offsetHeight;"] integerValue];
    }

    upateWebViewHeightTheWayYorLike(webViewHeight);// Now your have real WebViewHeight so you can update your webview height you like.
    ...

PD: Puede implementar el retardo de tiempo (SetTimeout y setInterval) dentro de su código ObjectiveC / Swift, depende de usted.

PSS Información importante sobre UIWebView y Facebook Embeds: la publicación de Facebook incrustada no se muestra correctamente en UIWebView

MingalevME
fuente
¡Esto es interesante! ¿Podría describir qué hace la parte del guión y cómo se puede modificar si es necesario? Parece que comprobará periódicamente la altura, pero ¿hay varios intervalos de tiempo involucrados?
Kieran Harper
@KieranHarper, ¿qué es exactamente lo que necesitas que te describan?
MingalevME
2

Al usar la vista web como una subvista en algún lugar de la vista de desplazamiento, puede establecer la restricción de altura en un valor constante y luego hacer una salida y usarla como:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    webView.scrollView.scrollEnabled = NO;
    _webViewHeight.constant = webView.scrollView.contentSize.height;
}
younke
fuente
1

También estoy atascado en este problema, luego me di cuenta de que si quiero calcular la altura dinámica de webView, primero necesito indicar el ancho de webView, así que agrego una línea antes de js y resulta que puedo obtener altura real muy precisa.

El código es tan simple como este:

-(void)webViewDidFinishLoad:(UIWebView *)webView
{

    //tell the width first
    webView.width = [UIScreen mainScreen].bounds.size.width;

    //use js to get height dynamically
    CGFloat scrollSizeHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] floatValue];
    webView.height = scrollSizeHeight;

    webView.x = 0;
    webView.y = 0;

    //......
}

fuente
1

Xcode8 swift3.1:

  1. Establecer la altura de webView en 0 .
  2. En webViewDidFinishLoadDelegado:

let height = webView.scrollView.contentSize.height

Sin el paso 1, si webview.height> real contentHeight, el paso 2 devolverá webview.height pero no contentsize.height.

hstdt
fuente
Esta es la razón por la que no pude obtener el marco requerido en mi caso .. upvoted
NSPratik
0

También en iOS 7 para el correcto funcionamiento de todos los métodos mencionados, agregue esto en su viewDidLoadmétodo de controlador de vista :

if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) {
    self.automaticallyAdjustsScrollViewInsets = NO;
}

De lo contrario, ninguno de los métodos funcionaría como debería.

Nikolay Shubenkov
fuente