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

SwiftUI中UIViewRepresentable随内容动态调整大小问题

解决UIViewRepresentable嵌套UIHostingController后无法动态调整尺寸的问题

你的问题核心是:SwiftUI无法感知到UIHostingController内部视图的尺寸变化,导致sizeThatFits只执行一次,外层边框无法跟随内容调整。以下是具体解决方法:

关键思路

通过SwiftUI的PreferenceKey捕获内部视图的高度变化,主动触发UIHostingController视图的尺寸更新通知,让SwiftUI重新计算sizeThatFits。

步骤1:定义偏好键捕获高度

创建一个PreferenceKey,用来传递内部视图的高度值:

struct ContentHeightPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

步骤2:在ExpandingView中注入高度监听

修改你的ExpandingView,用GeometryReader捕获自身高度,并通过偏好键传递出去:

struct ExpandingView: View {
    @State private var isExpanded = false
    
    var body: some View {
        VStack {
            Button(isExpanded ? "折叠" : "展开") {
                isExpanded.toggle()
            }
            Color.blue.frame(height: isExpanded ? 500 : 100) // 替换为你的内容视图
        }
        .background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: ContentHeightPreferenceKey.self, value: proxy.size.height)
            }
        )
    }
}

步骤3:修改WrapperHostingControllerRepresentable

更新你的包装类,监听偏好变化并主动触发尺寸更新,同时让sizeThatFits返回正确的内部尺寸:

struct WrapperHostingControllerRepresentable: UIViewRepresentable {
    typealias UIViewType = UIView
    
    @State private var contentHeight: CGFloat = 0
    
    func makeUIView(context: Context) -> UIView {
        // 初始化HostingController并监听偏好变化
        let rootView = ExpandingView()
            .onPreferenceChange(ContentHeightPreferenceKey.self) { newHeight in
                contentHeight = newHeight
                // 通知视图尺寸失效,触发SwiftUI重新计算
                context.coordinator.hostingView?.invalidateIntrinsicContentSize()
            }
        
        let hostingController = UIHostingController(rootView: rootView)
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        
        // 创建带红色边框的容器视图
        let containerView = UIView()
        containerView.addSubview(hostingController.view)
        containerView.layer.borderColor = UIColor.red.cgColor
        containerView.layer.borderWidth = 2
        
        // 绑定约束,让HostingView填满容器
        NSLayoutConstraint.activate([
            hostingController.view.topAnchor.constraint(equalTo: containerView.topAnchor),
            hostingController.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
        ])
        
        context.coordinator.hostingView = hostingController.view
        return containerView
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // 更新HostingView的固有尺寸,确保SwiftUI能拿到正确高度
        context.coordinator.hostingView?.intrinsicContentSize = CGSize(
            width: UIView.noIntrinsicMetric,
            height: contentHeight
        )
    }
    
    func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIView, context: Context) -> CGSize? {
        // 返回容器的固有尺寸,让SwiftUI以此调整布局
        return uiView.intrinsicContentSize
    }
    
    // 用Coordinator保存HostingView引用
    class Coordinator {
        var hostingView: UIView?
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
}

为什么这样能解决问题?

  • 原来的sizeThatFits只执行一次,是因为SwiftUI没有收到任何尺寸变化的通知,认为视图尺寸不需要更新。
  • 通过PreferenceKey捕获内部视图高度变化后,主动调用invalidateIntrinsicContentSize(),告诉UIKit视图尺寸已变,进而触发SwiftUI重新调用sizeThatFits。
  • 重写sizeThatFits返回正确的固有尺寸,确保SwiftUI用最新的内容高度来调整外层容器的大小。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:42:45