SwiftUI:如何从多个视图复用调用同一个Sheet?
多视图复用SwiftUI Sheet的优化方案
你的原始代码如下,当前需要在多个视图中打开这个Sheet,直接复制代码违反DRY原则,除了用ObservableObject作为environmentObject传递状态,还有以下两种更实用的方案:
.sheet(item: $logMyPractice, content: { item in AssesmentRecorderView( assessment: DrillModel( questions: [ .init(prompt: "Question 1", resultValue: .integer), ] ), completion: { date, answers in print("response", date, answers) // 自定义逻辑,保存到数据库 }) .presentationDetents([.fraction(0.85), .large ]) }
方案一:封装为ViewModifier(推荐,灵活度高)
把Sheet的配置、内容和回调逻辑封装成自定义ViewModifier,任何视图只需调用对应的扩展方法就能打开Sheet,无需重复编写Sheet代码。
实现代码
// 假设你有对应的Answer类型,可根据实际情况调整 typealias Answer = [String: Any] struct AssessmentSheetModifier: ViewModifier { @Binding var isPresented: Bool let assessment: DrillModel let completion: (Date, Answer) -> Void func body(content: Content) -> some View { content .sheet(isPresented: $isPresented) { AssesmentRecorderView( assessment: assessment, completion: { date, answers in completion(date, answers) isPresented = false // 回调后自动关闭Sheet } ) .presentationDetents([.fraction(0.85), .large]) } } } // 扩展View,简化调用 extension View { func assessmentSheet( isPresented: Binding<Bool>, assessment: DrillModel, completion: @escaping (Date, Answer) -> Void ) -> some View { self.modifier( AssessmentSheetModifier( isPresented: isPresented, assessment: assessment, completion: completion ) ) } }
使用方式
在任意视图中,只需声明一个状态变量控制显示,调用assessmentSheet方法即可:
struct SomeChildView: View { @State private var showAssessmentSheet = false // 可根据需求传入自定义的DrillModel private let customAssessment = DrillModel(questions: [.init(prompt: "Question 1", resultValue: .integer)]) var body: some View { Button("打开评估Sheet") { showAssessmentSheet = true } .assessmentSheet( isPresented: $showAssessmentSheet, assessment: customAssessment ) { date, answers in print("response", date, answers) // 执行保存到数据库等自定义逻辑 } } }
方案二:全局Sheet管理类(适合统一控制)
如果需要从多个视图打开同一份Sheet,且希望集中管理Sheet的显示状态,可以创建一个全局的ObservableObject类,通过environmentObject注入到所有视图中。
实现代码
@MainActor class SheetManager: ObservableObject { @Published var showAssessmentSheet = false private var completionHandler: ((Date, Answer) -> Void)? var currentAssessment: DrillModel? // 对外暴露的打开方法,传入评估模型和回调 func presentAssessment(_ assessment: DrillModel, completion: @escaping (Date, Answer) -> Void) { currentAssessment = assessment completionHandler = completion showAssessmentSheet = true } // 内部调用的完成方法,触发回调并重置状态 func handleCompletion(date: Date, answers: Answer) { completionHandler?(date, answers) resetState() } private func resetState() { showAssessmentSheet = false completionHandler = nil currentAssessment = nil } }
注入全局状态
在App入口注入SheetManager:
@main struct YourApp: App { @StateObject private var sheetManager = SheetManager() var body: some Scene { WindowGroup { MainView() .environmentObject(sheetManager) } } }
在根视图添加Sheet
在主视图或根视图中添加Sheet的定义:
struct MainView: View { @EnvironmentObject private var sheetManager: SheetManager var body: some View { // 主视图内容 .sheet(isPresented: $sheetManager.showAssessmentSheet) { if let assessment = sheetManager.currentAssessment { AssesmentRecorderView( assessment: assessment, completion: sheetManager.handleCompletion(date:answers:) ) .presentationDetents([.fraction(0.85), .large]) } } } }
在子视图中调用
任意子视图只需通过@EnvironmentObject获取SheetManager,调用方法即可打开Sheet:
struct AnotherChildView: View { @EnvironmentObject private var sheetManager: SheetManager private let customAssessment = DrillModel(questions: [.init(prompt: "Question 1", resultValue: .integer)]) var body: some View { Button("打开评估Sheet") { sheetManager.presentAssessment(customAssessment) { date, answers in print("response", date, answers) // 执行保存到数据库等自定义逻辑 } } } }
方案对比
- ViewModifier方案:状态分散在各个视图,每个视图独立控制Sheet的显示,适合需要不同配置(如不同的
DrillModel)的场景,灵活度更高。 - 全局管理类方案:状态集中管理,适合所有视图打开完全相同Sheet的场景,避免重复定义Sheet内容。
内容的提问来源于stack exchange,提问作者breq
相关产品推荐
相关产品推荐

