iOS16中嵌入ViewController的SwiftUI Sheet无法随VC同步消失的解决方法
实现iOS17同款Sheet随ViewController同步消失效果(适配iOS16)
问题现象
iOS17 预期效果:返回
SecondVC时,Sheet会随ViewController一起同步消失。
iOS16 问题表现:Sheet会停留在屏幕上,待ViewController的消失动画完全完成后,才会延迟消失。

现有代码
包含Sheet的SecondVC及SwiftUI视图代码
import SwiftUI import UIKit class SecondVC: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.installSecondView() } private func installSecondView() { let demoView = SecondView() let hostingVC = UIHostingController(rootView: demoView) self.addChild(hostingVC) if let hostingVCview = hostingVC.view { self.view.addSubview(hostingVCview) self.view.backgroundColor = .purple hostingVCview.translatesAutoresizingMaskIntoConstraints = false hostingVCview.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true hostingVCview.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true hostingVCview.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true hostingVCview.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true hostingVC.didMove(toParent: self) } } } struct SecondView: View { @State private var isShiftSheetPresented = true @State private var selectedDetent: PresentationDetent = .fraction(0.4) var body: some View { VStack { Text("Second View") } .sheet(isPresented: self.$isShiftSheetPresented, content: { Text("Sheet content") .presentationDetents([.fraction(0.2), .fraction(0.3), .fraction(0.4), .large], selection: self.$selectedDetent) .presentationDragIndicator(.visible) .backport.interactiveDismissDisabled() .backport.undimSheetBackground() .backport.presentationCornerRadius(32) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.orange) } }
跳转至SecondVC的DemoVC及SwiftUI视图代码
import UIKit import SwiftUI class DemoVC: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.installDemoView() } private func installDemoView() { let rootView = DemoView(pushButtonAction: self.pushBtnAction) let hostingVC = UIHostingController(rootView: rootView) self.addChild(hostingVC) if let hostingView = hostingVC.view { self.view.addSubview(hostingView) self.view.backgroundColor = .purple hostingView.translatesAutoresizingMaskIntoConstraints = false hostingView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true hostingView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true hostingView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true hostingView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true hostingVC.didMove(toParent: self) } } private func pushBtnAction() { DispatchQueue.main.async { self.navigationController?.pushViewController(SecondVC(), animated: true) } } } struct DemoView: View { private let pushButtonAction: () -> Void init(pushButtonAction: @escaping () -> Void) { self.pushButtonAction = pushButtonAction } var body: some View { Button { self.pushButtonAction() } label: { Text("Push a VC with a sheet") } } }
解决方案
要在iOS16实现iOS17的同步消失效果,核心是在ViewController开始消失时,同步触发Sheet的关闭动画,具体步骤如下:
1. 提取Sheet状态到ObservableObject
创建独立的状态管理类,让ViewController可以直接控制Sheet的显示状态:
class SheetState: ObservableObject { @Published var isSheetPresented = true @Published var selectedDetent: PresentationDetent = .fraction(0.4) }
2. 修改SecondVC,持有状态并监听消失事件
更新SecondVC,初始化状态实例并传递给SwiftUI视图,同时在ViewController开始消失时关闭Sheet:
class SecondVC: UIViewController { private let sheetState = SheetState() private var hostingVC: UIHostingController<SecondView>? override func viewDidLoad() { super.viewDidLoad() installSecondView() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 仅在返回上一级VC时触发Sheet关闭 guard isMovingFromParent, let coordinator = transitionCoordinator else { return } // 同步VC消失动画与Sheet关闭动画 coordinator.animate(alongsideTransition: { _ in self.sheetState.isSheetPresented = false }, completion: nil) } private func installSecondView() { let demoView = SecondView(state: sheetState) hostingVC = UIHostingController(rootView: demoView) guard let hostingVC = hostingVC, let hostingVCview = hostingVC.view else { return } addChild(hostingVC) view.addSubview(hostingVCview) view.backgroundColor = .purple hostingVCview.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ hostingVCview.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), hostingVCview.leadingAnchor.constraint(equalTo: view.leadingAnchor), hostingVCview.trailingAnchor.constraint(equalTo: view.trailingAnchor), hostingVCview.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) hostingVC.didMove(toParent: self) } }
3. 更新SecondView,使用共享状态
修改SecondView,通过ObservedObject绑定共享状态:
struct SecondView: View { @ObservedObject var state: SheetState var body: some View { VStack { Text("Second View") } .sheet(isPresented: $state.isSheetPresented, content: { Text("Sheet content") .presentationDetents([.fraction(0.2), .fraction(0.3), .fraction(0.4), .large], selection: $state.selectedDetent) .presentationDragIndicator(.visible) .backport.interactiveDismissDisabled() .backport.undimSheetBackground() .backport.presentationCornerRadius(32) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.orange) } }
原理说明
通过transitionCoordinator的animate(alongsideTransition:)方法,将Sheet的关闭动画绑定到ViewController的消失动画流程中,确保两者同步执行,从而实现iOS17同款的同步消失效果。
内容的提问来源于stack exchange,提问作者Adriano
相关产品推荐
相关产品推荐

