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

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

Ответ 1

Оказалось, что мое первое предположение с использованием -sizeThatFits: было не совсем ошибочным. Кажется, что это работает, но только если рамка webView установлена ​​на минимальный размер перед отправкой -sizeThatFits:. После этого мы можем исправить неправильный размер рамки по размеру фитинга. Это звучит ужасно, но на самом деле это не так уж плохо. Поскольку мы делаем оба изменения кадра сразу после друг друга, представление не обновляется и не мерцает.

Конечно, нам нужно подождать, пока содержимое будет загружено, поэтому мы помещаем код в метод делегата -webViewDidFinishLoad:.

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

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

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

Ответ 2

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

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

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

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

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

Вот мой код, чтобы заставить макет webView к желаемому width и вернуть соответствующий height к самому webView:

- (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 Frame, forcing the Layout of its embedded scrollView with current Frame constraints (Width set above).

    frame.size.height = aWebView.scrollView.contentSize.height;  // Get the corresponding height from the webView embedded scrollView.

    aWebView.frame = frame;       // Set the scrollView contentHeight back to the frame itself.
}

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

Ответ 3

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

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

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

Ответ 4

В 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)")
    }

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Если высота UIWebView слишком велика (т.е. установлена ​​она на 1000, но размер содержимого HTML составляет всего 500):

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

Все возвращают a height из 1000.

Чтобы решить мою проблему в этом случае, я использовал fooobar.com/questions/51910/..., который я покорно проголосовал. Тем не менее, я использую это решение только тогда, когда мой UIWebView.frame.width совпадает с -[UIWebView sizeThatFits:] width.

Ответ 8

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

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

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

Ответ 9

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

P.S. Вы можете реализовать временную задержку (SetTimeout и setInterval) внутри вашего кода ObjectiveC/Swift - это зависит от вас.

P.S.S. Важная информация о UIWebView и вложениях для Facebook: Вложенная публикация Facebook не отображается правильно в UIWebView

Ответ 10

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

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

Ответ 11

Это странно!

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

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

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

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

EDIT:

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

Ответ 12

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

Ответ 13

Я тоже застрял в этой проблеме, тогда я понял, что, если я хочу рассчитать динамическую высоту 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;

    //......
}

Ответ 14

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

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

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

Ответ 15

Xcode8 swift3.1:

  • Сначала установите высоту webView в 0.
  • В webViewDidFinishLoad Делегат:

let height = webView.scrollView.contentSize.height

Без шага 1, если webview.height > actual contentHeight, шаг 2 вернет webview.height, но не contentize.height.