如何使用状态机状态结构体作为SwiftUI TabView的选中项?
解决SwiftUI TabView与状态机状态关联的问题
要让状态结构体作为TabView的标签索引,需要解决存在类型的Hashable/Equatable合规性以及SwiftUI状态观察两个核心问题,具体修改步骤如下:
1. 让状态协议遵守Hashable与Equatable
首先更新ContextState类型别名,让它继承Hashable和Equatable协议,同时为协议添加默认的比较和哈希实现——因为状态机中我们只关心状态的类型,不关心实例的存储属性:
// 保持原Events和Activities协议不变 protocol Events { mutating func gotoState1(_: Context) -> Transition? mutating func gotoState2(_: Context) -> Transition? } extension Events { mutating func gotoState1(_: Context) -> Transition? { return nil } mutating func gotoState2(_: Context) -> Transition? { return nil } } protocol Activities { func enter( _ : Context ) func exit( _ : Context ) } extension Activities { func enter( _ : Context ) {} func exit( _ : Context ) {} } // 更新类型别名,添加Hashable和Equatable约束 typealias ContextState = Events & Activities & Hashable & Equatable // 为ContextState提供默认的Equatable和Hashable实现 extension ContextState { static func == (lhs: Self, rhs: Self) -> Bool { // 相同类型的状态实例视为相等 type(of: lhs) == type(of: rhs) } func hash(into hasher: inout Hasher) { // 用状态类型的唯一标识作为哈希值 hasher.combine(ObjectIdentifier(type(of: self))) } }
2. 修正状态结构体的参数类型
原代码中状态的enter方法参数是GoProRemote,应改为Context(状态机的上下文类型),同时因为我们已经通过ContextState继承了Hashable/Equatable,结构体无需额外实现(无存储属性时自动合成):
struct State1: ContextState { func enter( _ context: Context ) { // TODO: 执行进入State1的操作 } mutating func gotoState2(_ context: Context) -> Transition? { return Transition(targetState: State2()) } } struct State2: ContextState { func enter( _ context: Context ) { // TODO: 执行进入State2的操作 } mutating func gotoState1(_ context: Context) -> Transition? { return Transition(targetState: State1()) } }
3. 将Context改为ObservableObject
原Context是struct类型,无法让SwiftUI视图观察状态变化,改为ObservableObject并使用@Published包装状态:
class Context: ObservableObject { @Published var state: any ContextState = State1() /// 执行状态转换逻辑 func perform( transition : Transition? ) { guard let transition = transition else { return } state.exit( self ) transition.effect?( self ) state = transition.targetState state.enter( self ) } } // 保持Transition结构体不变 struct Transition { var targetState: any ContextState var effect: Effect? = nil typealias Effect = ( Context ) -> () }
4. 调整TabView的标签与绑定
在SMView中,使用@ObservedObject观察Context,同时给Tab的tag明确指定类型为any ContextState,确保与selection的绑定类型匹配:
struct SMView: View { @ObservedObject var context: Context var body: some View { TabView(selection: $context.state) { Text("State 1") .tag(State1() as any ContextState) Text("State 2") .tag(State2() as any ContextState) } .onChange(of: context.state) { newState in print("进入状态:\(type(of: newState))") } } }
关键说明
- 状态的相等性基于类型而非实例:因为状态机中每个状态类型代表唯一的状态,即使创建新实例也视为同一个状态。
- 使用
ObservableObject+@Published确保状态变化能被SwiftUI视图捕获,自动更新TabView的选中项。 - 明确
tag的类型是为了避免Swift类型推断错误,确保与selection的Binding<any ContextState>匹配。
内容的提问来源于stack exchange,提问作者Martin Kopecký
相关产品推荐
相关产品推荐

