SwiftUI中UISheetPresentationController内容无法更新问题求助
SwiftUI adaptiveSheet 内容不更新问题修复
问题描述
实现了SwiftUI的adaptiveSheet扩展以封装UISheetPresentationController,测试时切换绑定的isOn状态,控制台打印语句能正确对应状态分支,但Sheet的UI始终不更新(例如颜色一直保持初始的红色,无法切换为绿色)。
问题根源
当前实现中,Sheet的内容视图仅在CustomSheetViewController初始化时被创建一次,后续状态变化时,既没有重新生成新的内容视图,也没有更新已展示的UIHostingController的根视图,导致UI无法响应状态变化。
修复方案
- 将
CustomSheetViewController中的静态content改为存储内容构建闭包,确保状态变化时能重新生成视图。 - 在
CustomSheet_UI的updateUIViewController方法中,当状态变化且Sheet已展示时,更新UIHostingController的根视图。 - 调整
AdaptiveSheet和CustomSheet_UI的内容传递方式,确保闭包能捕获最新状态。
修改后的完整代码
修改后的View扩展及Modifier
import SwiftUI @available(iOS 15.0, macCatalyst 15.0,*) extension View { func adaptiveSheet<T: View>(isPresented: Binding<Bool>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool = false, disableSwipeToDismiss: Bool = false, preferredCornerRadius: CGFloat? = nil, @ViewBuilder content: @escaping () -> T)-> some View { return modifier(AdaptiveSheet<T>(isPresented: isPresented, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss, preferredCornerRadius: preferredCornerRadius, sheetContent: content)) } } @available(iOS 15.0, macCatalyst 15.0,*) struct AdaptiveSheet<T: View>: ViewModifier { @Binding var isPresented: Bool let detents : [UISheetPresentationController.Detent] let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? let prefersScrollingExpandsWhenScrolledToEdge: Bool let prefersEdgeAttachedInCompactHeight: Bool let prefersGrabberVisible: Bool let disableSwipeToDismiss: Bool let preferredCornerRadius: CGFloat? let sheetContent: () -> T // 修改为闭包,而非静态View func body(content: Content) -> some View { content.overlay( CustomSheet_UI(isPresented: $isPresented, detents: detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss, preferredCornerRadius: preferredCornerRadius, content: sheetContent) .frame(width: 0, height: 0) ) .onChange(of: isPresented) { _ in } } }
修改后的UIViewControllerRepresentable及相关类
@available(iOS 15.0, macCatalyst 15.0,*) struct CustomSheet_UI<T: View>: UIViewControllerRepresentable { @Binding var isPresented: Bool var detents : [UISheetPresentationController.Detent] = [.medium(), .large()] var largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium var prefersScrollingExpandsWhenScrolledToEdge: Bool = false var prefersEdgeAttachedInCompactHeight: Bool = true var prefersGrabberVisible: Bool = false var disableSwipeToDismiss: Bool = false var preferredCornerRadius: CGFloat? let content: () -> T // 保持闭包传递 func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIViewController(context: Context) -> CustomSheetViewController<T> { CustomSheetViewController( coordinator: context.coordinator, detents: detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss, preferredCornerRadius: preferredCornerRadius, content: content ) } func updateUIViewController(_ uiViewController: CustomSheetViewController<T>, context: Context) { // 更新内容闭包,确保捕获最新状态 uiViewController.content = content // 如果Sheet已展示,更新根视图 if isPresented, let hostingVC = uiViewController.presentedViewController as? UIHostingController<T> { hostingVC.rootView = content() } // 处理展示/隐藏逻辑 if isPresented { uiViewController.presentModalView() } else { uiViewController.dismissModalView() } } class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate { var parent: CustomSheet_UI init(_ parent: CustomSheet_UI) { self.parent = parent } func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { if parent.isPresented { parent.isPresented = false } } } } @available(iOS 15.0, macCatalyst 15.0,*) class CustomSheetViewController<Content: View>: UIViewController { var content: () -> Content // 改为存储闭包 var coordinator: CustomSheet_UI<Content>.Coordinator let detents : [UISheetPresentationController.Detent] let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? let prefersScrollingExpandsWhenScrolledToEdge: Bool let prefersEdgeAttachedInCompactHeight: Bool let prefersGrabberVisible: Bool let disableSwipeToDismiss: Bool let preferredCornerRadius: CGFloat? private var isLandscape: Bool = UIDevice.current.orientation.isLandscape init( coordinator: CustomSheet_UI<Content>.Coordinator, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool, disableSwipeToDismiss: Bool, preferredCornerRadius: CGFloat?, content: @escaping () -> Content ) { self.content = content self.coordinator = coordinator self.detents = detents self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge self.prefersGrabberVisible = prefersGrabberVisible self.disableSwipeToDismiss = disableSwipeToDismiss self.preferredCornerRadius = preferredCornerRadius super.init(nibName: nil, bundle: .main) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func dismissModalView(){ dismiss(animated: true, completion: nil) } func presentModalView(){ guard presentedViewController == nil else { return } let hostingController = UIHostingController(rootView: content()) hostingController.view.backgroundColor = nil hostingController.modalPresentationStyle = .popover hostingController.presentationController?.delegate = coordinator as UIAdaptivePresentationControllerDelegate hostingController.modalTransitionStyle = .coverVertical hostingController.isModalInPresentation = disableSwipeToDismiss if let hostPopover = hostingController.popoverPresentationController { hostPopover.sourceView = super.view let sheet = hostPopover.adaptiveSheetPresentationController sheet.detents = (isLandscape ? [.large()] : detents) sheet.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier sheet.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge sheet.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true sheet.prefersGrabberVisible = prefersGrabberVisible sheet.preferredCornerRadius = preferredCornerRadius } present(hostingController, animated: true, completion: nil) } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { isLandscape = UIDevice.current.orientation.isLandscape self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = isLandscape ? [.large()] : detents super.viewWillTransition(to: size, with: coordinator) } }
测试代码(无需修改)
import SwiftUI struct TestView: View { @State var isOn = false var body: some View { VStack { Toggle("Is on?", isOn: $isOn) Spacer() } .padding() .adaptiveSheet(isPresented: self.$isOn) { print("sheet => rendered with value:", isOn) if isOn { print("sheet => inside isOn = true condition:", isOn) Rectangle() .fill(Color.green) } else { print("sheet => inside isOn = false condition:", isOn) Rectangle() .fill(Color.red) } } } } struct TestView_Previews: PreviewProvider { static var previews: some View { TestView() } }
关键修改点说明
- 将
AdaptiveSheet、CustomSheet_UI和CustomSheetViewController中的静态content改为存储内容构建闭包() -> T,确保每次状态变化时能生成最新的视图。 - 在
CustomSheet_UI的updateUIViewController方法中,更新CustomSheetViewController的内容闭包,并在Sheet已展示时直接更新UIHostingController的rootView。 - 移除了不必要的打印语句,保持代码简洁。
内容的提问来源于stack exchange,提问作者Dot Freelancer
相关产品推荐
相关产品推荐

