You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中NSScrollView内嵌WKWebView全屏无法正常自适应尺寸

问题原因

核心是两处约束设置违反了系统控件的布局规则:

  • NSScrollView的documentView不需要手动添加和滚动视图关联的居中约束,滚动范围完全由documentView自身尺寸决定。之前只给作为documentView的NSHostingView加了居中约束、关闭了自动掩码转换,既没有定义documentView的合法尺寸,也不符合NSScrollView的布局逻辑,WKWebView触发全屏修改父视图尺寸时会直接触发约束冲突,导致尺寸适配失败。
  • WKWebView自带的全屏逻辑依赖系统自动转换的autoresizing约束,手动强制设置translatesAutoresizingMaskIntoConstraints = false会打断这部分逻辑,导致全屏时无法正确撑满窗口。
修复方案
  1. 移除NSHostingView上错误的居中约束,改用重写滚动视图布局方法的方式调整documentView尺寸与位置,既保证内容小于滚动区域时自动居中,又完全符合NSScrollView的滚动规则,不干扰WKWebView全屏时的尺寸调整逻辑。
  2. 移除WKWebView子类中强制关闭自动掩码转换的代码,保留系统默认的布局逻辑,保证全屏模式下尺寸自适应正常。
  3. 补充SwiftUI内容更新时的尺寸同步逻辑,避免状态变化后布局错乱。
修改后完整代码
struct ContentView: View {
    var body: some View {
        VStack {
            ScrollViewX {
                HStack {
                    WebViewX()
                        .frame(width: 300, height: 300)
                }
            }
            .frame(width: 500, height : 500)
            
        }.frame(width: 700, height : 700)
    }
}

struct ScrollViewX<Content : View>: NSViewRepresentable {
    let content: Content

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    func makeNSView(context: Context) -> CustomScrollView {
        let scrollView = CustomScrollView(frame: .zero)
        let hostingView = NSHostingView(rootView: content)
        // 保留自动掩码转换,适配documentView原生布局逻辑
        hostingView.autoresizingMask = [.width, .height]
        scrollView.documentView = hostingView
        return scrollView
    }

    func updateNSView(_ nsView: CustomScrollView, context: Context) {
        // 同步SwiftUI内容更新,刷新滚动视图布局
        if let hostingView = nsView.documentView as? NSHostingView<Content> {
            hostingView.rootView = content
            nsView.reflectScrolledClipView(nsView.contentView)
        }
    }
}

class CustomScrollView: NSScrollView {
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        self.drawsBackground = false
        self.hasHorizontalScroller = true
        self.hasVerticalScroller = false
        self.autoresizesSubviews = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layout() {
        super.layout()
        guard let documentView = documentView else { return }
        let contentSize = documentView.fittingSize
        let scrollContentSize = contentView.bounds.size
        
        var documentFrame = documentView.frame
        // 保证documentView最小尺寸不小于滚动区域,避免滚动范围异常
        documentFrame.size.width = max(contentSize.width, scrollContentSize.width)
        documentFrame.size.height = max(contentSize.height, scrollContentSize.height)
        // 内容尺寸小于滚动区域时自动居中
        documentFrame.origin.x = contentSize.width < scrollContentSize.width ? (scrollContentSize.width - contentSize.width) / 2 : 0
        documentFrame.origin.y = contentSize.height < scrollContentSize.height ? (scrollContentSize.height - contentSize.height) / 2 : 0
        
        documentView.frame = documentFrame
    }
}

struct WebViewX: NSViewRepresentable {
    func makeNSView(context: Context) -> customWebView {
        let webView = customWebView(url: nil)
        return webView
    }
        
    func updateNSView(_ nsView: customWebView, context: Context) {
    }
}

class customWebView : WKWebView {
    convenience init(url : String?) {
        let config = WKWebViewConfiguration()
        
        config.limitsNavigationsToAppBoundDomains = false
        config.preferences.isTextInteractionEnabled = true
        config.preferences.setValue(true, forKey: "fullScreenEnabled")
        config.preferences.javaScriptCanOpenWindowsAutomatically = true
        config.defaultWebpagePreferences.allowsContentJavaScript = true
        self.init(frame: .zero, configuration : config)

        self.allowsBackForwardNavigationGestures = false
        self.allowsMagnification = true
        // 移除强制设置translatesAutoresizingMaskIntoConstraints = false的代码,保留系统默认全屏适配逻辑

        let req = URLRequest(url: URL(string: "https://www.youtube.com")!)
        self.load(req)
    }
    
    convenience required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

内容的提问来源于stack exchange,提问作者arthas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 06:18:21