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
相关产品推荐
相关产品推荐

