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

SwiftUI实现模态Sheet半屏转全屏的动画过渡方案咨询

SwiftUI实现带动画的Sheet从半屏过渡到全屏

问题背景

需要实现Sheet初始以半屏(medium)状态呈现,待加载完成后平滑过渡到全屏(large)。UIKit中已实现该效果,但SwiftUI尝试的代码切换时无动画,需解决此问题,同时询问是否可将SwiftUI视图包装在UIViewController中,由SwiftUI处理呈现逻辑。

UIKit实现代码

let vc = UIViewController()
vc.view.backgroundColor = .red
vc.sheetPresentationController?.detents = [.medium(), .large()]
self.present(vc, animated: true)

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
    vc.sheetPresentationController?.animateChanges {
        vc.sheetPresentationController?.selectedDetentIdentifier = .large
    }
}

你尝试的SwiftUI代码(无动画)

struct ContentView: View {
    @State var showSheet = false
    
    var body: some View {
        Button {
            showSheet = true
        } label: {
            Text("Show-Sheet")
        }
        .sheet(isPresented: $showSheet, content: {
            SheetView()
        })
    }
}

struct SheetView: View {
    @State var showFullSheet = false
    
    var body: some View {
        Button {
            withAnimation {
                showFullSheet.toggle()
            }
        } label: {
            Text("Toggle")
        }
        .presentationDetents(showFullSheet ? [.large] : [.medium, .large])
    }
}

解决方案

方案1:SwiftUI原生实现(带动画)

核心是使用presentationDetents(_:selection:)绑定选中的detent,而非替换detents数组,这样配合withAnimation就能触发平滑过渡。

struct ContentView: View {
    @State var showSheet = false
    
    var body: some View {
        Button {
            showSheet = true
        } label: {
            Text("Show-Sheet")
        }
        .sheet(isPresented: $showSheet) {
            SheetView()
        }
    }
}

struct SheetView: View {
    // 初始选中medium detent
    @State private var selectedDetent: PresentationDetent = .medium
    
    var body: some View {
        VStack {
            Text("半屏加载中...")
            Button("切换全屏") {
                withAnimation {
                    selectedDetent = .large
                }
            }
        }
        // 设置支持的detents,并绑定选中状态
        .presentationDetents([.medium, .large], selection: $selectedDetent)
        .onAppear {
            // 模拟加载延迟,自动切换到全屏
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                withAnimation {
                    selectedDetent = .large
                }
            }
        }
    }
}

说明:通过绑定selectedDetent,修改其值时用withAnimation包裹,就能让Sheet平滑从半屏过渡到全屏,完全符合需求。

方案2:将SwiftUI视图包装在UIViewController中

这种方式可行,可直接复用UIKit的Sheet过渡逻辑,同时由SwiftUI控制呈现。

struct ContentView: View {
    @State var showSheet = false
    
    var body: some View {
        Button {
            showSheet = true
        } label: {
            Text("Show-Sheet")
        }
        .sheet(isPresented: $showSheet) {
            UIKitSheetWrapper(content: SheetView())
        }
    }
}

// 承载SwiftUI视图的UIViewController
class SheetHostVC: UIViewController {
    private let content: AnyView
    
    init<Content: View>(content: Content) {
        self.content = AnyView(content)
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 将SwiftUI视图添加为子视图
        let hostingVC = UIHostingController(rootView: content)
        addChild(hostingVC)
        view.addSubview(hostingVC.view)
        hostingVC.view.frame = view.bounds
        hostingVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        hostingVC.didMove(toParent: self)
        
        // 配置Sheet的detents
        sheetPresentationController?.detents = [.medium(), .large()]
        
        // 模拟加载延迟,执行UIKit的动画过渡
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.sheetPresentationController?.animateChanges {
                self.sheetPresentationController?.selectedDetentIdentifier = .large
            }
        }
    }
}

// UIViewControllerRepresentable桥接SwiftUI与UIKit
struct UIKitSheetWrapper<Content: View>: UIViewControllerRepresentable {
    let content: Content
    
    func makeUIViewController(context: Context) -> SheetHostVC {
        SheetHostVC(content: content)
    }
    
    func updateUIViewController(_ uiViewController: SheetHostVC, context: Context) {}
}

// 原SwiftUI内容视图
struct SheetView: View {
    var body: some View {
        Text("我是SwiftUI内容")
            .font(.title)
            .background(.red)
    }
}

说明:通过UIViewControllerRepresentable将SwiftUI视图包装进UIViewController,然后在UIViewController中使用原生的sheetPresentationController动画逻辑,既保留了UIKit的过渡效果,又由SwiftUI的.sheet控制显示隐藏。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 03:10:25