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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 16:17:42