Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: WKWebView Cutting Off Page Content on macOS (NSViewRepresentable)

I have a basic NSViewRepresentable implementation of WKWebView, for use with SwiftUI apps on macOS. The UIViewRepresentable equivalent works fine on iOS, but on macOS (natively, not Catalyst), the top content is always cut off.

The amount lost always equals the size of parent views (such as the tab view) and their padding, which indicates that the web view keeps scaling its content to the window size, rather than the view size.

For example, this page:

Cut Off Web Content

...should be as follows (as shown in Chrome). The entire navigation bar has been cropped out (though the sides appear not to be affected).

Chrome Version Rendering Correctly

Any suggestions on how to fix this? Interestingly, if I switch back & forth between tabs, the content shows correctly for ~1 second, then resizes the content so it's cut off again. This makes me think something's required in the updateNSView method, but I'm not sure what.

Seems to be a similar issue to the one discussed here, but that's for IB-based apps, and I can't see a way to apply it for SwiftUI.

The code used is as follows. Note: The web view is kept as a property so it can be referenced by other methods (such as triggering page load, refresh, go back, etc.)

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

Example usage:

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()
    }
}
like image 738
TheNeil Avatar asked Dec 15 '19 20:12

TheNeil


3 Answers

webView.edgesIgnoringSafeArea(.all) seems to work around this problem.

like image 185
marcprux Avatar answered Oct 06 '22 16:10

marcprux


The solution by @Ahmed with GeometryReader is quite elegant, yet I've found a shorter workaround.

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

Seems that the default padding, which equals to 0, causes this issue. So I just set it to -1.

like image 38
Dean Avatar answered Oct 06 '22 16:10

Dean


I had the exact same issue with WKWebView in MacOS app using SwiftUI.

The solution that worked for me is to use GeometryReader to get the exact height, and put the web view inside a scrollview (I believe it has something to do with the layout priority calculation, but couldn't get to the core of it yet).

Here is a snippet of what worked for me, maybe it will work with you as well

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

    }.frame(height: g.size.height)
}
like image 38
Ahmed Avatar answered Oct 06 '22 17:10

Ahmed