SwiftUI:如何通过Published属性实现Sheet的显示控制
解决ObservableObject中@Published变量与Sheet的Binding参数适配问题
问题核心
你在遵循ObservableObject的ViewModel中使用@Published private(set) var isVisable = false封装状态,希望通过它控制Sheet的显示,但sheet(isPresented:)参数要求Binding<Bool>类型:
- 直接使用
model.$isVisable会得到Published<Bool>.Publisher而非Binding,触发编译错误; - 若尝试使用
$model.isVisable,又会因isVisable的private(set)修饰符导致外部无法修改状态,同样报错; - 你希望避免在View中添加
@State变量并通过回调同步,保持架构清晰。
解决方案1:在ViewModel中提供自定义Binding(推荐)
在ViewModel内部添加一个计算属性,封装对应isVisable的Binding<Bool>,既保持状态的封装性,又能满足View的Binding需求:
修改后的ViewModel代码:
final class ContentViewModel: ObservableObject { @Published private(set) var isVisable = false func toggleVisability() { isVisable.toggle() } // 供View调用的Binding属性 var isVisableBinding: Binding<Bool> { Binding( get: { self.isVisable }, set: { newValue in guard newValue != self.isVisable else { return } self.isVisable = newValue // 可在此添加状态变更后的额外逻辑(如日志、事件上报等) } ) } }
对应的ContentView代码:
struct ContentView: View { @StateObject var model = ContentViewModel() var body: some View { VStack { Button("Show") { model.toggleVisability() } // 使用ViewModel提供的Binding控制Sheet .sheet(isPresented: model.isVisableBinding) { Text("Placeholder") } } } }
关键说明:
- 当用户手动关闭Sheet时,
isPresented会自动将Binding的value设为false,此时Binding的set块会同步更新ViewModel中的isVisable,无需额外添加onDismiss回调(原代码中onDismiss调用toggleVisability会导致状态反转错误); - 该方案完全遵循MVVM架构,View仅负责展示和触发ViewModel的方法,状态逻辑全部封装在ViewModel内,无架构污染。
解决方案2:移除封装(不推荐)
若暂时忽略封装性,可移除isVisable的private(set)修饰符,直接使用@Published var isVisable = false,然后在View中用$model.isVisable作为Binding参数:
// 修改后的ViewModel final class ContentViewModel: ObservableObject { @Published var isVisable = false // 移除private(set) func toggleVisability() { isVisable.toggle() } } // View中使用 .sheet(isPresented: $model.isVisable) { Text("Placeholder") }
但此方式破坏了ViewModel的封装性,外部可直接修改状态,不符合架构设计原则,仅作为临时变通方案。
内容的提问来源于stack exchange,提问作者Kirill Rud
相关产品推荐
相关产品推荐

