SwiftUI WKWebView проблема с высотой содержимого - PullRequest
3 голосов
/ 17 января 2020

Неделю я застрял с этой проблемой, где у меня есть мой пользовательский WKWebView с UIViewRepresentable

struct Webview : UIViewRepresentable {
    var webview: WKWebView?

    init() {
        self.webview = WKWebView()
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: Webview

        init(_ parent: Webview) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print("Loading finished -- Delegate")
            webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in
                print(height)
                webView.bounds.size.height = height as! CGFloat
            })
        }

    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView  {
        return webview!
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.navigationDelegate = context.coordinator
        let htmlStart = "<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\"></HEAD><BODY>"
        let htmlEnd = "</BODY></HTML>"
        let dummy_html = """
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ut venenatis risus. Fusce eget orci quis odio lobortis hendrerit. Vivamus in sollicitudin arcu. Integer nisi eros, hendrerit eget mollis et, fringilla et libero. Duis tempor interdum velit. Curabitur</p>
                        <p>ullamcorper, nulla nec elementum sagittis, diam odio tempus erat, at egestas nibh dui nec purus. Suspendisse at risus nibh. Mauris lacinia rutrum sapien non faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec interdum enim et augue suscipit, vitae mollis enim maximus.</p>
                        <p>Fusce et convallis ligula. Ut rutrum ipsum laoreet turpis sodales, nec gravida nisi molestie. Ut convallis aliquet metus, sit amet vestibulum risus dictum mattis. Sed nec leo vel mauris pharetra ornare quis non lorem. Aliquam sed justo</p>
                        """
        let htmlString = "\(htmlStart)\(dummy_html)\(htmlEnd)"
        uiView.loadHTMLString(htmlString, baseURL:  nil)
    }
}

, и он выглядит так:

enter image description here

, и проблема здесь в том, что веб-просмотру не хватает высоты. На мой взгляд, он не появится, если я не добавлю жестко закодированное значение frame, где мой контент обрезается.

Webview()
   .frame(height:300)

Я почти сталкивался с похожими вопросами, но это не помогло: /

Ответы [ 2 ]

5 голосов
/ 17 января 2020

Это сбивает с толку ScrollView в SwiftUI, который ожидает заранее известный размер контента, и UIWebView внутренний UIScrollView, который пытается получить размер из родительского представления ... cycling.

Итак здесь возможен подход ... передать определенный размер из веб-представления в мир SwiftUI, чтобы не использовать жесткое кодирование и ScrollView ведет себя как плоский контент.

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

enter image description here

Вот полный код модуля демонстрации. Протестировано и работало на Xcode 11.2 / iOS 13.2.

import SwiftUI
import WebKit

struct Webview : UIViewRepresentable {
    @Binding var dynamicHeight: CGFloat
    var webview: WKWebView = WKWebView()

    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: Webview

        init(_ parent: Webview) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in
                DispatchQueue.main.async {
                    self.parent.dynamicHeight = height as! CGFloat
                }
            })
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView  {
        webview.scrollView.bounces = false
        webview.navigationDelegate = context.coordinator
        let htmlStart = "<HTML><HEAD><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\"></HEAD><BODY>"
        let htmlEnd = "</BODY></HTML>"
        let dummy_html = """
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ut venenatis risus. Fusce eget orci quis odio lobortis hendrerit. Vivamus in sollicitudin arcu. Integer nisi eros, hendrerit eget mollis et, fringilla et libero. Duis tempor interdum velit. Curabitur</p>
                        <p>ullamcorper, nulla nec elementum sagittis, diam odio tempus erat, at egestas nibh dui nec purus. Suspendisse at risus nibh. Mauris lacinia rutrum sapien non faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec interdum enim et augue suscipit, vitae mollis enim maximus.</p>
                        <p>Fusce et convallis ligula. Ut rutrum ipsum laoreet turpis sodales, nec gravida nisi molestie. Ut convallis aliquet metus, sit amet vestibulum risus dictum mattis. Sed nec leo vel mauris pharetra ornare quis non lorem. Aliquam sed justo</p>
                        """
        let htmlString = "\(htmlStart)\(dummy_html)\(htmlEnd)"
        webview.loadHTMLString(htmlString, baseURL:  nil)
        return webview
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
    }
}


struct TestWebViewInScrollView: View {
    @State private var webViewHeight: CGFloat = .zero
    var body: some View {
        ScrollView {
            VStack {
                Image(systemName: "doc")
                    .resizable()
                    .scaledToFit()
                    .frame(height: 300)
                Divider()
                Webview(dynamicHeight: $webViewHeight)
                    .padding(.horizontal)
                    .frame(height: webViewHeight)
            }
        }
    }
}

struct TestWebViewInScrollView_Previews: PreviewProvider {
    static var previews: some View {
        TestWebViewInScrollView()
    }
}
3 голосов
/ 22 января 2020

Если я просто помещу ваш Webview в VStack, то он будет иметь размеры, как ожидается. Там нет никаких реальных проблем.

Хотя вы не заявили об этом, наиболее вероятная причина того, что ваш Webview не занимает никакого места, состоит в том, что вы поместили эти элементы в ScrollView. Поскольку WKWebView в основном (хотя и не является подклассом) UIScrollView, система не знает, как его размер, если он содержится в другом представлении прокрутки.

Например:

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView { // THIS LINE means that...
                VStack(spacing: 0.0) {
                    Color.red.aspectRatio(1.0, contentMode: .fill)
                    Text("Lorem ipsum dolor sit amet").fontWeight(.bold)
                    Webview()
                        .frame(height: 300) // ...THIS is necessary 
                }
            }
        .navigationBarItems(leading: Text("Item"))
            .navigationBarTitle("", displayMode: .inline)
        }
    }
}

Вложенные прокрутки - это не то, что вам нужно для макета, который вы показали в любом случае. Вы, несомненно, хотите, чтобы изображение и текст вверху прокручивались, когда прокручивается WKWebView.

Техника, которую вы должны использовать с UIKit или SwiftUI, будет похожей. Я действительно не рекомендую делать это с SwiftUI на этом этапе.

  1. Поместите WKWebView в контейнер (наиболее вероятно UIViewController.view)
  2. Поместите содержимое заголовка (изображение плюс текст, который может быть в UIHostingContainer) в этом представлении в виде родного брата над веб-представлением.
  3. Установите для webView.scrollView.contentInset.top размер вашего контента от # 2
  4. Реализация UIScrollViewDelegate.scrollViewDidScroll()
  5. В scrollViewDidScroll измените положение своего контента, чтобы оно соответствовало contentOffset.

Вот одна из возможных реализаций:

WebView:

import SwiftUI
import WebKit

struct WebView : UIViewRepresentable {

    @Binding var offset: CGPoint
    var contentInset: UIEdgeInsets = .zero

    class Coordinator: NSObject, UIScrollViewDelegate {
        var parent: WebView

        init(_ parent: WebView) {
            self.parent = parent
        }

        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            var offset = scrollView.contentOffset
            offset.y += self.parent.contentInset.top
            self.parent.offset = offset
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView {
        let webview = WKWebView()
        webview.scrollView.delegate = context.coordinator
        // continue setting up webview content
        return webview
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        if uiView.scrollView.contentInset != self.contentInset {
            uiView.scrollView.contentInset = self.contentInset
        }
    }

}

ContentView. Примечание: я использовал 50 в качестве константы вместо расчета размера. Можно получить фактический размер, используя GeometryReader, хотя.

struct ContentView: View {
    @State var offset: CGPoint = .zero
    var body: some View {
        NavigationView {
            WebView(offset: self.$offset, contentInset: UIEdgeInsets(top: 50, left: 0, bottom: 0, right: 0))
            .overlay(
                Text("Hello World!")
                    .frame(height: 50)
                    .offset(y: -self.offset.y)
                , alignment: .topLeading)
                .edgesIgnoringSafeArea(.all)
        }
        .navigationBarItems(leading: Text("Item"))
            .navigationBarTitle("", displayMode: .inline)
    }
}
...