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

如何使用状态机状态结构体作为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ý

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:45:58