SwiftUI:WKWebView在macOS上切断页面内容(NSViewRepresentable)

12

我有一个基本的NSViewRepresentable实现了WKWebView,可用于在macOS上的SwiftUI应用程序中使用。在iOS上,等效的UIViewRepresentable可以正常工作,但在macOS上(原生而非Catalyst),顶部内容总是被裁剪掉。

丢失的量始终等于父视图(如选项卡视图)及其填充的大小,这表明Web视图将其内容缩放到窗口大小,而不是视图大小。

例如,此页面:

Cut Off Web Content

...应该如下所示(在Chrome中显示)。整个导航栏已被裁剪掉(尽管似乎未受影响的两侧)。

Chrome Version Rendering Correctly

有什么建议可以解决这个问题吗?有趣的是,如果我在选项卡之间切换时,内容会正确地显示约1秒钟,然后重新调整大小,因此再次被裁剪掉。这使我认为updateNSView方法中需要做些修改,但我不确定需要做什么。

似乎是与此处讨论的问题类似,但那是针对IB-based应用程序的,我看不到一种适用于SwiftUI的方法。

使用的代码如下。注意:Web视图被保留为属性,以便可以通过其他方法引用它(例如触发页面加载、刷新、返回等)。

public struct WebBrowserView {

    private let webView: WKWebView = WKWebView()

    // ...

    public func load(url: URL) {        
        webView.load(URLRequest(url: url))
    }

    public class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {

        var parent: WebBrowserView

        init(parent: WebBrowserView) {
            self.parent = parent
        }

        public func webView(_: WKWebView, didFail: WKNavigation!, withError: Error) {
            // ...
        }

        public func webView(_: WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error) {
            // ...
        }

        public func webView(_: WKWebView, didFinish: WKNavigation!) {
            // ...
        }

        public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            // ...
        }

        public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            decisionHandler(.allow)
        }

        public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
            if navigationAction.targetFrame == nil {
                webView.load(navigationAction.request)
            }
            return nil
        }
    }

    public func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
}


#if os(macOS) // macOS Implementation (iOS version omitted for brevity)
extension WebBrowserView: NSViewRepresentable {

    public typealias NSViewType = WKWebView

    public func makeNSView(context: NSViewRepresentableContext<WebBrowserView>) -> WKWebView {

        webView.navigationDelegate = context.coordinator
        webView.uiDelegate = context.coordinator
        return webView
    }

    public func updateNSView(_ nsView: WKWebView, context: NSViewRepresentableContext<WebBrowserView>) {

    }
}
#endif

使用示例:

struct BrowserView: View {

    private let browser = WebBrowserView()

    var body: some View {
        HStack {
            browser
                .onAppear() {
                    self.browser.load(url: URL(string: "https://stackoverflow.com/tags")!)
                }
        }
        .padding()
    }
}

struct ContentView: View {

    @State private var selection = 0

    var body: some View { 
        TabView(selection: $selection){
            Text("Email View")
                .tabItem {
                    Text("Email")
                }
                .tag(0)
            BrowserView()
                .tabItem {
                    Text("Browser")
                }
                .tag(1)
        }
        .padding()
    }
}

测试您的代码时,我得到了“类型'WebBrowserView'的值没有成员'load'”。我做错了什么?我把所有这些都粘贴到了ContentView.swift中? - esaruoho
1
谢谢@TheNeil!您的建议帮助我成功运行了这个浏览器页面。现在,我只需要丢弃“电子邮件/浏览器”拆分,并以某种方式显示完整页面,而不带任何额外的窗口装饰:) 我非常感激您的编辑和建议。我已经为此头痛了一个星期,试图找到一些“真正有效”的东西,大多数在线教程都是针对iOS的WKWebView。 - esaruoho
1
谢谢!@TheNeil,这个基本上已经实现了“全部功能”,但唯一缺失的是,当我在浏览器页面上点击“文件浏览器”(上传图片),.app不会显示Finder窗口。如果我能解决这个问题,那就太好了 :) - esaruoho
1
@esaruoho 祝你好运!很抱歉,这超出了我所需要的范围,所以我不确定,但如果我找到解决方案,我会在这里进行更新。也许检查一下您应用的沙盒设置是否包括文件访问、选择等? - TheNeil
1
没问题,你帮了我大忙了!我刚刚发现如何让应用程序窗口弹出全屏。 :) 对我来说,让它显示网站已经是一个巨大的胜利,而你在其中起到了关键作用。我想我会在StackOverflow上提出一些其他问题。是的,据我所知,我已经启用了所有沙盒功能。祝你周末愉快! - esaruoho
显示剩余5条评论
3个回答

11

我在使用SwiftUI创建的MacOS应用中遇到了与WKWebView完全相同的问题。

对我有效的解决方案是使用GeometryReader获取确切的高度,并将Web视图放入ScrollView中(我认为这与布局优先级计算有关,但尚未找到其核心)。

以下是对我有效的片段代码,也许它对你也有用

GeometryReader { g in
    ScrollView {
        BrowserView().tabItem {
            Text("Browser")
        }
        .frame(height: g.size.height)
        .tag(1)

    }.frame(height: g.size.height)
}

1
谢谢!很蠢需要这样的解决方法,但至少现在有了一些东西。 - TheNeil
希望有一种简单的方法可以将其放入BrowserView对象本身中,但我没有看到这样的方法,因为NSViewRepresentable不使用任意的View返回类型。 - TheNeil
1
值得注意的是,当满足以下两个条件时,通常会出现此问题:
  • Web视图不是垂直方向上的第一个视图
  • 顶部容器视图大小是基于子视图布局和所需大小计算的
至少在下一次SwiftUI更新之前,我们有一个解决方法 :)
- Ahmed
嗨,请问这段代码应该放在哪里才能让它正常工作? - esaruoho
@esaruoho,请使用您的WKWebView NSViewRepresentable实例替换以下代码: `BrowserView().tabItem { Text("Browser") }` - Ahmed

3

webView.edgesIgnoringSafeArea(.all) 似乎可以解决这个问题。


1

@Ahmed使用GeometryReader的解决方案非常优雅,但我找到了一个更短的变通方法。

struct BrowserView: View {
// ...
    var body: some View {
        // ...
        browser.padding(-1.0)
        // ...
    }
}

似乎默认的填充值为0导致了这个问题。所以我将其设置为-1。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接