Como determinar o tamanho do conteúdo de um UIWebView?

116

Eu tenho um UIWebViewconteúdo diferente (página única). Eu gostaria de descobrir o CGSizedo conteúdo para redimensionar minhas visualizações pai apropriadamente. O óbvio, -sizeThatFits:infelizmente, apenas retorna o tamanho do quadro atual do webView.

Ortwin Gentz
fonte
1
Não tenho certeza, mas talvez as respostas a esta pergunta ajudem: stackoverflow.com/questions/745160/…
Greg

Respostas:

250

Acontece que meu primeiro palpite usando -sizeThatFits:não estava completamente errado. Parece funcionar, mas apenas se o quadro do webView for definido com um tamanho mínimo antes do envio -sizeThatFits:. Depois disso, podemos corrigir o tamanho do quadro errado pelo tamanho do encaixe. Isso parece terrível, mas na verdade não é tão ruim. Como fazemos as duas mudanças de quadro uma após a outra, a visualização não é atualizada e não pisca.

Claro, temos que esperar até que o conteúdo seja carregado, então colocamos o código no -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
}

Devo salientar que há outra abordagem (obrigado @GregInYEG) usando JavaScript. Não tenho certeza de qual solução tem melhor desempenho.

De duas soluções hacky, gosto mais desta.

Ortwin Gentz
fonte
Tentei sua abordagem. O problema que experimentei é que não há rolagem. Alguma sugestão?
teste de
1
Descobri que o problema com a rolagem ocorre por causa da altura alterada do webview. Tenho que alterar os tamanhos do UIView?
teste de
Não tenho certeza de qual é o seu problema. Talvez você possa postar uma nova pergunta com algum código ou descrição?
Ortwin Gentz
@testing, @Bogatyr: Certo, se sua UIWebViewvisão for superior à de seus pais, você precisa incorporá-la a um UIScrollView.
Ortwin Gentz
7
se estou fornecendo webView.scalesPageToFit = YES; então apenas esta solução funciona .. obrigado pela solução ..
SP
92

Eu tenho outra solução que funciona muito bem.

Por um lado, a abordagem e solução de Ortwin funciona apenas com iOS 6.0 e posterior, mas não funciona corretamente no iOS 5.0, 5.1 e 5.1.1 e, por outro lado, há algo que não gosto e não consigo entender com a abordagem de Ortwin, é o uso do método [webView sizeThatFits:CGSizeZero]com o parâmetro CGSizeZero: Se você ler a documentação oficial da Apple sobre esses métodos e seus parâmetros, diz claramente:

A implementação padrão deste método retorna a parte do tamanho do retângulo de limites da visualização. As subclasses podem substituir esse método para retornar um valor personalizado com base no layout desejado de quaisquer subvisualizações. Por exemplo, um objeto UISwitch retorna um valor de tamanho fixo que representa o tamanho padrão de uma visualização do switch e um objeto UIImageView retorna o tamanho da imagem que está exibindo no momento.

O que quero dizer é que é como se ele encontrasse sua solução sem nenhuma lógica, pois lendo a documentação, o parâmetro passado para [webView sizeThatFits: ...]deve ter pelo menos o desejado width. Com sua solução, a largura desejada é definida para o webViewquadro de antes de chamar sizeThatFitscom um CGSizeZeroparâmetro. Portanto, mantenho esta solução funcionando no iOS 6 por "acaso".

Imaginei uma abordagem mais racional, que tem a vantagem de funcionar para iOS 5.0 e posteriores ... E também em situações complexas onde mais de um webView (com sua propriedade webView.scrollView.scrollEnabled = NOestá embutido em um scrollView.

Aqui está meu código para forçar o Layout do webViewpara o desejado widthe obter o heightconjunto correspondente de volta para o webViewpróprio:

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

Observe que no meu exemplo, o webViewfoi embutido em um custom scrollViewtendo outros webViews... Todos estes webViewstinham seus webView.scrollView.scrollEnabled = NO, e a última parte do código que eu tive que adicionar foi o cálculo do heightde contentSizedo meu custom scrollViewembeding estes webViews, mas foi tão fácil como soma minha webViewé frame.size.heightcalculado com o truque descrito acima ...

Fenômenos
fonte
3
Isso é muito mais elegante do que a solução aceita e não depende de comportamento não documentado e indefinido. É muito menos hackeado do que inserir javascript. Eu gostaria de poder votar a favor 64 vezes.
bugloaf de
Isso é exatamente o que tentei explicar. Obrigado. E sua grande vantagem é que parece funcionar em todas as versões do iOS.
Fenômenos de
A solução parece mesmo boa, mas tome cuidado se você ainda está mirando no iOS 4.x. -[UIWebView scrollView]está disponível apenas no iOS 5.0 ou posterior.
Ortwin Gentz
2
Tentei esta e uma solução semelhante e descobri que só funciona se o webview ou o modo de exibição em que está embutido já estiverem na tela. Se você tentar isso antes de adicionar a visualização da web à hierarquia de visualização, o resultado será a altura do tamanho manual.
k1 de
1
a outra solução não funciona sempre, não sei porque, mas essa aí resolve !!! isso deve ser marcado como a resposta certa.
Vassily
37

Ressuscitando esta pergunta porque descobri que a resposta de Ortwin só funciona na MAIORIA do tempo ...

O webViewDidFinishLoadmétodo pode ser chamado mais de uma vez, e o primeiro valor retornado por sizeThatFitsé apenas uma parte do tamanho final que deve ser. Então, por qualquer motivo, a próxima chamada para sizeThatFitsquando webViewDidFinishLoaddisparar novamente retornará incorretamente o mesmo valor que fazia antes! Isso acontecerá aleatoriamente para o mesmo conteúdo, como se fosse algum tipo de problema de simultaneidade. Talvez esse comportamento tenha mudado com o tempo, porque estou compilando para iOS 5 e também descobri que sizeToFitfunciona da mesma maneira (embora anteriormente não funcionasse?)

Eu escolhi esta solução simples:

- (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;
}

Swift (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
    }
}

Atualização: descobri, conforme mencionado nos comentários, que isso não parece pegar o caso em que o conteúdo foi reduzido. Não tenho certeza se isso é verdade para todo o conteúdo e versão do sistema operacional, experimente.

Kieran Harper
fonte
Você incluiu imagens ou outros ativos em sua página da web? Isso pode causar a demissão adicional do delegado. Tirando isso, sua solução só funciona quando você não dá webView.scalesPageToFit = YEScomo @Sijo mencionado no comentário acima.
Ortwin Gentz
Sim, provavelmente é isso - sua solução funciona bem em meus casos apenas de texto. Mas sizeThatFits não fornecendo o valor correto durante a chamada final do delegado é o problema ... muito estranho. Como podemos saber qual chamada será a última para não tentarmos usar sizeThatFits até então?
Kieran Harper
Se na primeira chamada de delegado você obtiver o resultado correto de sizeThatFits, você não pode simplesmente ignorar as chamadas seguintes?
Ortwin Gentz
Sim, mas é o contrário :) A primeira chamada de delegado pode resultar no sizeThatFits errado, então esse valor permanece. É como se sizeThatFits estivesse de alguma forma modificando a visualização da web para que quaisquer chamadas adicionais de sizeThatFits fornecessem o mesmo resultado, ou seja a configuração do quadro enquanto ele ainda está carregando que está fazendo isso.
Kieran Harper
1
Esta solução também não funciona corretamente. Depois de definir uma página maior e, em seguida, definir uma página menor, você sempre receberá o valor maior de volta. Parece que "preguiçosamente" estende as visões internas. Alguma solução alternativa para isso?
Legoless
23

No Xcode 8 e iOS 10 para determinar a altura de uma visualização da web. você pode obter altura usando

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

OU para Swift

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }
Hardik Thakkar
fonte
1
Tente encontrar usando o tamanho do conteúdo do webview .. myWebView.scrollView.contentSize.height
Thakkar
8

Uma solução simples seria apenas usar, webView.scrollView.contentSizemas não sei se isso funciona com JavaScript. Se não houver JavaScript usado, isso funciona com certeza:

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

AFAIK você pode usar [webView sizeThatFits:CGSizeZero]para descobrir o tamanho do conteúdo.

Rengers
fonte
3

Isso é estranho!

Eu testei as soluções sizeThatFits:e NÃO[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] estão funcionando para mim.

No entanto, descobri uma maneira fácil e interessante de obter a altura certa do conteúdo da página da web. Atualmente, eu usei no meu método delegado scrollViewDidScroll:.

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

Verificado no simulador / dispositivo iOS 9.3, boa sorte!

EDITAR:

Background: O conteúdo html é calculado pela minha variável de string e modelo de conteúdo HTTP, carregado pelo método loadHTMLString:baseURL:, sem scripts JS registrados lá.

Itachi
fonte
3

Para iOS10, eu estava obtendo o valor 0 (zero) de document.heightentão document.body.scrollHeighté a solução para obter a altura do documento no Webview. O problema também pode ser resolvido para width.

iAnkit
fonte
2

Estou usando um UIWebViewque não é uma subvisualização (e, portanto, não faz parte da hierarquia da janela) para determinar os tamanhos do conteúdo HTML UITableViewCells. Descobri que o desconectado UIWebViewnão informa seu tamanho corretamente com -[UIWebView sizeThatFits:]. Além disso, conforme mencionado em https://stackoverflow.com/a/3937599/9636 , você deve definir o UIWebView's frame heightcomo 1 para obter a altura adequada.

Se a UIWebViewaltura de for muito grande (ou seja, você configurou para 1000, mas o tamanho do conteúdo HTML é de apenas 500):

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

Todos retornam height1000.

Para resolver meu problema neste caso, usei https://stackoverflow.com/a/11770883/9636 , que votei obedientemente. No entanto, só uso essa solução quando meu UIWebView.frame.widthé igual ao -[UIWebView sizeThatFits:] width.

Heath Borders
fonte
1
Eu tentei todos os exemplos neste tópico, e usar "document.height" para recuperar a altura do documento é o mais confiável. HTML adicional pode ser adicionado fora de um contêiner (como DIV), o que torna getElementById (...) não confiável em todos os testes que realizei. document.height funciona muito bem.
John Rogers
2

Nenhuma das sugestões aqui me ajudou com minha situação, mas li algo que me deu uma resposta. Eu tenho um ViewController com um conjunto fixo de controles de IU seguido por um UIWebView. Eu queria que a página inteira rolasse como se os controles da IU estivessem conectados ao conteúdo HTML, então eu desabilito a rolagem no UIWebView e devo então definir o tamanho do conteúdo de uma exibição de rolagem pai corretamente.

A dica importante acabou sendo que o UIWebView não informa seu tamanho corretamente até que seja processado na tela. Portanto, quando carrego o conteúdo, defino o tamanho do conteúdo para a altura de tela disponível. Então, em viewDidAppear eu atualizo o tamanho do conteúdo do scrollview para o valor correto. Isso funcionou para mim porque estou chamando loadHTMLString no conteúdo local. Se você estiver usando loadRequest, pode ser necessário atualizar o contentSize em webViewDidFinishLoad também, dependendo da rapidez com que o html é recuperado.

Não há oscilação, porque apenas a parte invisível da visualização de rolagem é alterada.

Eli Burke
fonte
Temo que seja pura sorte que a visualização da web carregue rápido o suficiente para ser concluída em viewDidAppear. Provavelmente, isso só funcionará se a visualização parecer animada, de forma que a animação seja longa o suficiente para carregar o conteúdo. Prefiro contar com webViewDidFinishLoada abordagem segura.
Ortwin Gentz
2

Se o seu HTML contiver conteúdo HTML pesado como o de iframe (ou seja, facebook, twitter, instagram-embols), a solução real é muito mais difícil, primeiro envolva seu 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>"];

Em seguida, adicione manipulação x-update-webview-height -scheme em seu 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;
    }
    ...

E, finalmente, adicione o seguinte código dentro de seu 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.
    ...

PS Você pode implementar o retardo de tempo (SetTimeout e setInterval) dentro do seu código ObjectiveC / Swift - você decide.

PSS Informações importantes sobre UIWebView e incorporações do Facebook: a postagem incorporada do Facebook não é exibida corretamente em UIWebView

MingalevME
fonte
Isto é interessante! Você poderia descrever o que a parte do script faz e como pode ser ajustada, se necessário? Parece que ele verificará a altura periodicamente, mas há vários intervalos de tempo envolvidos?
Kieran Harper
@KieranHarper, o que exatamente você precisa ser descrito?
MingalevME
2

Ao usar o webview como uma subvisualização em algum lugar em scrollview, você pode definir a restrição de altura para algum valor constante e, posteriormente, fazer uma saída a partir dele e usá-lo como:

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

Eu também estou preso neste problema, então percebi que se eu quiser calcular a altura dinâmica do webView, preciso informar a largura do webView primeiro, então adiciono uma linha antes de js e descubro que posso obter altura real muito precisa.

O código é simples assim:

-(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;

    //......
}

fonte
1

Xcode8 swift3.1:

  1. Defina a altura do webView como 0 primeiro.
  2. No webViewDidFinishLoadDelegado:

let height = webView.scrollView.contentSize.height

Sem a etapa 1, se webview.height> real contentHeight, a etapa 2 retornará webview.height, mas não contentsize.height.

hstdt
fonte
Este é o motivo pelo qual eu não consegui obter o frame necessário no meu caso. Upvoted
NSPratik
0

Também no iOS 7, para o funcionamento adequado de todos os métodos mencionados, adicione isso ao viewDidLoadmétodo do controlador de visualização :

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

Caso contrário, nenhum dos métodos funcionaria como deveria.

Nikolay Shubenkov
fonte