Как определить размер содержимого UIWebView?

116

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

Ортвин Генц
источник
1
Я не уверен, но, возможно, ответы на этот вопрос помогут: stackoverflow.com/questions/745160/…
Грег,

Ответы:

250

Оказалось, что мое первое предположение об использовании -sizeThatFits:не было полностью ошибочным. Кажется, это работает, но только в том случае, если перед отправкой фрейм webView установлен на минимальный размер -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);
}

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
}

Я должен указать на другой подход (спасибо @GregInYEG) с использованием JavaScript. Не уверен, какое решение работает лучше.

Из двух хакерских решений мне больше нравится это.

Ортвин Генц
источник
Я попробовал ваш подход. Проблема, с которой я столкнулся, заключается в том, что нет прокрутки. Какие-либо предложения?
тестирование
1
Я обнаружил, что проблема с прокруткой возникает из-за измененной высоты веб-просмотра. Нужно ли мне менять размеры UIView?
тестирование
Не уверен, в чем ваша проблема. Возможно, вы сможете опубликовать новый вопрос с каким-нибудь кодом или описанием?
Ортвин Генц,
@testing, @Bogatyr: Верно, если ваше UIWebViewпредставление выше родительского, вам нужно встроить его в файл UIScrollView.
Ортвин Генц
7
если я даю webView.scalesPageToFit = YES; тогда работает только это решение .. спасибо за решение ..
SP
92

У меня есть другое решение, которое отлично работает.

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

Реализация по умолчанию этого метода возвращает часть размера прямоугольника границ представления. Подклассы могут переопределить этот метод, чтобы возвращать настраиваемое значение на основе желаемого макета любых вложенных представлений. Например, объект UISwitch возвращает значение фиксированного размера, которое представляет стандартный размер представления переключателя, а объект UIImageView возвращает размер изображения, которое он отображает в данный момент.

Я имею в виду, что он как будто наткнулся на свое решение без какой-либо логики, потому что, читая документацию, переданный параметр [webView sizeThatFits: ...]должен, по крайней мере, иметь желаемое width. В его решении желаемая ширина устанавливается для webViewрамки перед вызовом sizeThatFitsс CGSizeZeroпараметром. Я утверждаю, что это решение работает на iOS 6 «случайно».

Я придумал более рациональный подход, преимущество которого заключается в работе с iOS 5.0 и более поздних версий ... А также в сложных ситуациях, когда более одного webView (его свойство webView.scrollView.scrollEnabled = NOвстроено в файл 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.
}

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

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

явления
источник
3
Это гораздо более элегантно, чем принятое решение, и оно не полагается на недокументированное и неопределенное поведение. Это гораздо менее хитроумно, чем вставка javascript. Хотел бы я проголосовать за это 64 раза.
bugloaf
Это именно то, что я пытался объяснить. Спасибо. И его большим преимуществом является то, что он работает со всеми версиями iOS.
Phenomen
Решение действительно выглядит неплохо, но будьте осторожны, если вы по-прежнему ориентируетесь на iOS 4.x. -[UIWebView scrollView]доступно только в iOS 5.0 или новее.
Ортвин Генц
2
Я пробовал это и подобное решение, и оказалось, что это работает, только если веб-просмотр или представление, в которое оно встроено, уже отображаются на экране. Если вы попробуете это перед добавлением веб-представления в иерархию представлений, результатом будет высота размера вручную.
k1,
1
другое решение не работает каждый раз, не знаю почему, но это помогает !!! это должно быть отмечено как правильный ответ.
Василий
37

Воскрешая этот вопрос, потому что я обнаружил, что ответ Ортвина работает только в большинстве случаев ...

webViewDidFinishLoadМетод может быть вызван более чем один раз, и первое значение , возвращаемое sizeThatFitsтолько некоторая часть того , что конечный размер должен быть. Тогда по какой-то причине следующий вызов sizeThatFitswhen 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;
}

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

Обновление: я обнаружил, что, как упоминалось в комментариях, это не похоже на случай, когда контент сократился. Не уверен, что это верно для всего контента и версии ОС, попробуйте.

Киран Харпер
источник
Включили ли вы изображения или другие ресурсы на свою веб-страницу? Это может вызвать дополнительное увольнение делегата. Кроме того, ваше решение работает только тогда, когда вы не webView.scalesPageToFit = YESуказываете как @Sijo, упомянутый в комментарии выше.
Ортвин Генц
Да, возможно, ваше решение отлично работает в моих текстовых случаях. Но sizeThatFits не дает правильного значения во время последнего вызова делегата - это проблема ... очень странная. Как мы можем узнать, какой вызов будет последним, чтобы до тех пор не пытаться использовать sizeThatFits?
Киран Харпер
Если при первом вызове делегата вы получаете правильный результат от sizeThatFits, не можете ли вы просто игнорировать любые последующие вызовы?
Ортвин Генц,
Да, но это наоборот :) Первый вызов делегата может привести к неправильному sizeThatFits, тогда это значение останется. Как будто либо sizeThatFits каким-то образом изменяет веб-представление, чтобы любые дальнейшие вызовы sizeThatFits давали тот же результат, либо это происходит при настройке фрейма, пока он все еще загружается.
Киран Харпер
1
Это решение тоже работает некорректно. После того, как вы установите большую страницу, а затем уменьшите ее, вам всегда будет возвращаться большее значение. Вроде бы «лениво» расширяет внутренние взгляды. Есть ли обходные пути для этого?
Legoless 04
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
Хардик Таккар,
8

Простым решением было бы просто использовать, webView.scrollView.contentSizeно я не знаю, работает ли это с JavaScript. Если не используется JavaScript, это наверняка работает:

- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
    CGSize contentSize = aWebView.scrollView.contentSize;
    NSLog(@"webView contentSize: %@", NSStringFromCGSize(contentSize));
}
OemerA
источник
3

AFAIK вы можете использовать, [webView sizeThatFits:CGSizeZero]чтобы выяснить размер содержимого.

Rengers
источник
3

Это странно!

Я тестировал оба решения, sizeThatFits:и [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]они НЕ работают для меня.

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

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

Проверено в iOS 9.3 Симулятор / Устройство, удачи!

РЕДАКТИРОВАТЬ:

Справочная информация: содержимое html рассчитывается моей строковой переменной и шаблоном содержимого HTTP, загруженным методом loadHTMLString:baseURL:, без зарегистрированных сценариев JS.

Итачи
источник
3

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

iAnkit
источник
2

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

Если 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.

Heath Borders
источник
1
Я пробовал все примеры в этом потоке, и использование «document.height» для получения высоты документа является наиболее надежным. Дополнительный HTML может быть добавлен вне контейнера (например, DIV), что делает getElementById (...) ненадежным во всех тестах, которые я выполнил. document.height отлично работает.
Джон Роджерс
2

Ни одно из предложенных здесь предложений не помогло мне в моей ситуации, но я прочитал кое-что, что дало мне ответ. У меня есть ViewController с фиксированным набором элементов управления пользовательского интерфейса, за которым следует UIWebView. Я хотел, чтобы вся страница прокручивалась, как если бы элементы управления пользовательского интерфейса были подключены к содержимому HTML, поэтому я отключил прокрутку в UIWebView и затем должен правильно установить размер содержимого родительского представления прокрутки.

Важным советом оказалось то, что UIWebView не сообщает свой размер правильно до тех пор, пока не отобразится на экране. Поэтому, когда я загружаю контент, я устанавливаю размер контента на доступную высоту экрана. Затем в viewDidAppear я обновляю размер содержимого scrollview до правильного значения. У меня это сработало, потому что я вызываю loadHTMLString для локального контента. Если вы используете loadRequest, вам может потребоваться обновить contentSize в webViewDidFinishLoad, в зависимости от того, как быстро извлекается html.

Мерцания нет, потому что изменяется только невидимая часть прокрутки.

Эли Берк
источник
Боюсь, это чистая удача, что веб-представление загружается достаточно быстро, чтобы завершить его на viewDidAppear. Вероятно, это работает только в том случае, если представление выглядит анимированным, поэтому анимации достаточно для загрузки содержимого. Я бы предпочел рассчитывать на webViewDidFinishLoadбезопасный подход.
Ortwin Gentz
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 -scheme в свой 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;
    }
    ...

И, наконец, добавьте следующий код в свой 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 Вы можете реализовать временную задержку (SetTimeout и setInterval) внутри вашего ObjectiveC / Swift-кода - решать вам.

PSS Важная информация о UIWebView и Facebook Embeds: встроенная публикация Facebook не отображается должным образом в UIWebView

MingalevME
источник
Это интересно! Не могли бы вы описать, что делает часть сценария и как ее можно настроить при необходимости? Похоже, он будет периодически проверять высоту, но задействовано несколько временных интервалов?
Киран Харпер,
@KieranHarper, что именно вам нужно описать?
MingalevME
2

При использовании веб-просмотра в качестве подпредставления где-то в режиме прокрутки вы можете установить ограничение высоты на некоторое постоянное значение, а затем сделать выход из него и использовать его как:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    webView.scrollView.scrollEnabled = NO;
    _webViewHeight.constant = webView.scrollView.contentSize.height;
}
younke
источник
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, но не contentsize.height.

hstdt
источник
Это причина, по которой я не смог получить требуемый фрейм в моем случае .. проголосовало за
NSPratik
0

Также в iOS 7 для правильной работы всех упомянутых методов добавьте это в свой viewDidLoadметод контроллера представления :

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

В противном случае ни один из методов не работал бы должным образом.

Николай Шубенков
источник