Jak określić rozmiar zawartości UIWebView?

116

Mam UIWebViewinną (pojedynczą stronę) zawartość. Chciałbym poznać CGSizezawartość, aby odpowiednio zmienić rozmiar widoków moich rodziców. Oczywiste -sizeThatFits:niestety po prostu zwraca aktualny rozmiar ramki WebView.

Ortwin Gentz
źródło
1
Nie jestem pewien, ale może odpowiedzi na to pytanie pomogą: stackoverflow.com/questions/745160/ ...
Greg

Odpowiedzi:

250

Okazało się, że moje pierwsze przypuszczenie -sizeThatFits:nie było całkowicie błędne. Wygląda na to, że działa, ale tylko wtedy, gdy ramka webView jest ustawiona na minimalny rozmiar przed wysłaniem -sizeThatFits:. Następnie możemy skorygować zły rozmiar ramy przez rozmiar dopasowania. Brzmi okropnie, ale w rzeczywistości nie jest tak źle. Ponieważ obie zmiany klatek wykonujemy bezpośrednio po sobie, widok nie jest aktualizowany i nie migocze.

Oczywiście musimy poczekać, aż zawartość zostanie załadowana, więc umieszczamy kod w -webViewDidFinishLoad:metodzie delegata.

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
}

Powinienem zwrócić uwagę, że istnieje inne podejście (dzięki @GregInYEG) z wykorzystaniem JavaScript. Nie wiem, które rozwiązanie działa lepiej.

Z dwóch hackerskich rozwiązań bardziej mi się podoba.

Ortwin Gentz
źródło
Próbowałem twojego podejścia. Problem, którego doświadczyłem, polega na tym, że nie ma przewijania. Jakieś sugestie?
testowanie
1
Dowiedziałem się, że problem z przewijaniem jest spowodowany zmienioną wysokością widoku. Czy muszę zmieniać rozmiary UIView?
testowanie
Nie jestem pewien, jaki jest twój problem. Może możesz zadać nowe pytanie z kodem lub opisem?
Ortwin Gentz,
@testing, @Bogatyr: Zgadza się, jeśli Twój widok UIWebViewjest wyższy niż widok rodzica, musisz osadzić go w pliku UIScrollView.
Ortwin Gentz,
7
jeśli daję webView.scalesPageToFit = YES; wtedy tylko to rozwiązanie działa .. dzięki za rozwiązanie ..
SP
92

Mam inne rozwiązanie, które działa świetnie.

Z jednej strony podejście i rozwiązanie Ortwina działa tylko z iOS 6.0 i nowszymi wersjami, ale nie działa poprawnie na iOS 5.0, 5.1 i 5.1.1, az drugiej strony jest coś, czego nie lubię i nie mogę zrozumieć w podejściu Ortwina jest to użycie metody [webView sizeThatFits:CGSizeZero]z parametrem CGSizeZero: Jeśli czytasz oficjalną dokumentację Apple na temat tych metod i ich parametrów, jest to wyraźnie napisane:

Domyślna implementacja tej metody zwraca część rozmiaru prostokąta granic widoku. Podklasy mogą przesłonić tę metodę, aby zwrócić wartość niestandardową na podstawie żądanego układu dowolnych widoków podrzędnych. Na przykład obiekt UISwitch zwraca wartość o stałym rozmiarze, która reprezentuje standardowy rozmiar widoku przełącznika, a obiekt UIImageView zwraca rozmiar aktualnie wyświetlanego obrazu.

Chodzi mi o to, że to tak, jakby natknął się na swoje rozwiązanie bez żadnej logiki, ponieważ czytając dokumentację, przekazany parametr [webView sizeThatFits: ...]powinien mieć przynajmniej pożądane width. W jego rozwiązaniu żądana szerokość jest ustawiana na webViewramkę przed wywołaniem sizeThatFitsz CGSizeZeroparametrem. Utrzymuję więc, że to rozwiązanie działa na iOS 6 przez przypadek.

Wyobraziłem sobie bardziej racjonalne podejście, które ma tę zaletę, że działa na iOS 5.0 i nowszych ... A także w złożonych sytuacjach, w których więcej niż jeden webView (ze swoją właściwością webView.scrollView.scrollEnabled = NOjest osadzony w scrollView.

Oto mój kod, który wymusza układ webViewna pożądany widthi heightprzywraca odpowiedni zestaw do webViewsamego siebie:

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

Należy zauważyć, że w moim przykładzie, webViewzostał osadzony w zwyczaju scrollViewposiadania innych webViews... Wszystkie te webViewsmiały swoje webView.scrollView.scrollEnabled = NO, a ostatni kawałek kodu Miałem dodać było obliczenie z heightz contentSizemojego zwyczaju scrollViewosadzania nich webViews, ale to było tak proste jak zsumowanie mój webView„s frame.size.heightobliczane z trick opisany powyżej ...

Zjawiska
źródło
3
Jest to o wiele bardziej eleganckie niż przyjęte rozwiązanie i nie polega na nieudokumentowanym i nieokreślonym zachowaniu. Jest to znacznie mniej hakerskie niż wstawianie javascript. Chciałbym móc głosować za tym 64 razy.
bugloaf
Dokładnie to próbowałem wyjaśnić. Dziękuję Ci. A wielką zaletą jest to, że wydaje się działać we wszystkich wersjach iOS.
Zjawiska
Rozwiązanie wygląda naprawdę ładnie, ale bądź ostrożny, jeśli nadal celujesz w system iOS 4.x. -[UIWebView scrollView]jest dostępny tylko w iOS 5.0 lub nowszym.
Ortwin Gentz
2
Wypróbowałem to i podobne rozwiązanie i okazało się, że działa to tylko wtedy, gdy widok sieciowy lub widok, w którym jest osadzony, są już na ekranie. Jeśli spróbujesz tego przed dodaniem widoku internetowego do hierarchii widoków, w rezultacie otrzymasz ręczną wysokość rozmiaru.
k1th
1
drugie rozwiązanie nie działa za każdym razem, nie wiem dlaczego, ale to załatwia sprawę !!! należy to oznaczyć jako właściwą odpowiedź.
Wasilij,
37

Wskrzeszenie tego pytania, ponieważ znalazłem odpowiedź Ortwina, która działa tylko przez większość czasu ...

webViewDidFinishLoadMetoda może być wywoływany więcej niż raz, a pierwsza wartość zwracana przez sizeThatFitsto tylko pewna część tego, co ostateczna wielkość powinna być. Wtedy, z jakiegokolwiek powodu, następne wywołanie funkcji, sizeThatFitsgdy webViewDidFinishLoadponownie pożary, zwróci nieprawidłowo tę samą wartość, co wcześniej! Będzie się to zdarzało losowo dla tej samej zawartości, jakby był to jakiś problem z współbieżnością. Może to zachowanie zmieniło się z biegiem czasu, ponieważ buduję na iOS 5 i odkryłem, że sizeToFitdziała w bardzo podobny sposób (chociaż wcześniej tak nie było?)

Zdecydowałem się na to proste rozwiązanie:

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

Aktualizacja: znalazłem, jak wspomniano w komentarzach, nie wydaje się, aby uchwycić przypadek, w którym zawartość się zmniejszyła. Nie jesteś pewien, czy to prawda dla wszystkich treści i wersji systemu operacyjnego, spróbuj.

Kieran Harper
źródło
Czy umieściłeś zdjęcia lub inne zasoby na swojej stronie internetowej? Może to spowodować dodatkowe zwolnienie delegata. Poza tym twoje rozwiązanie działa tylko wtedy, gdy nie dajesz webView.scalesPageToFit = YEStak, jak @Sijo wspomniano w komentarzu powyżej.
Ortwin Gentz
Tak, to prawdopodobnie to - Twoje rozwiązanie działa dobrze w moich przypadkach zawierających tylko tekst. Ale rozmiar, który pasuje, nie podaje właściwej wartości podczas ostatniego wezwania delegata, jest problemem ... bardzo dziwny. Skąd możemy wiedzieć, które wywołanie będzie ostatnie, więc do tego czasu nie będziemy próbować używać sizeThatFits?
Kieran Harper,
Jeśli w pierwszym wywołaniu delegata uzyskasz prawidłowy wynik z funkcji sizeThatFits, czy nie możesz po prostu zignorować kolejnych wywołań?
Ortwin Gentz
Tak, ale jest odwrotnie :) Pierwsze wywołanie delegata może skutkować nieprawidłowym rozmiarem, który pasuje, a następnie ta wartość się zacina. Wygląda na to, że albo sizeThatFits w jakiś sposób modyfikuje widok sieciowy, aby dalsze wywołania sizeThatFits dawały ten sam wynik, albo ustawienie ramki podczas ładowania, które to robi.
Kieran Harper,
1
To rozwiązanie również nie działa poprawnie. Gdy ustawisz większą stronę, a następnie ustawisz mniejszą, zawsze otrzymasz zwróconą większą wartość. Wydaje się, że „leniwie” rozszerza wewnętrzne poglądy. Jakieś obejścia tego problemu?
Bez legalnej
23

W Xcode 8 i iOS 10 do określenia wysokości widoku internetowego. Możesz uzyskać wysokość za pomocą

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

LUB dla Swift

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }
Hardik Thakkar
źródło
1
Spróbuj znaleźć, używając rozmiaru zawartości webview .. myWebView.scrollView.contentSize.height
Hardik Thakkar
8

Prostym rozwiązaniem byłoby po prostu użycie, webView.scrollView.contentSizeale nie wiem, czy to działa z JavaScript. Jeśli nie jest używany JavaScript, to na pewno działa:

- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
    CGSize contentSize = aWebView.scrollView.contentSize;
    NSLog(@"webView contentSize: %@", NSStringFromCGSize(contentSize));
}
OemerA
źródło
3

AFAIK, którego możesz użyć [webView sizeThatFits:CGSizeZero]do określenia rozmiaru zawartości.

Rengers
źródło
3

To dziwne!

Testowałem oba rozwiązania sizeThatFits:i [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]nie pracuje dla mnie.

Jednak znalazłem interesujący, łatwy sposób na uzyskanie odpowiedniej wysokości zawartości strony internetowej. Obecnie użyłem go w mojej metodzie delegata scrollViewDidScroll:.

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

Zweryfikowano w symulatorze / urządzeniu iOS 9.3, powodzenia!

EDYTOWAĆ:

Tło: zawartość html jest obliczana przez moją zmienną łańcuchową i szablon treści HTTP, ładowana metodą loadHTMLString:baseURL:, brak tam zarejestrowanych skryptów JS.

Itachi
źródło
3

W przypadku iOS10 otrzymywałem wartość 0 (zero), document.heightwięc document.body.scrollHeightjest to rozwiązanie, aby uzyskać wysokość dokumentu w Webview. Problem można rozwiązać również dla width.

iAnkit
źródło
2

Używam, UIWebViewktóry nie jest widokiem podrzędnym (a zatem nie jest częścią hierarchii okien), aby określić rozmiary zawartości HTML UITableViewCells. Okazało się, że odłączony UIWebViewnie zgłasza poprawnie swojego rozmiaru z -[UIWebView sizeThatFits:]. Ponadto, jak wspomniano w https://stackoverflow.com/a/3937599/9636 , należy ustawić UIWebView„s frame heightdo 1 w celu uzyskania odpowiedniej wysokości w ogóle.

Jeśli UIWebViewwysokość jest zbyt duża (tj. Ustawiono ją na 1000, ale rozmiar zawartości HTML to tylko 500):

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

Wszystkie zwracają height1000.

Aby rozwiązać mój problem w tym przypadku, użyłem https://stackoverflow.com/a/11770883/9636 , na który sumiennie zagłosowałem. Jednak używam tego rozwiązania tylko wtedy, gdy mój UIWebView.frame.widthjest taki sam jak -[UIWebView sizeThatFits:] width.

Heath Borders
źródło
1
Wypróbowałem wszystkie przykłady w tym wątku i użycie „document.height” do pobrania wysokości dokumentu jest najbardziej wiarygodne. Dodatkowy kod HTML można dodać poza kontenerem (takim jak DIV), co sprawia, że ​​metoda getElementById (...) jest niewiarygodna we wszystkich wykonanych przeze mnie testach. document.height działa wspaniale.
John Rogers,
2

Żadna z propozycji nie pomogła mi w mojej sytuacji, ale przeczytałem coś, co dało mi odpowiedź. Mam ViewController ze stałym zestawem kontrolek interfejsu użytkownika, a następnie UIWebView. Chciałem, aby cała strona przewijała się tak, jakby kontrolki interfejsu użytkownika były połączone z zawartością HTML, więc wyłączam przewijanie w UIWebView, a następnie muszę poprawnie ustawić rozmiar zawartości nadrzędnego widoku przewijania.

Ważną wskazówką okazało się to, że UIWebView nie zgłasza poprawnie swojego rozmiaru, dopóki nie zostanie wyrenderowany na ekranie. Więc kiedy ładuję zawartość, ustawiam rozmiar zawartości na dostępną wysokość ekranu. Następnie w viewDidAppear aktualizuję rozmiar zawartości widoku scrollview do prawidłowej wartości. To zadziałało dla mnie, ponieważ wywołuję loadHTMLString w lokalnej zawartości. Jeśli używasz loadRequest, może być konieczne zaktualizowanie contentSize również w webViewDidFinishLoad, w zależności od szybkości pobierania html.

Nie ma migotania, ponieważ zmienia się tylko niewidoczna część widoku przewijania.

Eli Burke
źródło
Obawiam się, że to szczęście, że widok sieciowy ładuje się na tyle szybko, że kończy się na viewDidAppear. Prawdopodobnie działa to tylko wtedy, gdy widok jest animowany, więc animacja jest wystarczająco długa, aby załadować zawartość. Wolę polegać na webViewDidFinishLoadbezpiecznym podejściu.
Ortwin Gentz
2

Jeśli twój HTML zawiera ciężkie treści HTML, takie jak iframe (tj. Facebook-, Twitter, Instagram-embed), prawdziwe rozwiązanie jest znacznie trudniejsze, najpierw zawiń swój 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>"];

Następnie dodaj obsługę x-update-webview-height -scheme do swojego 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;
    }
    ...

Na koniec dodaj następujący kod w swoim 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 Możesz wdrożyć opóźnienie czasowe (SetTimeout i setInterval) w kodzie ObjectiveC / Swift - to zależy od Ciebie.

PSS Ważne informacje o UIWebView i osadzeniach na Facebooku: Osadzony post na Facebooku nie wyświetla się poprawnie w UIWebView

MingalevME
źródło
To jest interesujące! Czy mógłbyś opisać, co robi część skryptu i jak w razie potrzeby można ją poprawić? Wygląda na to, że będzie okresowo sprawdzać wysokość, ale w grę wchodzi wiele odstępów czasu?
Kieran Harper
@KieranHarper, co dokładnie musisz opisać?
MingalevME
2

Używając widoku internetowego jako widoku podrzędnego gdzieś w widoku przewijania, możesz ustawić ograniczenie wysokości na jakąś stałą wartość, a później zrobić z niego wylot i używać go tak, jak:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    webView.scrollView.scrollEnabled = NO;
    _webViewHeight.constant = webView.scrollView.contentSize.height;
}
younke
źródło
1

Utknąłem też w tym problemie, wtedy zdałem sobie sprawę, że jeśli chcę obliczyć dynamiczną wysokość webView, to najpierw muszę podać szerokość webView, więc dodaję jedną linię przed js i okazuje się, że mogę uzyskać bardzo dokładna rzeczywista wysokość.

Kod jest prosty w następujący sposób:

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

    //......
}

źródło
1

Xcode8 swift3.1:

  1. Najpierw ustaw wysokość webView na 0 .
  2. W webViewDidFinishLoaddelegacie:

let height = webView.scrollView.contentSize.height

Bez kroku 1, jeśli webview.height> current contentHeight, krok 2 zwróci wartość webview.height, ale nie contentize.height.

hstdt
źródło
To jest powód, dlaczego nie mogę uzyskać wymagany ramki w moim przypadku .. upvoted
NSPratik
0

Również w iOS 7 dla poprawnego działania wszystkich wymienionych metod dodaj to w viewDidLoadmetodzie kontrolera widoku :

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

W przeciwnym razie żadna z metod nie działałaby tak, jak powinna.

Nikolay Shubenkov
źródło