Wie bestimme ich die Inhaltsgröße eines UIWebView?

116

Ich habe einen UIWebViewmit unterschiedlichen (einseitigen) Inhalten. Ich möchte CGSizeden Inhalt herausfinden, um die Größe meiner übergeordneten Ansichten entsprechend zu ändern. Das Offensichtliche gibt -sizeThatFits:leider nur die aktuelle Frame-Größe der WebView zurück.

Ortwin Gentz
quelle
1
Ich bin nicht sicher, aber vielleicht helfen die Antworten auf diese Frage: stackoverflow.com/questions/745160/…
Greg

Antworten:

250

Es stellte sich heraus, dass meine erste Vermutung -sizeThatFits:nicht völlig falsch war. Es scheint zu funktionieren, aber nur, wenn der Frame der WebView vor dem Senden auf eine minimale Größe eingestellt ist -sizeThatFits:. Danach können wir die falsche Rahmengröße durch die Anpassungsgröße korrigieren. Das klingt schrecklich, ist aber eigentlich gar nicht so schlecht. Da wir beide Frame-Änderungen direkt nacheinander vornehmen, wird die Ansicht nicht aktualisiert und flackert nicht.

Natürlich müssen wir warten, bis der Inhalt geladen wurde, also fügen wir den Code in die -webViewDidFinishLoad:Delegate-Methode ein.

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
}

Ich sollte darauf hinweisen, dass es einen anderen Ansatz gibt (danke @GregInYEG), der JavaScript verwendet. Ich bin mir nicht sicher, welche Lösung besser funktioniert.

Von zwei hackigen Lösungen gefällt mir diese besser.

Ortwin Gentz
quelle
Ich habe deinen Ansatz ausprobiert. Das Problem, das ich hatte, ist, dass es kein Scrollen gibt. Irgendwelche Vorschläge?
Testen
1
Ich fand heraus, dass das Problem beim Scrollen aufgrund der geänderten Höhe der Webansicht auftritt. Muss ich die UIView-Größen ändern?
Testen
Ich bin mir nicht sicher, was dein Problem ist. Vielleicht können Sie eine neue Frage mit einem Code oder einer Beschreibung posten?
Ortwin Gentz
@testing, @Bogatyr: Richtig, wenn Ihre UIWebViewAnsicht höher als Ihre übergeordnete Ansicht ist, müssen Sie sie in eine einbetten UIScrollView.
Ortwin Gentz
7
wenn ich webView.scalesPageToFit = YES gebe; dann funktioniert nur diese Lösung. Danke für die Lösung.
SP
92

Ich habe eine andere Lösung, die großartig funktioniert.

Einerseits funktioniert Ortwins Ansatz und Lösung nur mit iOS 6.0 und höher, aber unter iOS 5.0, 5.1 und 5.1.1 nicht richtig, und andererseits gibt es etwas, das ich nicht mag und nicht verstehen kann Bei Ortwins Ansatz wird die Methode [webView sizeThatFits:CGSizeZero]mit dem Parameter verwendet CGSizeZero: Wenn Sie die offizielle Dokumentation von Apple zu diesen Methoden und ihren Parametern lesen, heißt es deutlich:

Die Standardimplementierung dieser Methode gibt den Größenbereich des Begrenzungsrechtecks ​​der Ansicht zurück. Unterklassen können diese Methode überschreiben, um einen benutzerdefinierten Wert basierend auf dem gewünschten Layout aller Unteransichten zurückzugeben. Beispielsweise gibt ein UISwitch-Objekt einen festen Größenwert zurück, der die Standardgröße einer Switch-Ansicht darstellt, und ein UIImageView-Objekt gibt die Größe des aktuell angezeigten Bildes zurück.

Was ich damit meine ist, dass es so ist, als wäre er ohne Logik auf seine Lösung gestoßen, denn beim Lesen der Dokumentation sollte der übergebene Parameter [webView sizeThatFits: ...]zumindest den gewünschten haben width. Bei seiner Lösung wird die gewünschte Breite auf den webViewFrame des Frames gesetzt, bevor sizeThatFitsmit einem CGSizeZeroParameter aufgerufen wird. Ich behaupte also, dass diese Lösung "zufällig" auf iOS 6 funktioniert.

Ich stellte mir einen rationaleren Ansatz vor, der den Vorteil hat, für iOS 5.0 und höher zu arbeiten ... Und auch in komplexen Situationen, in denen mehr als ein WebView (mit seiner Eigenschaft webView.scrollView.scrollEnabled = NOin a eingebettet ist) scrollView.

Hier ist mein Code , um das Layout der zu zwingen , webViewauf den gewünschten widthund erhält die entsprechenden heightSatz zurück in die webViewselbst:

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

Beachten Sie, dass in meinem Beispiel das webViewin eine benutzerdefinierte scrollViewDatei mit anderen eingebettet war webViews... Alle diese webViewshatten ihre webView.scrollView.scrollEnabled = NO, und der letzte Code, den ich hinzufügen musste, war die Berechnung heightder contentSizevon meiner benutzerdefinierten scrollViewEinbettung dieser webViews, aber es war genauso einfach als Summieren meines webView‚s frame.size.heightmit dem Trick berechnet wie oben beschrieben ...

Phänomene
quelle
3
Dies ist viel eleganter als die akzeptierte Lösung und beruht nicht auf undokumentiertem und undefiniertem Verhalten. Es ist viel weniger hackisch als das Einfügen von Javascript. Ich wünschte, ich könnte dies 64 Mal positiv bewerten.
Bugloaf
Genau das habe ich versucht zu erklären. Danke dir. Und es ist ein großer Vorteil, dass es unter allen iOS-Versionen zu funktionieren scheint.
Phänomene
Die Lösung sieht in der Tat gut aus, aber seien Sie vorsichtig, wenn Sie immer noch auf iOS 4.x abzielen. -[UIWebView scrollView]ist nur in iOS 5.0 oder höher verfügbar.
Ortwin Gentz
2
Ich habe dies und eine ähnliche Lösung ausprobiert und es stellte sich heraus, dass dies nur funktioniert, wenn die Webansicht oder die Ansicht, in die sie eingebettet ist, bereits auf dem Bildschirm angezeigt wird. Wenn Sie dies versuchen, bevor Sie die Webansicht zur Ansichtshierarchie hinzufügen, ist das Ergebnis die Höhe der manuellen Größe.
1.
1
Die andere Lösung funktioniert nicht jedes Mal, ich weiß nicht warum, aber das macht den Trick !!! Dies sollte als die richtige Antwort markiert werden.
Vassily
37

Wiederbelebung dieser Frage, weil ich gefunden habe, dass Ortwins Antwort nur die meiste Zeit funktioniert ...

Die webViewDidFinishLoadMethode kann mehrmals aufgerufen werden, und der erste von zurückgegebene Wert sizeThatFitsist nur ein Teil der endgültigen Größe. Dann, aus welchem ​​Grund auch immer, gibt der nächste Aufruf bei sizeThatFitserneutem webViewDidFinishLoadBrennen fälschlicherweise den gleichen Wert zurück wie zuvor! Dies geschieht zufällig für denselben Inhalt, als ob es sich um ein Parallelitätsproblem handelt. Vielleicht hat sich dieses Verhalten im Laufe der Zeit geändert, weil ich für iOS 5 baue und festgestellt habe, dass dies sizeToFitauf die gleiche Weise funktioniert (obwohl dies zuvor nicht der Fall war?).

Ich habe mich für diese einfache Lösung entschieden:

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

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

Update: Ich habe festgestellt, dass dies, wie in den Kommentaren erwähnt, nicht den Fall erfasst, in dem der Inhalt geschrumpft ist. Sie sind sich nicht sicher, ob dies für alle Inhalte und Betriebssystemversionen gilt. Probieren Sie es aus.

Kieran Harper
quelle
Haben Sie Bilder oder andere Elemente in Ihre Webseite aufgenommen? Dies kann zu einer zusätzlichen Entlassung des Delegierten führen. Abgesehen davon funktioniert Ihre Lösung nur, wenn Sie nicht webView.scalesPageToFit = YESwie im obigen Kommentar erwähnt @Sijo angeben.
Ortwin Gentz
Ja, das ist es wahrscheinlich - Ihre Lösung funktioniert in meinen Nur-Text-Fällen einwandfrei. Aber sizeThatFits, die beim letzten Delegatenaufruf nicht den richtigen Wert angeben, ist das Problem ... sehr seltsam. Wie können wir wissen, welcher Aufruf der letzte sein wird, damit wir bis dahin nicht versuchen, sizeThatFits zu verwenden?
Kieran Harper
Wenn Sie beim ersten Delegatenaufruf das richtige Ergebnis von sizeThatFits erhalten, können Sie dann nicht alle folgenden Aufrufe einfach ignorieren?
Ortwin Gentz
Ja, aber es ist umgekehrt :) Der erste Delegatenaufruf kann zu einer falschen Größe führen. ThatFits, dann bleibt dieser Wert erhalten. Es ist, als würde sizeThatFits entweder die Webansicht so ändern, dass weitere sizeThatFits-Aufrufe das gleiche Ergebnis liefern, oder es ist die Einstellung des Frames, während er noch geladen wird.
Kieran Harper
1
Diese Lösung funktioniert auch nicht richtig. Sobald Sie eine größere Seite und dann eine kleinere Seite festgelegt haben, wird immer der größere Wert zurückgegeben. Scheint, als würde es "träge" die internen Ansichten erweitern. Irgendwelche Problemumgehungen dafür?
Legoless
23

In Xcode 8 und iOS 10, um die Höhe einer Webansicht zu bestimmen. Sie können Höhe mit bekommen

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

ODER für Swift

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }
Hardik Thakkar
quelle
1
Versuchen Sie, mithilfe der Größe des Webview-Inhalts zu ermitteln. MyWebView.scrollView.contentSize.height
Hardik Thakkar
8

Eine einfache Lösung wäre, nur zu verwenden, webView.scrollView.contentSizeaber ich weiß nicht, ob dies mit JavaScript funktioniert. Wenn kein JavaScript verwendet wird, funktioniert dies mit Sicherheit:

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

Mit AFAIK können [webView sizeThatFits:CGSizeZero]Sie die Inhaltsgröße ermitteln.

Rengers
quelle
3

Das ist komisch!

Getestet habe ich die Lösungen sowohl sizeThatFits:und [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]sind nicht für mich arbeiten.

Ich fand jedoch einen interessanten einfachen Weg, um die richtige Höhe des Webseiteninhalts zu erhalten. Derzeit habe ich es in meiner Delegatenmethode verwendet scrollViewDidScroll:.

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

Verifiziert in iOS 9.3 Simulator / Gerät, viel Glück!

BEARBEITEN:

Hintergrund: Der HTML-Inhalt wird anhand meiner Zeichenfolgenvariablen und der HTTP-Inhaltsvorlage berechnet, die nach Methoden geladen werden loadHTMLString:baseURL:und keine registrierten JS-Skripte enthalten.

Itachi
quelle
3

Für iOS10, ich war immer 0 (Null) Wert , document.heightso document.body.scrollHeightist die Lösung Höhe von Dokumenten Webview zu bekommen. Das Problem kann auch für behoben werden width.

iAnkit
quelle
2

Ich verwende eine UIWebView, die keine Unteransicht ist (und daher nicht Teil der Fensterhierarchie ist), um die Größe des HTML-Inhalts für zu bestimmen UITableViewCells. Ich habe festgestellt, dass der UIWebViewnicht verbundene Benutzer seine Größe nicht richtig mit meldet -[UIWebView sizeThatFits:]. Wie unter https://stackoverflow.com/a/3937599/9636 erwähnt , müssen Sie außerdem die UIWebViewWerte frame heightauf 1 setzen, um überhaupt die richtige Höhe zu erhalten.

Wenn die UIWebViewHöhe zu groß ist (dh Sie haben sie auf 1000 gesetzt, aber die HTML-Inhaltsgröße beträgt nur 500):

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

Alle geben eine heightvon 1000 zurück.

Um mein Problem in diesem Fall zu lösen, habe ich https://stackoverflow.com/a/11770883/9636 verwendet , das ich pflichtbewusst gewählt habe. Ich verwende diese Lösung jedoch nur, wenn meine UIWebView.frame.widthmit der identisch ist -[UIWebView sizeThatFits:] width.

Heidegrenzen
quelle
1
Ich habe alle Beispiele in diesem Thread ausprobiert, und die Verwendung von "document.height" zum Abrufen der Höhe des Dokuments ist am zuverlässigsten. Zusätzliches HTML kann außerhalb eines Containers (z. B. DIV) hinzugefügt werden, wodurch getElementById (...) in allen von mir durchgeführten Tests unzuverlässig wird. document.height ist ein Genuss.
John Rogers
2

Keiner der Vorschläge hier half mir in meiner Situation, aber ich las etwas, das mir eine Antwort gab. Ich habe einen ViewController mit einem festen Satz von UI-Steuerelementen, gefolgt von einem UIWebView. Ich wollte, dass die gesamte Seite gescrollt wird, als ob die Steuerelemente der Benutzeroberfläche mit dem HTML-Inhalt verbunden wären. Daher deaktiviere ich das Scrollen in der UIWebView und muss dann die Inhaltsgröße einer übergeordneten Bildlaufansicht korrekt festlegen.

Der wichtige Tipp stellte sich heraus, dass UIWebView seine Größe erst dann korrekt meldet, wenn es auf dem Bildschirm gerendert wird. Wenn ich den Inhalt lade, stelle ich die Inhaltsgröße auf die verfügbare Bildschirmhöhe ein. Dann aktualisiere ich in viewDidAppear die Inhaltsgröße der Bildlaufansicht auf den richtigen Wert. Dies hat bei mir funktioniert, weil ich loadHTMLString für lokale Inhalte aufrufe. Wenn Sie loadRequest verwenden, müssen Sie möglicherweise auch die contentSize in webViewDidFinishLoad aktualisieren, je nachdem, wie schnell das HTML abgerufen wird.

Es gibt kein Flackern, da nur der unsichtbare Teil der Bildlaufansicht geändert wird.

Eli Burke
quelle
Ich fürchte, es ist reines Glück, dass die Webansicht so schnell geladen wird, dass sie bei viewDidAppear fertig ist. Dies funktioniert wahrscheinlich nur, wenn die Ansicht animiert angezeigt wird, sodass die Animation lang genug ist, um den Inhalt zu laden. Ich würde mich lieber auf webViewDidFinishLoadden sicheren Ansatz verlassen.
Ortwin Gentz
2

Wenn Ihr HTML enthält schwere HTML-Inhalte wie iframe ist (dh Facebook-, Twitter, instagram-Einbettungen) die wirkliche Lösung ist viel schwieriger, zuerst wickeln Sie Ihre 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>"];

Fügen Sie dann das Handling x-update-webview-height -scheme zu Ihrem shouldStartLoadWithRequest hinzu :

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

Fügen Sie abschließend den folgenden Code in Ihre layoutSubviews ein :

    ...
    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 Sie können Zeitverzögerungen (SetTimeout und setInterval) in Ihrem ObjectiveC / Swift-Code implementieren - es liegt an Ihnen.

PSS Wichtige Informationen zu UIWebView- und Facebook-Einbettungen: Eingebettete Facebook-Beiträge werden in UIWebView nicht richtig angezeigt

MingalevME
quelle
Das ist interessant! Können Sie beschreiben, was der Skriptteil tut und wie er bei Bedarf angepasst werden kann? Es sieht so aus, als würde es regelmäßig die Höhe überprüfen, aber es gibt mehrere Zeitintervalle?
Kieran Harper
@KieranHarper, was genau musst du beschrieben werden?
MingalevME
2

Wenn Sie die Webansicht als Unteransicht irgendwo in der Bildlaufansicht verwenden, können Sie die Höhenbeschränkung auf einen konstanten Wert festlegen und später einen Ausgang daraus erstellen und ihn wie folgt verwenden:

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

Ich bin auch bei diesem Problem festgefahren, dann wurde mir klar, dass ich, wenn ich die dynamische Höhe der WebView berechnen möchte, zuerst die Breite der WebView angeben muss, also füge ich eine Zeile vor js hinzu und es stellt sich heraus, dass ich sie bekommen kann sehr genaue tatsächliche Höhe.

Der Code ist einfach so:

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

    //......
}

quelle
1

Xcode8 swift3.1:

  1. Setzen Sie die Höhe von webView zuerst auf 0 .
  2. Im webViewDidFinishLoadDelegierten:

let height = webView.scrollView.contentSize.height

Ohne web1 gibt Schritt 2, wenn webview.height> actual contentHeight, webview.height zurück, jedoch nicht contentize.height.

hstdt
quelle
Dies ist der Grund, warum ich in meinem Fall nicht den erforderlichen Frame bekommen konnte. Upvoted
NSPratik
0

Fügen Sie dies auch in iOS 7 für die ordnungsgemäße Funktion aller genannten Methoden in Ihre View Controller- viewDidLoadMethode ein:

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

Andernfalls würde keine der Methoden so funktionieren, wie sie sollte.

Nikolay Shubenkov
quelle