Як визначити розмір вмісту UIWebView?


116

У мене є UIWebViewрізний (одна сторінка) вміст. Я хотів би дізнатися CGSizeвміст, щоб належним чином змінити перегляд батьків. Очевидний, -sizeThatFits:на жаль, просто повертає поточний розмір кадру webView.


1
Я не впевнений, але, можливо, відповіді на це питання допоможуть: stackoverflow.com/questions/745160/…
Грег,

Відповіді:


250

Виявилося, що моя перша здогадка про використання -sizeThatFits:була не зовсім помилковою. Здається, це працює, але лише якщо рамка веб-перегляду встановлена ​​на мінімальний розмір до надсилання -sizeThatFits:. Після цього ми можемо виправити неправильний розмір кадру за розміром, що підходить. Це звучить жахливо, але насправді це не так вже й погано. Оскільки ми обидва зміни кадру робимо один за одним, подання не оновлюється і не мерехтить.

Звичайно, нам доведеться чекати, поки вміст не буде завантажений, тому ми вводимо код у -webViewDidFinishLoad:метод делегування.

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

Швидкий 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
}

Слід зазначити, що існує інший підхід (спасибі @GregInYEG) із використанням JavaScript. Не впевнений, яке рішення працює краще.

З двох химерних рішень мені це більше подобається.


Я спробував ваш підхід. Проблема, яку я відчув, полягає в тому, що прокрутки немає. Будь-які пропозиції?
тестування

1
Я дізнався, що проблема з прокруткою виникає через змінену висоту веб-перегляду. Чи потрібно змінювати розміри UIView?
тестування

Не впевнений, у чому ваша проблема. Можливо, ви можете залишити нове запитання з якимсь кодом чи описом?
Ортвін Генц

@testing, @Bogatyr: Правильно, якщо ваш UIWebViewпоказник вище, ніж ваш батьківський вигляд, вам потрібно вбудувати його в UIScrollView.
Ортвін Генц

7
якщо я даю webView.scalesPageToFit = ТАК; тоді працює лише це рішення .. дякую за рішення ..
СП

92

У мене є ще одне рішення, яке чудово працює.

З одного боку, підхід та рішення Ortwin працює лише з iOS 6.0 та новішими версіями, але не працює належним чином на iOS 5.0, 5.1 та 5.1.1, а з іншого - це те, що мені не подобається і не можу зрозуміти з підходом Ортвіна, це використання методу [webView sizeThatFits:CGSizeZero]з параметром CGSizeZero: Якщо ви читаєте офіційну документацію Apple про цей метод та його параметр, він чітко говорить:

Реалізація за замовчуванням цього методу повертає частину розміру прямокутника меж перегляду. Підкласи можуть замінити цей метод, щоб повернути спеціальне значення на основі бажаного макета будь-яких підпрезентацій. Наприклад, об'єкт UISwitch повертає значення фіксованого розміру, яке представляє стандартний розмір перегляду комутатора, а об’єкт UIImageView повертає розмір зображення, яке він відображає в даний час.

Я маю на увазі те, що це наче він натрапив на своє рішення без будь-якої логіки, тому що, читаючи документацію, параметр, який передається, [webView sizeThatFits: ...]повинен хоча б мати бажане width. З його розчином бажаної шириною встановлюються на webViewраму «s перед викликом sizeThatFitsз CGSizeZeroпараметром. Тому я вважаю, що це рішення працює на iOS 6 "випадково".

Я уявив більш раціональний підхід, який має перевагу роботи для iOS 5.0 і пізніших версій ... А також у складних ситуаціях, коли більше одного веб-перегляду (з його властивістю webView.scrollView.scrollEnabled = NOвбудовано в a scrollView.

Ось мій код, щоб змусити макет webViewпотрібного widthі повернути відповідний heightнабір до webViewсебе:

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

Швидкий 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;
}

Зверніть увагу , що в моєму прикладі, webViewбув вбудований в звичай , scrollViewщо має інше webViews... Все це webViewsбуло їх webView.scrollView.scrollEnabled = NO, і останній шматок коду , який я повинен був додати був обчислення heightз contentSizeмого звичаю scrollViewвкладення їх webViews, але це було так легко а підсумовуючи мій webView«и frame.size.heightобчислюється за допомогою трюку описано вище ...


3
Це набагато більш елегантно, ніж прийняте рішення, і воно не покладається на недокументовану та невизначену поведінку. Це набагато менше хакі, ніж вставляти JavaScript. Я б хотів, щоб я міг підтвердити це 64 рази.
bugloaf

Саме це я і намагався пояснити. Дякую. І великою перевагою є те, що він, здається, працює під усіма версіями iOS.
Феномени

Рішення виглядає справді приємно, але будьте обережні, якщо ви все ще орієнтуєтесь на iOS 4.x. -[UIWebView scrollView]доступний лише в iOS 5.0 або новішої версії.
Ортвін Генц

2
Я спробував це і подібне рішення, і виявилося, що це працює лише, якщо веб-перегляд або перегляд, в який він вбудований, вже є на екрані. Якщо ви спробуєте це перед тим, як додати веб-перегляд до ієрархії перегляду, результатом буде висота розміру вручну.
k1th

1
інше рішення працює не кожен раз, не знаю чому, але це робить трюк !!! це має бути позначено правильною відповіддю.
Василь

37

Воскрешаючи це запитання, тому що я знайшов відповідь Ортвіна лише на МОСТЬ часу ...

webViewDidFinishLoadМетод може бути викликаний більш ніж один раз, і перше значення , що повертається sizeThatFitsтільки деяка частина того , що кінцевий розмір повинен бути. Тоді з будь-якої причини наступний дзвінок, sizeThatFitsколи webViewDidFinishLoadпожежі знову будуть неправильно повернути те саме значення, що і раніше! Це станеться випадковим чином для того ж вмісту, як якщо б це була якась проблема паралельності. Можливо, ця поведінка з часом змінилася, тому що я будую для iOS 5 і це теж знайшовsizeToFit працює майже так само (хоча раніше цього не було?)

Я зупинився на цьому простому рішенні:

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

Швидкий (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
    }
}

Оновлення: Я виявив, як згадувалося в коментарях, це, схоже, не сприймає випадок, коли вміст зменшився. Не впевнений, чи це правда для всього вмісту та версії ОС, спробуйте.


Ви включили зображення або інші об’єкти на свою веб-сторінку? Це може спричинити додатковий вистріл делегата. Крім того, ваше рішення працює лише тоді, коли ви не даєте, webView.scalesPageToFit = YESяк @Sijo, зазначений у коментарі вище.
Ортвін Генц

Так, це, мабуть, так - ваше рішення чудово працює в моїх текстових випадках. Але проблема sizeThatFits, що не дає правильного значення під час остаточного виклику делегата, є проблемою ... дуже дивно. Як ми можемо знати, який дзвінок буде останнім, щоб ми не намагалися до цього часу використовувати sizeThatFits?
Kieran Harper

Якщо під час першого дзвінка делегата ви отримуєте правильний результат від sizeThatFits, чи не можете ви просто ігнорувати будь-які наступні дзвінки?
Ортвін Генц

Так, але це навпаки :) Перший виклик делегата може призвести до неправильного розміруThatFits, потім це значення дотримується. Це як би або sizeThatFits якимось чином змінює веб-перегляд, щоб будь-які подальші виклики sizeThatFits давали той же результат, або це встановлення кадру, поки він все ще завантажується, що це робить.
Kieran Harper

1
Це рішення також не працює належним чином. Як тільки ви встановите більшу сторінку, а потім встановите меншу, вам завжди буде повернено більше значення. Схоже, що "ліниво" розширює внутрішні погляди. Будь-які обходи для цього?
Legoless

23

У Xcode 8 та iOS 10 для визначення висоти веб-перегляду. Ви можете отримати висоту за допомогою

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

АБО для Swift

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

1
Спробуйте знайти розмір вмісту веб-перегляду .. myWebView.scrollView.contentSize.height
Hardik Thakkar

8

Простим рішенням було б просто користуватися, webView.scrollView.contentSizeале я не знаю, чи це працює з JavaScript. Якщо JavaScript не використовується, це працює точно:

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

3

AFAIK, за допомогою якого можна [webView sizeThatFits:CGSizeZero]визначити розмір вмісту.


3

Це дивно!

Я тестував рішення як sizeThatFits:і [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]є НЕ працюю для мене.

Однак я знайшов цікавий простий спосіб отримати правильну висоту вмісту веб-сторінки. В даний час я використовував його в своєму делегатному методі scrollViewDidScroll:.

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

Перевірено в iOS 9.3 Simulator / Device, удачі!

Редагувати:

Передумови: Вміст html обчислюється за моєю змінною рядка та шаблоном вмісту HTTP, завантаженою методом loadHTMLString:baseURL: , немає зареєстрованих сценаріїв JS.


3

Для iOS10 я отримував значення 0 (нуль), document.heightтак що document.body.scrollHeightце рішення отримати висоту документа в Webview. Питання може бути вирішено також для width.


2

Я використовую UIWebViewте, що не є підглядом (і, отже, не є частиною ієрархії вікон) для визначення розмірів вмісту HTML для UITableViewCells. Я виявив, що відключений UIWebViewне повідомляє належним чином його розмір -[UIWebView sizeThatFits:]. Крім того, як було зазначено в https://stackoverflow.com/a/3937599/9636 , ви повинні встановити значення UIWebView" frame height1", щоб отримати правильну висоту взагалі.

Якщо UIWebViewвисота 'занадто велика (тобто у вас встановлено 1000, але розмір вмісту HTML становить лише 500):

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

Усі повертають height1000.

Щоб вирішити свою проблему в цьому випадку, я використав https://stackoverflow.com/a/11770883/9636 , яку я гідно проголосував. Однак я використовую це рішення лише тоді, коли мій UIWebView.frame.widthтакий самий, як і -[UIWebView sizeThatFits:] width.


1
Я спробував усі приклади в цій темі, і використання "document.height" для отримання висоти документа є найбільш надійним. Додатковий HTML може бути доданий поза контейнером (наприклад, DIV), що робить getElementById (...) ненадійним у всіх тестах, які я виконував. document.height працює частуванням.
Джон Роджерс

2

Жодна із пропозицій тут не допомогла мені зі своєю ситуацією, але я прочитав щось, що дало мені відповідь. У мене є ViewController з фіксованим набором елементів користувальницького інтерфейсу, за яким слід UIWebView. Я хотів, щоб вся сторінка прокручувалася так, ніби елементи керування інтерфейсом були підключені до вмісту HTML, тому я відключаю прокрутку на UIWebView і повинен правильно встановити розмір вмісту батьківського перегляду прокрутки.

Важливим підказом виявилось те, що UIWebView не повідомляє про свій розмір правильно, доки не буде виведено на екран. Тому коли я завантажую вміст, я встановлюю розмір вмісту на доступну висоту екрана. Потім в viewDidAppear я поновлюю розмір вмісту прокрутки до правильного значення. Це працювало для мене, тому що я викликаю loadHTMLString на локальному вмісті. Якщо ви використовуєте loadRequest, можливо, знадобиться оновити contentSize у webViewDidFinishLoad також залежно від швидкості отримання файлу html.

Не буває мерехтіння, оскільки змінюється лише невидима частина перегляду прокрутки.


Я боюся, що це чиста удача, що веб-перегляд завантажується досить швидко, що він закінчений на viewDidAppear. Можливо, це працює лише в тому випадку, якщо представлення виглядає анімованим, тому анімація є достатньо довгою для завантаження вмісту. Я б краще покластися на webViewDidFinishLoadбезпечний підхід.
Ортвін Генц

2

Якщо ваш HTML містить важкий HTML-вміст, наприклад iframe (тобто facebook-, twitter, instagram-embeds), справжнє рішення набагато складніше, спочатку введіть свій 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>"];

Потім додайте керування x-update-webview-height -sheme в свою слідStartLoadWithRequest :

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

І, нарешті, додайте такий код у свій макетSubviews :

    ...
    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 Ви можете впроваджувати затримку часу (SetTimeout та setInterval) всередині вашого ObjectiveC / Swift-коду - саме вам.

PSS Важлива інформація про UIWebView та Facebook Вбудовування : Вбудований пост у Facebook не відображається належним чином у UIWebView


Це цікаво! Чи можете ви описати, що робить сценарій та як її можна змінити за потреби? Схоже, він буде періодично перевіряти висоту, але задіяно кілька часових інтервалів?
Kieran Harper

@KieranHarper, що саме вам потрібно описати?
MingalevME

2

Використовуючи веб-перегляд як підвид, десь у перегляді прокрутки, ви можете встановити обмеження висоти на якесь постійне значення, а пізніше зробити з нього розетку і використовувати її як:

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

1

Я також зациклювався на цій проблемі, тоді я зрозумів, що якщо я хочу обчислити динамічну висоту webView, мені потрібно спочатку сказати ширину webView, тому я додаю один рядок перед js, і виявиться, що я можу отримати дуже точна фактична висота.

Код простий так:

-(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. Встановіть висоту webView спочатку 0 .
  2. У webViewDidFinishLoadДелегат:

let height = webView.scrollView.contentSize.height

Без кроку 1, якщо webview.height> фактичний contentHeight, крок 2 поверне webview.height, але не contentize.height.


Це причина, чому я не міг отримати потрібний кадр у моєму випадку .. аплодовано
NSPratik

0

Також в iOS 7 для належної роботи всіх згаданих методів додайте це у свій viewDidLoadметод контролера перегляду :

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

Інакше жоден із методів не працював би як слід.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.