Comment déterminer la taille du contenu d'un UIWebView?

116

J'ai un UIWebViewcontenu différent (d'une seule page). J'aimerais connaître CGSizele contenu pour redimensionner correctement les vues de mes parents. L'évident -sizeThatFits:renvoie malheureusement simplement la taille actuelle du cadre de la webView.

Ortwin Gentz
la source
1
Je ne suis pas sûr, mais peut-être que les réponses à cette question vous aideront: stackoverflow.com/questions/745160/…
Greg

Réponses:

250

Il s'est avéré que ma première estimation -sizeThatFits:n'était pas complètement fausse. Cela semble fonctionner, mais seulement si le cadre de la webView est défini sur une taille minimale avant l'envoi -sizeThatFits:. Après cela, nous pouvons corriger la mauvaise taille de cadre par la taille appropriée. Cela semble terrible mais ce n'est en fait pas si mal. Étant donné que nous effectuons les deux changements d'image l'un après l'autre, la vue n'est pas mise à jour et ne scintille pas.

Bien sûr, nous devons attendre que le contenu soit chargé, nous mettons donc le code dans la -webViewDidFinishLoad:méthode déléguée.

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
}

Je dois souligner qu'il existe une autre approche (merci @GregInYEG) utilisant JavaScript. Je ne sais pas quelle solution fonctionne le mieux.

De deux solutions hacky, j'aime mieux celle-ci.

Ortwin Gentz
la source
J'ai essayé votre approche. Le problème que j'ai rencontré est qu'il n'y a pas de défilement. Aucune suggestion?
test
1
J'ai découvert que le problème de défilement se produit en raison de la modification de la hauteur de la vue Web. Dois-je changer les tailles d'UIView?
test
Je ne sais pas quel est votre problème. Peut-être pouvez-vous publier une nouvelle question avec un code ou une description?
Ortwin Gentz
@testing, @Bogatyr: Oui, si votre point de vue UIWebViewest supérieur à votre vue parent, vous devez l'intégrer dans un fichier UIScrollView.
Ortwin Gentz
7
si je donne webView.scalesPageToFit = YES; alors seule cette solution fonctionne..merci pour la solution ..
SP
92

J'ai une autre solution qui fonctionne très bien.

D'une part, l'approche et la solution d'Ortwin ne fonctionnent qu'avec iOS 6.0 et versions ultérieures, mais ne fonctionnent pas correctement sur iOS 5.0, 5.1 et 5.1.1, et d'autre part, il y a quelque chose que je n'aime pas et que je ne comprends pas. avec l'approche d'Ortwin, c'est l'utilisation de la méthode [webView sizeThatFits:CGSizeZero]avec le paramètre CGSizeZero: Si vous lisez la documentation officielle d'Apple sur cette méthode et son paramètre, elle dit clairement:

L'implémentation par défaut de cette méthode renvoie la partie de taille du rectangle de limites de la vue. Les sous-classes peuvent remplacer cette méthode pour renvoyer une valeur personnalisée basée sur la disposition souhaitée de toutes les sous-vues. Par exemple, un objet UISwitch renvoie une valeur de taille fixe qui représente la taille standard d'une vue de commutation, et un objet UIImageView renvoie la taille de l'image qu'il affiche actuellement.

Ce que je veux dire, c'est que c'est comme s'il était tombé sur sa solution sans aucune logique, car à la lecture de la documentation, le paramètre passé à [webView sizeThatFits: ...]devrait au moins avoir celui souhaité width. Avec sa solution, la largeur souhaitée est définie sur le webViewcadre du 's avant d'appeler sizeThatFitsavec unCGSizeZero paramètre. Je maintiens donc que cette solution fonctionne sur iOS 6 par «hasard».

J'ai imaginé une approche plus rationnelle, qui a l'avantage de travailler pour iOS 5.0 et plus tard ... Et aussi dans des situations complexes où plus d'un webView (Avec sa propriété webView.scrollView.scrollEnabled = NOest embarquée dans un fichier scrollView.

Voici mon code pour forcer la mise en page de la webViewà la valeur souhaitée widthet récupérer le heightjeu correspondant à webViewlui - même:

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

Notez que dans mon exemple, webViewa été incorporé dans une coutume scrollViewayant d' autres webViews... Tous ces webViewsavaient leur webView.scrollView.scrollEnabled = NO, et le dernier morceau de code que je devais ajouter était le calcul du heightde contentSizema coutume scrollViewintégrer ces webViews, mais il était aussi facile comme la somme ma webViewest frame.size.heightcalculée avec l'astuce décrite ci - dessus ...

Phénomènes
la source
3
C'est beaucoup plus élégant que la solution acceptée, et cela ne repose pas sur un comportement non documenté et non défini. C'est beaucoup moins hackish que d'insérer du javascript. J'aurais aimé pouvoir voter 64 fois.
bugloaf
C'est exactement ce que j'ai essayé d'expliquer. Je vous remercie. Et son grand avantage est qu'il semble fonctionner sous toutes les versions iOS.
Phenomena
La solution a l'air bien, mais soyez prudent si vous ciblez toujours iOS 4.x. -[UIWebView scrollView]est disponible dans iOS 5.0 ou version ultérieure uniquement.
Ortwin Gentz
2
J'ai essayé ceci et une solution similaire et il s'est avéré que cela ne fonctionne que si la vue Web ou la vue dans laquelle elle est intégrée sont déjà à l'écran. Si vous essayez ceci avant d'ajouter la vue Web à la hiérarchie des vues, le résultat sera la hauteur de taille manuelle.
k1th
1
l'autre solution ne fonctionne pas à chaque fois, je ne sais pas pourquoi, mais cela fait l'affaire !!! cela devrait être étiqueté comme la bonne réponse.
Vassily
37

Ressusciter cette question parce que j'ai trouvé que la réponse d'Ortwin ne fonctionnait que la plupart du temps ...

La webViewDidFinishLoadméthode peut être appelée plus d'une fois et la première valeur renvoyée par sizeThatFitsn'est qu'une partie de la taille finale. Alors, quelle qu'en soit la raison, le prochain appel à sizeThatFitsquand se webViewDidFinishLoaddéclenche à nouveau retournera incorrectement la même valeur qu'auparavant! Cela se produira au hasard pour le même contenu que s'il s'agissait d'une sorte de problème de concurrence. Peut-être que ce comportement a changé au fil du temps, car je construis pour iOS 5 et j'ai également constaté quesizeToFit fonctionne à peu près de la même manière (bien que précédemment ce ne soit pas le cas?)

J'ai opté pour cette solution simple:

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

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

Mise à jour: j'ai trouvé comme mentionné dans les commentaires que cela ne semble pas saisir le cas où le contenu a diminué. Je ne sais pas si c'est vrai pour tout le contenu et la version du système d'exploitation, essayez-le.

Kieran Harper
la source
Avez-vous inclus des images ou d'autres éléments dans votre page Web? Cela pourrait provoquer le licenciement supplémentaire du délégué. En dehors de cela, votre solution ne fonctionne que lorsque vous ne donnez pas webView.scalesPageToFit = YEScomme @Sijo mentionné dans le commentaire ci-dessus.
Ortwin Gentz
Ouais c'est probablement ça - votre solution fonctionne bien dans mes cas de texte uniquement. Mais sizeThatFits ne donne pas la bonne valeur lors de l'appel final du délégué est le problème ... très étrange. Comment pourrions-nous savoir quel appel sera le dernier pour ne pas essayer d'utiliser sizeThatFits d'ici là?
Kieran Harper
Si lors du premier appel de délégué, vous obtenez le bon résultat de sizeThatFits, ne pouvez-vous pas simplement ignorer les appels suivants?
Ortwin Gentz
Oui, mais c'est l'inverse :) Le premier appel de délégué peut entraîner le mauvais sizeThatFits, puis cette valeur reste. C'est comme si sizeThatFits modifiait d'une manière ou d'une autre la vue Web afin que tout autre appel à sizeThatFits donne le même résultat, ou que ce soit le réglage du cadre pendant son chargement qui le fait.
Kieran Harper
1
Cette solution ne fonctionne pas correctement non plus. Une fois que vous avez défini une page plus grande, puis une plus petite, vous obtiendrez toujours la valeur la plus élevée. On dirait qu'il étend "paresseusement" les vues internes. Des solutions de contournement pour cela?
Legoless
23

Dans Xcode 8 et iOS 10 pour déterminer la hauteur d'une vue Web. vous pouvez obtenir de la hauteur en utilisant

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

OU pour Swift

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }
Hardik Thakkar
la source
1
Essayez de trouver en utilisant la taille du contenu de la vue Web .. myWebView.scrollView.contentSize.height
Hardik Thakkar
8

Une solution simple serait de simplement utiliser webView.scrollView.contentSizemais je ne sais pas si cela fonctionne avec JavaScript. S'il n'y a pas de JavaScript utilisé, cela fonctionne à coup sûr:

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

AFAIK, vous pouvez utiliser [webView sizeThatFits:CGSizeZero]pour déterminer la taille de son contenu.

Rengers
la source
3

C'est bizarre!

Je l' ai testé les solutions à la fois sizeThatFits:et [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]sont pas travailler pour moi.

Cependant, j'ai trouvé un moyen simple et intéressant d'obtenir la bonne hauteur de contenu de la page Web. Actuellement, je l'ai utilisé dans ma méthode déléguée scrollViewDidScroll:.

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

Vérifié dans le simulateur / appareil iOS 9.3, bonne chance!

ÉDITER:

Contexte: Le contenu html est calculé par ma variable de chaîne et mon modèle de contenu HTTP, chargé par méthode loadHTMLString:baseURL:, aucun script JS enregistré.

Itachi
la source
3

Pour iOS10, je recevais 0 (zéro) valeur document.heightdonc document.body.scrollHeightest la solution pour obtenir la hauteur du document Webview. Le problème peut être résolu également pour width.

iAnkit
la source
2

J'utilise un UIWebViewqui n'est pas une sous-vue (et ne fait donc pas partie de la hiérarchie des fenêtres) pour déterminer les tailles du contenu HTML pour UITableViewCells. J'ai trouvé que le déconnecté UIWebViewne signale pas correctement sa taille avec -[UIWebView sizeThatFits:]. De plus, comme mentionné dans https://stackoverflow.com/a/3937599/9636 , vous devez définir le UIWebView's frame heightsur 1 afin d'obtenir la bonne hauteur.

Si la UIWebViewhauteur du s est trop grande (c'est-à-dire que vous l'avez définie sur 1000, mais la taille du contenu HTML n'est que de 500):

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

Tous renvoient un heightde 1000.

Pour résoudre mon problème dans ce cas, j'ai utilisé https://stackoverflow.com/a/11770883/9636 , que j'ai consciencieusement voté. Cependant, je n'utilise cette solution que lorsque mon UIWebView.frame.widthest le même que le -[UIWebView sizeThatFits:] width.

Frontières de la santé
la source
1
J'ai essayé tous les exemples de ce fil, et utiliser "document.height" pour récupérer la hauteur du document est le plus fiable. Du HTML supplémentaire pourrait être ajouté en dehors d'un conteneur (tel que DIV), ce qui rend getElementById (...) peu fiable dans tous les tests que j'ai effectués. document.height fonctionne un régal.
John Rogers
2

Aucune des suggestions ici ne m'a aidé avec ma situation, mais j'ai lu quelque chose qui m'a donné une réponse. J'ai un ViewController avec un ensemble fixe de contrôles d'interface utilisateur suivi d'un UIWebView. Je voulais que la page entière défile comme si les contrôles de l'interface utilisateur étaient connectés au contenu HTML.Je désactive donc le défilement sur UIWebView et je dois ensuite définir correctement la taille du contenu d'une vue de défilement parent.

Le conseil important s'est avéré être que UIWebView ne signale pas sa taille correctement jusqu'à ce qu'il soit rendu à l'écran. Ainsi, lorsque je charge le contenu, je règle la taille du contenu sur la hauteur d'écran disponible. Ensuite, dans viewDidAppear, je mets à jour la taille du contenu du scrollview à la valeur correcte. Cela a fonctionné pour moi car j'appelle loadHTMLString sur le contenu local. Si vous utilisez loadRequest, vous devrez peut-être également mettre à jour contentSize dans webViewDidFinishLoad, en fonction de la rapidité avec laquelle le code HTML est récupéré.

Il n'y a pas de scintillement, car seule la partie invisible de la vue de défilement est modifiée.

Eli Burke
la source
Je crains que ce soit par pure chance que la vue Web se charge assez rapidement pour qu'elle soit terminée à viewDidAppear. Cela ne fonctionne probablement que si la vue semble animée, de sorte que l'animation est suffisamment longue pour charger le contenu. Je préfère me fier à webViewDidFinishLoadl'approche sûre.
Ortwin Gentz
2

Si votre HTML contient des contenus HTML lourds comme ceux d'iframe (c'est-à-dire facebook, twitter, instagram-embeds), la vraie solution est beaucoup plus difficile, enveloppez d'abord votre 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>"];

Ensuite, ajoutez la gestion de x-update-webview-height -scheme dans votre 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;
    }
    ...

Et enfin, ajoutez le code suivant dans votre 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 Vous pouvez implémenter la temporisation (SetTimeout et setInterval) dans votre ObjectiveC / code Swift - c'est à vous de décider.

PSS Informations importantes sur UIWebView et Facebook Embeds: la publication Facebook intégrée ne s'affiche pas correctement dans UIWebView

MingalevME
la source
C'est intéressant! Pourriez-vous décrire ce que fait la partie de script et comment elle peut être modifiée si nécessaire? On dirait qu'il vérifiera périodiquement la hauteur, mais il y a plusieurs intervalles de temps impliqués?
Kieran Harper
@KieranHarper, que faut-il exactement décrire?
MingalevME
2

Lorsque vous utilisez la vue Web comme sous-vue quelque part dans la vue de défilement, vous pouvez définir la contrainte de hauteur sur une valeur constante, puis en faire une sortie et l'utiliser comme:

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

Je suis également coincé sur ce problème, puis j'ai réalisé que si je veux calculer la hauteur dynamique de la webView, je dois d'abord indiquer la largeur de la webView, donc j'ajoute une ligne avant js et il s'avère que je peux obtenir hauteur réelle très précise.

Le code est simple comme ceci:

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

    //......
}

la source
1

Xcode8 swift3.1:

  1. Définissez d'abord la hauteur de webView sur 0 .
  2. En webViewDidFinishLoaddélégué:

let height = webView.scrollView.contentSize.height

Sans step1, si webview.height> actual contentHeight, l'étape 2 renverra webview.height mais pas contentsize.height.

hstdt
la source
C'est la raison pour laquelle je n'ai pas pu obtenir le cadre requis dans mon cas .. voté pour
NSPratik
0

Également dans iOS 7 pour le bon fonctionnement de toutes les méthodes mentionnées, ajoutez ceci dans votre viewDidLoadméthode de contrôleur de vue :

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

Sinon, aucune des méthodes ne fonctionnerait comme elle le devrait.

Nikolay Shubenkov
la source