Bagaimana cara menentukan ukuran konten UIWebView?

116

Saya memiliki konten UIWebViewdengan (satu halaman) berbeda. Saya ingin mencari tahu CGSizekonten untuk mengubah ukuran tampilan orang tua saya dengan tepat. -sizeThatFits:Sayangnya yang jelas hanya mengembalikan ukuran bingkai webView saat ini.

Ortwin Gentz
sumber
1
Saya tidak yakin, tapi mungkin jawaban atas pertanyaan ini akan membantu: stackoverflow.com/questions/745160/…
Greg

Jawaban:

250

Ternyata tebakan pertama saya menggunakan -sizeThatFits:tidak sepenuhnya salah. Tampaknya berfungsi, tetapi hanya jika bingkai webView disetel ke ukuran minimal sebelum dikirim -sizeThatFits:. Setelah itu kita bisa mengoreksi ukuran frame yang salah dengan ukuran yang pas. Ini terdengar buruk tapi sebenarnya tidak seburuk itu. Karena kami melakukan kedua perubahan bingkai tepat setelah satu sama lain, tampilan tidak diperbarui dan tidak berkedip.

Tentu saja, kami harus menunggu hingga konten dimuat, jadi kami memasukkan kode ke dalam -webViewDidFinishLoad:metode delegasi.

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

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

Saya harus menunjukkan bahwa ada pendekatan lain (terima kasih @GregInYEG) menggunakan JavaScript. Tidak yakin solusi mana yang berkinerja lebih baik.

Dari dua solusi hacky, saya lebih suka yang ini.

Ortwin Gentz
sumber
Saya mencoba pendekatan Anda. Masalah yang saya alami adalah tidak ada scrolling. Ada saran?
menguji
1
Saya menemukan bahwa masalah dengan pengguliran terjadi karena ketinggian tampilan web yang berubah. Apakah saya harus mengubah ukuran UIView?
menguji
Tidak yakin apa masalah Anda. Mungkin Anda dapat memposting pertanyaan baru dengan beberapa kode atau deskripsi?
Ortwin Gentz
@testing, @Bogatyr: Benar, jika UIWebViewtampilan Anda lebih tinggi dari tampilan orang tua, Anda perlu menyematkannya ke file UIScrollView.
Ortwin Gentz
7
jika saya memberikan webView.scalesPageToFit = YES; maka hanya solusi ini yang berhasil .. terima kasih untuk solusinya ..
SP
92

Saya memiliki solusi lain yang berfungsi dengan baik.

Di satu sisi, pendekatan & solusi Ortwin hanya berfungsi dengan iOS 6.0 dan yang lebih baru, tetapi gagal berfungsi dengan benar di iOS 5.0, 5.1 dan 5.1.1, dan di sisi lain ada sesuatu yang saya tidak suka dan tidak dapat mengerti. dengan pendekatan Ortwin, itu adalah penggunaan metode [webView sizeThatFits:CGSizeZero]dengan parameter CGSizeZero: Jika Anda membaca dokumentasi Resmi Apple tentang metode ini dan parameternya, dikatakan dengan jelas:

Implementasi default metode ini mengembalikan porsi ukuran persegi panjang batas tampilan. Subclass dapat mengganti metode ini untuk mengembalikan nilai kustom berdasarkan tata letak yang diinginkan dari setiap subview. Misalnya, objek UISwitch mengembalikan nilai ukuran tetap yang mewakili ukuran standar tampilan sakelar, dan objek UIImageView mengembalikan ukuran gambar yang sedang ditampilkan.

Yang saya maksud adalah sepertinya dia menemukan solusinya tanpa logika apa pun, karena membaca dokumentasi, parameter yang diteruskan [webView sizeThatFits: ...]setidaknya harus memiliki yang diinginkan width. Dengan solusinya, lebar yang diinginkan diatur ke webViewbingkai sebelum memanggil sizeThatFitsdengan CGSizeZeroparameter. Jadi saya mempertahankan solusi ini berfungsi di iOS 6 secara "kebetulan".

Saya membayangkan pendekatan yang lebih rasional, yang memiliki keuntungan bekerja untuk iOS 5.0 dan yang lebih baru ... Dan juga dalam situasi kompleks di mana lebih dari satu webView (Dengan propertinya webView.scrollView.scrollEnabled = NOtertanam dalam file scrollView.

Berikut adalah kode saya untuk memaksa Layout webViewke yang diinginkan widthdan mendapatkan heightset yang sesuai kembali ke webViewdirinya sendiri:

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

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

Perhatikan bahwa dalam contoh saya, webViewitu tertanam dalam kebiasaan yang scrollViewmemiliki webViews... Semua ini webViewsmemiliki mereka webView.scrollView.scrollEnabled = NO, dan bagian terakhir dari kode yang harus saya tambahkan adalah perhitungan dari heightdari embedding contentSizekhusus saya scrollViewini webViews, tetapi itu semudah sebagai menjumlahkan saya webView's frame.size.heightdihitung dengan trik yang dijelaskan di atas ...

Fenomena
sumber
3
Ini jauh lebih elegan daripada solusi yang diterima, dan tidak bergantung pada perilaku yang tidak terdokumentasi dan tidak terdefinisi. Ini jauh lebih sedikit meretas daripada memasukkan javascript. Saya berharap saya dapat meningkatkan ini 64 kali.
bugloaf
Itulah yang coba saya jelaskan. Terima kasih. Dan keuntungan besar adalah tampaknya berfungsi di semua versi iOS.
Fenomena
Solusinya memang terlihat bagus, tetapi hati-hati jika Anda masih menargetkan iOS 4.x. -[UIWebView scrollView]hanya tersedia di iOS 5.0 atau lebih baru.
Ortwin Gentz
2
Saya mencoba ini dan solusi serupa dan ternyata ini hanya berfungsi, jika tampilan web atau tampilan yang disematkannya, sudah ada di layar. Jika Anda mencoba ini sebelum menambahkan tampilan web ke hierarki tampilan, hasilnya adalah tinggi ukuran manual.
k1
1
solusi lain tidak bekerja setiap saat, tidak tahu mengapa, tapi ini berhasil !!! ini harus diberi tag sebagai jawaban yang benar.
Vassily
37

Menghidupkan kembali pertanyaan ini karena saya menemukan jawaban Ortwin hanya berfungsi SEBAGIAN besar waktu ...

The webViewDidFinishLoadMetode dapat disebut lebih dari sekali, dan nilai pertama dikembalikan oleh sizeThatFitshanya beberapa bagian dari apa ukuran akhir harus. Lalu untuk alasan apa pun panggilan berikutnya ke sizeThatFitssaat webViewDidFinishLoaddiaktifkan lagi akan salah mengembalikan nilai yang sama seperti sebelumnya! Ini akan terjadi secara acak untuk konten yang sama seolah-olah itu semacam masalah konkurensi. Mungkin perilaku ini telah berubah dari waktu ke waktu, karena saya sedang membangun untuk iOS 5 dan juga menemukan bahwa sizeToFitbekerja dengan cara yang hampir sama (meskipun sebelumnya tidak?)

Saya telah menetapkan solusi sederhana ini:

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

Pembaruan: Saya telah menemukan seperti yang disebutkan di komentar ini tampaknya tidak menangkap kasus di mana konten telah menyusut. Tidak yakin apakah itu benar untuk semua konten dan versi OS, cobalah.

Kieran Harper
sumber
Sudahkah Anda menyertakan gambar atau aset lain di halaman web Anda? Itu mungkin menyebabkan pemecatan tambahan pada delegasi. Selain itu, solusi Anda hanya berfungsi jika Anda tidak memberikan webView.scalesPageToFit = YESseperti yang disebutkan @Sijo dalam komentar di atas.
Ortwin Gentz
Ya mungkin itu - solusi Anda berfungsi dengan baik dalam kasus hanya teks saya. Tapi sizeThatFits tidak memberikan nilai yang benar selama panggilan delegasi terakhir adalah masalahnya ... sangat aneh. Bagaimana kita bisa tahu panggilan mana yang akan menjadi yang terakhir sehingga kita tidak mencoba menggunakan sizeThatFits sampai saat itu?
Kieran Harper
Jika dalam panggilan delegasi pertama Anda mendapatkan hasil yang benar dari sizeThatFits, tidak bisakah Anda mengabaikan panggilan berikut?
Ortwin Gentz
Ya, tetapi sebaliknya :) Panggilan delegasi pertama dapat menghasilkan sizeThatFits yang salah, lalu nilainya tetap. Seolah-olah salah satu sizeThatFits entah bagaimana memodifikasi tampilan web sehingga panggilan sizeThatFits lebih lanjut memberikan hasil yang sama, atau pengaturan frame saat masih memuat yang melakukannya.
Kieran Harper
1
Solusi ini juga tidak berfungsi dengan benar. Setelah Anda mengatur halaman yang lebih besar, dan kemudian mengatur yang lebih kecil, Anda akan selalu mendapatkan nilai yang lebih besar. Sepertinya itu "malas" memperluas pandangan internal. Ada solusi untuk ini?
Tanpa kaki
23

Di Xcode 8 dan iOS 10 untuk menentukan ketinggian tampilan web. Anda bisa mendapatkan ketinggian menggunakan

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

ATAU untuk Swift

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }
Hardik Thakkar
sumber
1
Cobalah untuk menemukan menggunakan ukuran konten tampilan web .. myWebView.scrollView.contentSize.height
Hardik Thakkar
8

Solusi sederhana adalah dengan hanya menggunakan webView.scrollView.contentSizetetapi saya tidak tahu apakah ini berfungsi dengan JavaScript. Jika tidak ada JavaScript yang digunakan, ini pasti berfungsi:

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

AFAIK dapat Anda gunakan [webView sizeThatFits:CGSizeZero]untuk mengetahui ukuran isinya.

Renger
sumber
3

Aneh!

Saya diuji solusi baik sizeThatFits:dan [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]yang tidak bekerja untuk saya.

Namun, saya menemukan cara mudah yang menarik untuk mendapatkan ketinggian yang tepat dari konten halaman web. Saat ini, saya menggunakannya dalam metode delegasi saya scrollViewDidScroll:.

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

Diverifikasi di iOS 9.3 Simulator / Perangkat, semoga berhasil!

EDIT:

Latar belakang: Konten html dihitung oleh variabel string saya dan template konten HTTP, dimuat dengan metode loadHTMLString:baseURL:, tidak ada skrip JS terdaftar di sana.

Itachi
sumber
3

Untuk iOS10, saya mendapatkan nilai 0 (nol) document.heightbegitu document.body.scrollHeightjuga solusi untuk mendapatkan tinggi dokumen di Webview. Masalahnya bisa diatasi juga untuk width.

iAnkit
sumber
2

Saya menggunakan UIWebViewyang bukan subview (dan karenanya bukan bagian dari hierarki jendela) untuk menentukan ukuran konten HTML UITableViewCells. Saya menemukan bahwa terputus UIWebViewtidak melaporkan ukurannya dengan benar -[UIWebView sizeThatFits:]. Selain itu, seperti yang disebutkan di https://stackoverflow.com/a/3937599/9636 , Anda harus menyetel UIWebViews frame heightke 1 untuk mendapatkan ketinggian yang tepat.

Jika UIWebViewtinggi badan terlalu besar (misalnya, Anda menyetelnya ke 1000, tetapi ukuran konten HTML hanya 500):

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

Semua mengembalikan height1000.

Untuk mengatasi masalah saya dalam kasus ini, saya menggunakan https://stackoverflow.com/a/11770883/9636 , yang saya pilih dengan patuh. Namun, saya hanya menggunakan solusi ini jika saya UIWebView.frame.widthsama dengan -[UIWebView sizeThatFits:] width.

Heath Borders
sumber
1
Saya telah mencoba semua contoh di utas ini, dan menggunakan "document.height" untuk mengambil tinggi dokumen adalah yang paling dapat diandalkan. HTML tambahan dapat ditambahkan di luar wadah (seperti DIV) yang membuat getElementById (...) tidak dapat diandalkan dalam semua pengujian yang telah saya lakukan. document.height bekerja dengan baik.
John Rogers
2

Tidak ada saran di sini yang membantu saya dengan situasi saya, tetapi saya membaca sesuatu yang memang memberi saya jawaban. Saya memiliki ViewController dengan satu set kontrol UI tetap diikuti oleh UIWebView. Saya ingin seluruh halaman menggulir seolah-olah kontrol UI terhubung ke konten HTML, jadi saya menonaktifkan pengguliran di UIWebView dan kemudian harus mengatur ukuran konten tampilan gulir induk dengan benar.

Tip pentingnya adalah UIWebView tidak melaporkan ukurannya dengan benar hingga ditampilkan ke layar. Jadi ketika saya memuat konten, saya mengatur ukuran konten ke ketinggian layar yang tersedia. Kemudian, di viewDidAppear saya memperbarui ukuran konten scrollview ke nilai yang benar. Ini berhasil untuk saya karena saya memanggil loadHTMLString pada konten lokal. Jika Anda menggunakan loadRequest, Anda mungkin perlu memperbarui contentSize di webViewDidFinishLoad juga, bergantung pada seberapa cepat html diambil.

Tidak ada kedipan, karena hanya bagian tak terlihat dari tampilan gulir yang diubah.

Eli Burke
sumber
Saya khawatir itu murni keberuntungan bahwa tampilan web memuat cukup cepat sehingga selesai di viewDidAppear. Mungkin ini hanya berfungsi jika tampilan tampak beranimasi sehingga animasinya cukup panjang untuk memuat konten. Saya lebih suka mengandalkan webViewDidFinishLoadsebagai pendekatan yang aman.
Ortwin Gentz
2

Jika HTML Anda berisi konten HTML yang berat seperti iframe (yaitu facebook-, twitter, instagram-embeds), solusi sebenarnya jauh lebih sulit, pertama-tama bungkus HTML Anda:

[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>"];

Kemudian tambahkan penanganan x-update-webview-height -scheme ke dalam 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;
    }
    ...

Dan terakhir tambahkan kode berikut di dalam layout Anda :

    ...
    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 Anda dapat mengimplementasikan penundaan waktu (SetTimeout dan setInterval) di dalam ObjectiveC / Swift-code Anda - terserah Anda.

PSS Info penting tentang UIWebView dan Facebook Embeds: Posting Facebook yang disematkan tidak ditampilkan dengan benar di UIWebView

MingalevME
sumber
Ini menarik! Bisakah Anda menjelaskan apa yang dilakukan bagian skrip dan bagaimana hal itu dapat diubah jika diperlukan? Sepertinya itu akan memeriksa ketinggian secara berkala, tetapi ada beberapa interval waktu yang terlibat?
Kieran Harper
@KieranHar, apa sebenarnya yang perlu Anda jelaskan?
MingalevME
2

Saat menggunakan tampilan web sebagai subview di suatu tempat di scrollview, Anda dapat mengatur batasan ketinggian ke beberapa nilai konstan dan kemudian membuat outlet darinya dan menggunakannya seperti:

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

Saya juga terjebak pada masalah ini, kemudian saya menyadari bahwa jika saya ingin menghitung tinggi dinamis dari webView, saya perlu memberi tahu lebar webView terlebih dahulu, jadi saya menambahkan satu baris sebelum js dan ternyata saya bisa mendapatkan tinggi sebenarnya sangat akurat.

Kodenya sederhana seperti ini:

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

    //......
}

sumber
1

Xcode8 swift3.1:

  1. Setel tinggi webView ke 0 terlebih dahulu.
  2. Dalam webViewDidFinishLoadDelegasi:

let height = webView.scrollView.contentSize.height

Tanpa langkah1, jika webview.height> actual contentHeight, langkah 2 akan mengembalikan webview.height tetapi tidak contentsize.height.

hstdt
sumber
Inilah alasan mengapa saya tidak bisa mendapatkan bingkai yang diperlukan dalam kasus saya .. suara positif
NSPratik
0

Juga di iOS 7 untuk bekerja dengan benar dari semua metode yang disebutkan, tambahkan ini dalam viewDidLoadmetode pengontrol tampilan Anda :

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

Jika tidak, tidak ada metode yang akan berfungsi sebagaimana mestinya.

Nikolay Shubenkov
sumber