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

iOS16中嵌入ViewController的SwiftUI Sheet无法随VC同步消失的解决方法

实现iOS17同款Sheet随ViewController同步消失效果(适配iOS16)

问题现象

  • iOS17 预期效果:返回SecondVC时,Sheet会随ViewController一起同步消失。
    iOS17效果

  • iOS16 问题表现:Sheet会停留在屏幕上,待ViewController的消失动画完全完成后,才会延迟消失。
    iOS16问题

现有代码

包含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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:05:55