Come determinare la dimensione del contenuto di un UIWebView?


116

Ho un contenuto UIWebViewdiverso (singola pagina). Vorrei scoprire CGSizeil contenuto per ridimensionare in modo appropriato le visualizzazioni dei miei genitori. L'ovvio -sizeThatFits:purtroppo restituisce solo la dimensione del frame corrente del webView.


1
Non ne sono sicuro, ma forse le risposte a questa domanda aiuteranno: stackoverflow.com/questions/745160/…
Greg

Risposte:


250

Si è scoperto che la mia prima ipotesi di utilizzo -sizeThatFits:non era completamente sbagliata. Sembra funzionare, ma solo se il frame di webView è impostato su una dimensione minima prima dell'invio -sizeThatFits:. Dopodiché possiamo correggere la dimensione del telaio sbagliata in base alle dimensioni del raccordo. Sembra terribile ma in realtà non è così male. Poiché eseguiamo entrambi i cambi di fotogramma uno dopo l'altro, la vista non viene aggiornata e non lampeggia.

Ovviamente, dobbiamo attendere che il contenuto sia stato caricato, quindi inseriamo il codice nel -webViewDidFinishLoad:metodo delegato.

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
}

Vorrei sottolineare che esiste un altro approccio (grazie @GregInYEG) che utilizza JavaScript. Non sono sicuro di quale soluzione funzioni meglio.

Di due soluzioni hacky mi piace di più questa.


Ho provato il tuo approccio. Il problema che ho riscontrato è che non c'è scorrimento. Eventuali suggerimenti?
test

1
Ho scoperto che il problema con lo scorrimento si verifica a causa della modifica dell'altezza del webview. Devo cambiare le dimensioni di UIView?
test

Non sono sicuro di quale sia il tuo problema. Forse puoi pubblicare una nuova domanda con un codice o una descrizione?
Ortwin Gentz

@testing, @Bogatyr: Giusto, se il tuo UIWebViewè più alto della tua visualizzazione genitore, devi incorporarlo in un file UIScrollView.
Ortwin Gentz

7
se sto dando webView.scalesPageToFit = YES; allora solo questa soluzione funziona..grazie per la soluzione ..
SP

92

Ho un'altra soluzione che funziona alla grande.

Da un lato, l'approccio e la soluzione di Ortwin funziona solo con iOS 6.0 e versioni successive, ma non funziona correttamente su iOS 5.0, 5.1 e 5.1.1, e dall'altro c'è qualcosa che non mi piace e non riesco a capire con l'approccio di Ortwin, è l'uso del metodo [webView sizeThatFits:CGSizeZero]con il parametro CGSizeZero: se leggi la documentazione ufficiale Apple su questo metodo e il suo parametro, dice chiaramente:

L'implementazione predefinita di questo metodo restituisce la parte delle dimensioni del rettangolo dei limiti della vista. Le sottoclassi possono sovrascrivere questo metodo per restituire un valore personalizzato basato sul layout desiderato di tutte le sottovisioni. Ad esempio, un oggetto UISwitch restituisce un valore di dimensione fissa che rappresenta la dimensione standard di una vista switch e un oggetto UIImageView restituisce la dimensione dell'immagine che sta attualmente visualizzando.

Quello che voglio dire è che è come se fosse imbattuto nella sua soluzione senza alcuna logica, perché leggendo la documentazione, il parametro passato a [webView sizeThatFits: ...]dovrebbe almeno avere il desiderato width. Con la sua soluzione, la larghezza desiderata viene impostata sul webViewframe di prima di chiamare sizeThatFitscon un CGSizeZeroparametro. Quindi ritengo che questa soluzione funzioni su iOS 6 "per caso".

Ho immaginato un approccio più razionale, che ha il vantaggio di funzionare per iOS 5.0 e versioni successive ... E anche in situazioni complesse in cui più di una webView (con la sua proprietà webView.scrollView.scrollEnabled = NOè incorporata in un file scrollView.

Ecco il mio codice per forzare il layout del layout webViewdesiderato widthe riportare il heightset corrispondente a webViewse stesso:

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

Si noti che nel mio esempio, il webViewè stato incorporato in un costume scrollViewcon altri webViews... Tutti questi webViewshanno avuto il loro webView.scrollView.scrollEnabled = NO, e l'ultimo pezzo di codice ho dovuto aggiungere è stato il calcolo del heightdel contentSizedel mio personalizzato scrollViewincorporare questi webViews, ma è stato così facile come sommando il mio webView's frame.size.heightcalcolata con il trucco descritto sopra ...


3
Questo è molto più elegante della soluzione accettata e non si basa su comportamenti non documentati e indefiniti. È molto meno hacker rispetto all'inserimento di javascript. Vorrei poter votare 64 volte.
bugloaf

È esattamente quello che ho cercato di spiegare. Grazie. E il grande vantaggio è che sembra funzionare con tutte le versioni di iOS.
Fenomeni

La soluzione sembra davvero carina, ma fai attenzione se stai ancora prendendo di mira iOS 4.x. -[UIWebView scrollView]è disponibile solo in iOS 5.0 o versioni successive.
Ortwin Gentz

2
Ho provato questa e una soluzione simile e si è scoperto che funziona solo se la visualizzazione Web o la visualizzazione in cui è incorporata sono già sullo schermo. Se lo provi prima di aggiungere la visualizzazione Web alla gerarchia della visualizzazione, il risultato sarà l'altezza della dimensione manuale.
k1

1
l'altra soluzione non funziona tutte le volte, non so perché, ma questa su fa il trucco !!! questo dovrebbe essere etichettato come la risposta giusta.
Vassily

37

Resuscitare questa domanda perché ho scoperto che la risposta di Ortwin funziona solo LA MAGGIOR PARTE del tempo ...

Il webViewDidFinishLoadmetodo può essere chiamato più di una volta e il primo valore restituito da sizeThatFitsè solo una parte di quella che dovrebbe essere la dimensione finale. Quindi, per qualsiasi motivo, la chiamata successiva a sizeThatFitsquando si webViewDidFinishLoadattiva di nuovo restituirà erroneamente lo stesso valore che aveva prima! Ciò accadrà in modo casuale per lo stesso contenuto come se fosse una sorta di problema di concorrenza. Forse questo comportamento è cambiato nel tempo, perché sto costruendo per iOS 5 e ho anche scoperto che sizeToFitfunziona più o meno allo stesso modo (anche se in precedenza non lo faceva?)

Ho scelto questa semplice soluzione:

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

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

Aggiornamento: ho scoperto che, come menzionato nei commenti, questo non sembra cogliere il caso in cui il contenuto si è ridotto. Non sono sicuro che sia vero per tutti i contenuti e la versione del sistema operativo, provalo.


Hai incluso immagini o altre risorse nella tua pagina web? Ciò potrebbe causare l'attivazione aggiuntiva del delegato. A parte questo, la tua soluzione funziona solo quando non dai webView.scalesPageToFit = YEScome @Sijo menzionato nel commento sopra.
Ortwin Gentz

Sì, probabilmente è così: la tua soluzione funziona bene nei miei casi di solo testo. Ma sizeThatFits non fornisce il valore corretto durante la chiamata finale del delegato è il problema ... molto strano. Come potremmo sapere quale chiamata sarà l'ultima in modo da non tentare di utilizzare sizeThatFits fino ad allora?
Kieran Harper

Se nella prima chiamata del delegato ottieni il risultato corretto da sizeThatFits, non puoi semplicemente ignorare le chiamate successive?
Ortwin Gentz

Sì, ma è il contrario :) La prima chiamata del delegato può dare come risultato sizeThatFits sbagliato, quindi quel valore rimane valido. È come se sizeThatFits stia in qualche modo modificando la visualizzazione Web in modo che qualsiasi ulteriore chiamata sizeThatFits dia lo stesso risultato, oppure è l'impostazione del frame mentre è ancora in caricamento che lo sta facendo.
Kieran Harper

1
Anche questa soluzione non funziona correttamente. Una volta impostata una pagina più grande e quindi impostata una più piccola, verrà sempre restituito il valore più grande. Sembra che "pigramente" estenda le visuali interne. Qualche soluzione alternativa per questo?
Legoless

23

In Xcode 8 e iOS 10 per determinare l'altezza di una visualizzazione Web. puoi ottenere l'altezza usando

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

O per Swift

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }

1
Prova a trovare utilizzando la dimensione del contenuto della visualizzazione web .. myWebView.scrollView.contentSize.height
Hardik Thakkar

8

Una soluzione semplice sarebbe quella di usarla webView.scrollView.contentSizema non so se funziona con JavaScript. Se non viene utilizzato JavaScript, funziona sicuramente:

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

3

Per quanto ne so, puoi usare [webView sizeThatFits:CGSizeZero]per capire la dimensione del contenuto.


3

È strano!

Ho provato le soluzioni sia sizeThatFits:e [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]stanno NON lavorando per me.

Tuttavia, ho trovato un modo semplice e interessante per ottenere la giusta altezza del contenuto della pagina web. Attualmente, l'ho usato nel mio metodo delegato scrollViewDidScroll:.

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

Verificato in iOS 9.3 Simulatore / Dispositivo, buona fortuna!

MODIFICARE:

Sfondo: il contenuto html viene calcolato dalla mia variabile stringa e dal modello di contenuto HTTP, caricato dal metodo loadHTMLString:baseURL:, non ci sono script JS registrati lì.


3

Per iOS10, ottenevo il valore 0 (zero), document.heightquindi document.body.scrollHeightè la soluzione per ottenere l'altezza del documento in Webview. Il problema può essere risolto anche per width.


2

Sto usando una UIWebViewche non è una sottoview (e quindi non fa parte della gerarchia della finestra) per determinare le dimensioni del contenuto HTML per UITableViewCells. Ho scoperto che il disconnesso UIWebViewnon segnala correttamente le sue dimensioni con -[UIWebView sizeThatFits:]. Inoltre, come menzionato in https://stackoverflow.com/a/3937599/9636 , è necessario impostare l UIWebView's frame heightsu 1 per ottenere l'altezza corretta.

Se l' UIWebViewaltezza di è troppo grande (ovvero è impostata su 1000, ma la dimensione del contenuto HTML è solo 500):

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

Tutti restituiscono un heightdi 1000.

Per risolvere il mio problema in questo caso, ho usato https://stackoverflow.com/a/11770883/9636 , che ho votato a favore. Tuttavia, utilizzo questa soluzione solo quando my UIWebView.frame.widthè uguale a -[UIWebView sizeThatFits:] width.


1
Ho provato tutti gli esempi in questo thread e l'utilizzo di "document.height" per recuperare l'altezza del documento è il più affidabile. HTML aggiuntivo potrebbe essere aggiunto al di fuori di un contenitore (come DIV) che rende getElementById (...) inaffidabile in tutti i test che ho eseguito. document.height funziona a meraviglia.
John Rogers,

2

Nessuno dei suggerimenti qui mi ha aiutato con la mia situazione, ma ho letto qualcosa che mi ha dato una risposta. Ho un ViewController con un set fisso di controlli dell'interfaccia utente seguito da un UIWebView. Volevo che l'intera pagina scorresse come se i controlli dell'interfaccia utente fossero collegati al contenuto HTML, quindi disabilito lo scorrimento su UIWebView e devo quindi impostare correttamente la dimensione del contenuto di una visualizzazione a scorrimento principale.

Il suggerimento importante si è rivelato essere che UIWebView non riporta correttamente le sue dimensioni fino a quando non viene visualizzato sullo schermo. Quindi, quando carico il contenuto, imposto la dimensione del contenuto all'altezza dello schermo disponibile. Quindi, in viewDidAppear aggiorno le dimensioni del contenuto di scrollview al valore corretto. Questo ha funzionato per me perché sto chiamando loadHTMLString sul contenuto locale. Se stai usando loadRequest potresti dover aggiornare il contentSize anche in webViewDidFinishLoad, a seconda della velocità con cui viene recuperato l'html.

Non c'è sfarfallio, perché viene modificata solo la parte invisibile della visualizzazione a scorrimento.


Temo che sia pura fortuna che la visualizzazione Web si carichi abbastanza velocemente da essere terminata in viewDidAppear. Probabilmente funziona solo se la vista appare animata, quindi l'animazione è abbastanza lunga da caricare il contenuto. Preferisco fare affidamento su webViewDidFinishLoadcome approccio sicuro.
Ortwin Gentz

2

Se il tuo HTML contiene contenuti HTML pesanti come iframe (cioè facebook-, twitter, instagram-embeds) la vera soluzione è molto più difficile, prima avvolgi il tuo 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>"];

Quindi aggiungi la gestione x-update-webview-height -scheme nel tuo 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 infine aggiungi il seguente codice all'interno del tuo 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 Puoi implementare il ritardo temporale (SetTimeout e setInterval) all'interno del tuo codice ObjectiveC / Swift - dipende da te.

PSS Informazioni importanti su UIWebView e gli incorporamenti di Facebook: il post di Facebook incorporato non viene visualizzato correttamente in UIWebView


Questo è interessante! Potresti descrivere cosa fa la parte dello script e come può essere ottimizzata se necessario? Sembra che controllerà periodicamente l'altezza, ma sono coinvolti più intervalli di tempo?
Kieran Harper

@KieranHarper, cosa devi descrivere esattamente?
MingalevME

2

Quando si utilizza webview come sottoview da qualche parte in scrollview, è possibile impostare il vincolo di altezza su un valore costante e successivamente estrarlo e usarlo come:

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

1

Sono anche bloccato su questo problema, poi mi sono reso conto che se voglio calcolare l'altezza dinamica del webView, devo prima dire la larghezza del webView, quindi aggiungo una riga prima di js e si scopre che posso ottenere altezza reale molto precisa.

Il codice è semplice come questo:

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

    //......
}

1

Xcode8 swift3.1:

  1. Impostare prima l' altezza di webView su 0 .
  2. In webViewDidFinishLoadDelegato:

let height = webView.scrollView.contentSize.height

Senza il passaggio 1, se webview.height> actual contentHeight, il passaggio 2 restituirà webview.height ma non contentize.height.


Questo è il motivo per cui non sono riuscito a ottenere il frame richiesto nel mio caso .. voto positivo
NSPratik

0

Anche in iOS 7 per il corretto funzionamento di tutti i metodi menzionati, aggiungi questo nel viewDidLoadmetodo del controller di visualizzazione :

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

Altrimenti nessuno dei metodi funzionerebbe come dovrebbe.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.