【问题标题】:SwiftUI WKWebView content height issueSwiftUI WKWebView 内容高度问题
【发布时间】:2020-01-17 15:05:55
【问题描述】:

我被这个问题困扰了一周 我有我的自定义 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)
    }
}

它是这样呈现的

这里的问题是 webview 缺少高度。 除非我添加硬编码 frame 值,否则它不会出现在我的视图中 我的内容被截断了。

Webview()
   .frame(height:300)

我几乎遇到了类似的问题,但没有帮助:/

【问题讨论】:

    标签: swiftui wkwebview


    【解决方案1】:

    混淆了 SwiftUI 中的 ScrollView 和 UIWebView internal UIScrollView,它试图从父视图中获取大小......循环。

    所以这是一种可能的方法.. 将确定的大小从 web 视图传递到 SwiftUI 世界,因此不使用硬编码,ScrollView 的行为就像拥有平面内容。

    根据我的理解和模拟结果的第一次演示......

    这里是完整的demo模块代码。 在 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()
        }
    }
    

    【讨论】:

    • 谢谢@Asperi 好吧,它的工作原理就像如果硬编码了高度值,内容会像imgur.com/IGFRf7y一样被截断,视图不适应我的内容高度(如果主要问题这里)
    • 不知道我理解了这个问题...UIViewRepresentable 消耗可用空间,所以如果它小于WebView 的内容,那么是的,它将被剪切,但WebView 是可滚动的,所以剪切的内容可以通过滚动获得。那么?
    • 我已经有一个 Scrollview 包含( Image + Webview : scrollview disabled 以避免 2 个滚动条)。因此,当 webview 加载时,高度应该是 = 它的内容长度。基本上就像一个 UILabel,它会根据其内容自动扩展。
    • 顺便说一句,我很抱歉我错了,它从一开始就不起作用(甚至你的答案),因为我忘记在我的视图中删除硬编码的帧值.frame(height:300)
    • @Mahi008,请参阅上述方案的更新解决方案。
    【解决方案2】:

    如果我只是将您的 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 中)作为 web 视图上方的兄弟放置在该视图中。
    3. 将 webView.scrollView.contentInset.top 设置为 #2 中的内容大小
    4. 实施UIScrollViewDelegate.scrollViewDidScroll()
    5. 在scrollViewDidScroll 中修改内容的位置以匹配webview 的contentOffset。

    这是一种可能的实现方式:

    网页视图:

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

    内容视图。注意:我使用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)
        }
    }
    

    【讨论】:

    • 谢谢@arsenius,您的实现效果很好(大部分情况下),即使我对图像+文本有点困惑。因为当我在 webview 的顶部 / 之前插入 Image() 时,它只是坐在那里而不滚动!
    • 如果您要坚持使用 SwiftUI,我建议您使用 .overlay() 至极 alignment: .top 或 .topLeading) 作为您的 Image,就像我在那里使用 Text 一样。只要您随后应用偏移量,它就应该可以正常工作。
    • 感谢 @arsenius 抽出宝贵时间,但我想我会坚持使用 Asperi 的实现来保持简单!
    • @Mahi008 它更简单,但根据内容,您将通过修复 webview 的大小遇到内存/速度问题。这是因为 webview 必须一次渲染所有可能的内容,无论它是否实际可见。这对于短页面来说很好,但是随着页面变得更复杂/更长,您可能会遇到问题。不过,只要您的内容最少,就应该没问题。
    猜你喜欢
    • 2021-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-16
    • 2021-10-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-18
    相关资源
    最近更新 更多