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

SwiftUI中UISheetPresentationController内容无法更新问题求助

SwiftUI adaptiveSheet 内容不更新问题修复

问题描述

实现了SwiftUI的adaptiveSheet扩展以封装UISheetPresentationController,测试时切换绑定的isOn状态,控制台打印语句能正确对应状态分支,但Sheet的UI始终不更新(例如颜色一直保持初始的红色,无法切换为绿色)。

问题根源

当前实现中,Sheet的内容视图仅在CustomSheetViewController初始化时被创建一次,后续状态变化时,既没有重新生成新的内容视图,也没有更新已展示的UIHostingController的根视图,导致UI无法响应状态变化。

修复方案

  1. 将CustomSheetViewController中的静态content改为存储内容构建闭包,确保状态变化时能重新生成视图。
  2. 在CustomSheet_UI的updateUIViewController方法中,当状态变化且Sheet已展示时,更新UIHostingController的根视图。
  3. 调整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()
    }
}

关键修改点说明

  1. 将AdaptiveSheet、CustomSheet_UI和CustomSheetViewController中的静态content改为存储内容构建闭包() -> T,确保每次状态变化时能生成最新的视图。
  2. 在CustomSheet_UI的updateUIViewController方法中,更新CustomSheetViewController的内容闭包,并在Sheet已展示时直接更新UIHostingController的rootView。
  3. 移除了不必要的打印语句,保持代码简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:05:22