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

如何在SwiftUI中结合泛型与MVVM实现Sheet组件?

SwiftUI MVVM泛型Sheet存储问题解决方法

背景

我正在开发SwiftUI的Sheet处理系统,采用MVVM设计模式结合泛型时遇到了问题。我定义了一个包含泛型View的Sheet结构体,视图模型中使用@Published变量存储当前激活的Sheet,但无法存储不同视图类型的Sheet,出现以下编译错误:

错误1: 引用泛型类型'Sheet'需要在<...>中传入参数
错误2: 'nil'需要上下文类型


原代码

public struct Sheet<Content: View>: Identifiable {
    public let id = UUID()
    
    let content: Content
    
    public init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
}

public class SheetViewModel: ObservableObject {
    public static let shared = SheetViewModel()
    
    private init() {}
    
    @Published var sheet: Sheet?                                   // Error 1
    
    public func present<Content: View>(_ sheet: Sheet<Content>) {
        self.sheet = sheet
    }
    
    public func present<Content: View>(@ViewBuilder content: () -> Content) {
        self.sheet = CRSheet(content: content)
    }
    
    public func dismiss() {
        self.sheet = nil                                          // Error 2
    }
}

public struct SheetViewModifier: ViewModifier {
    @ObservedObject private var sheetVM = SheetViewModel.shared
    
    public func body(content: Content) -> some View {
        content
            .sheet(item: $sheetVM.sheet) { sheet in
                sheet.content
            }
    }
}

问题

  • 如何解决这些错误,尤其是在同一个@Published变量中存储不同泛型视图的Sheet并在视图修饰器中正常使用?
    我曾考虑将Sheet结构体中的View存储为any View,但不确定如何在视图修饰器中使用。

注: 此代码属于对外公开的包,泛型内容必须通过present(_)方法传入。


解决方案

核心思路是移除Sheet的泛型约束,将content类型改为any View(Swift 5.7+支持的存在类型),让Sheet结构体可以容纳任意类型的View,同时保持Identifiable协议实现以适配SwiftUI的sheet(item:)修饰器。

1. 修改Sheet结构体

去掉泛型参数,将content声明为any View:

public struct Sheet: Identifiable {
    public let id = UUID()
    let content: any View
    
    public init(@ViewBuilder content: () -> some View) {
        self.content = content()
    }
}

2. 修复SheetViewModel

现在可以直接声明@Published var sheet: Sheet?,无需泛型参数,两个编译错误直接解决:

public class SheetViewModel: ObservableObject {
    public static let shared = SheetViewModel()
    
    private init() {}
    
    @Published var sheet: Sheet?  // 错误1解决
    
    public func present(_ sheet: Sheet) {
        self.sheet = sheet
    }
    
    public func present(@ViewBuilder content: () -> some View) {
        self.sheet = Sheet(content: content)
    }
    
    public func dismiss() {
        self.sheet = nil  // 错误2解决
    }
}

3. 适配视图修饰器

由于content是any View,需要用AnyView包裹后才能在SwiftUI视图中渲染:

public struct SheetViewModifier: ViewModifier {
    @ObservedObject private var sheetVM = SheetViewModel.shared
    
    public func body(content: Content) -> some View {
        content
            .sheet(item: $sheetVM.sheet) { sheet in
                AnyView(sheet.content)
            }
    }
}

4. 便捷调用扩展(可选)

给View添加扩展,方便快速使用该修饰器:

extension View {
    public func sheetHandler() -> some View {
        self.modifier(SheetViewModifier())
    }
}

方案说明

  • 使用any View替代泛型,让Sheet结构体可以存储任意类型的View实例,同时保留类型安全。
  • Identifiable协议的实现确保sheet(item:)修饰器能正确识别Sheet的变化,触发展示/消失逻辑。
  • 对外的present方法依然支持通过ViewBuilder传入任意视图,完全符合包的对外访问要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 03:25:22