绑定到具有不同类型关联值的枚举的关联值

Binding to an associated value of an enum that has different types of associated values

的跟进问题。

如果:

enum Choice {
  case one(String)
  case two(String)
}

改为:

enum Choice {
  case one(String)
  case two(Bool)
}

在我看来,我正在打开枚举,然后将一个案例的关联值绑定到文本字段,而另一个绑定到切换按钮?在我需要绑定到 bool 的情况下, 现在对我不起作用。

我能想到的就是复制并粘贴 属性 并将所有 String 更改为 Bool 但是在我的实际代码中我有很多不同的类型所以方法是越来越傻了

任何想法将不胜感激,谢谢!

当前代码:

import SwiftUI

@main
struct MyApp: App {
    @StateObject private var model = Model()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(model)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject private var model: Model
    
    var body: some View {
        VStack {
            ScrollView {
                Text(model.jsonString)
            }
            
            List($model.data, id: \.name, children: \.children) { $node in
                HStack {
                    switch node.value {
                    case .none:
                        Spacer()
                    case .bool:
                        Toggle(node.name, isOn: $node.value.bool)
                    case let .int(value):
                        Stepper("\(value)", value: $node.value.int)
                    case let .float(value):
                        Text(value.description)
                    case let .string(value):
                        Text(value)
                    }
                }
            }
        }
    }
}

class Model: ObservableObject {
    @Published var data: [Node] = [
        Node(name: "My Bool", value: .bool(false)),
        Node(name: "My Int", value: .int(25)),
        Node(name: "My float", value: .float(3.123)),
        Node(name: "My parent node", value: .none, children: [
            Node(name: "Default name", value: .string("Untitled")),
            Node(name: "Fluid animations", value: .bool(true)),
            Node(name: "More children??", value: .none, children: [
                Node(name: "Hello", value: .string("There"))
            ])
        ])
    ]
    
    private let encoder = JSONEncoder()
    
    init() {
        encoder.outputFormatting = .prettyPrinted
    }
    
    var jsonString: String {
        String(data: try! encoder.encode(data), encoding: .utf8)!
    }
}

struct Node: Codable {
    var name: String
    var value: Value
    var children: [Node]? = nil
}

enum Value: Codable {
    case none
    case bool(Bool)
    case int(Int)
    case float(Float)
    case string(String)
    
    
    var bool: Bool {
        get {
            switch self {
            case let .bool(value): return value
            default: fatalError("Unexpected value \(self)")
            }
        }
        set {
            switch self {
            case .bool: self = .bool(newValue)
            default: fatalError("Unexpected value \(self)")
            }
        }
    }
    
    var int: Int {
        get {
            switch self {
            case let .int(value): return value
            default: fatalError("Unexpected value \(self)")
            }
        }
        set {
            switch self {
            case .int: self = .int(newValue)
            default: fatalError("Unexpected value \(self)")
            }
        }
    }
    
    var float: Float {
        get {
            switch self {
            case let .float(value): return value
            default: fatalError("Unexpected value \(self)")
            }
        }
        set {
            switch self {
            case .float: self = .float(newValue)
            default: fatalError("Unexpected value \(self)")
            }
        }
    }
    
    var string: String {
        get {
            switch self {
            case let .string(value): return value
            default: fatalError("Unexpected value \(self)")
            }
        }
        set {
            switch self {
            case .string: self = .string(newValue)
            default: fatalError("Unexpected value \(self)")
            }
        }
    }
}

更新:在下面添加了代码,我在更改答案并解决了一个奇怪的 swiftui 动画问题后得到了这些代码。

import SwiftUI

enum Value: Codable, Equatable {
    case none
    case bool(Bool)
    case int(Int)
    case float(Float)
    case string(String)
}

struct Node: Codable {
    var name: String
    var value: Value
    var children: [Node]? = nil
}

struct ValueView: View {
    let name: String
    @Binding var value: Value
    
    var body: some View {
        HStack {
            switch value {
            case .none:
                Spacer()
            case let .bool(bool):
                Toggle(name, isOn: Binding(get: { bool }, set: { value = .bool([=13=]) } ))
            case let .int(int):
                Stepper("\(int)", value: Binding(get: { int }, set: { value = .int([=13=]) }))
            case let .float(float):
                Text(float.description)
            case let .string(string):
                Text(string)
            }
        }
    }
}

class Model: ObservableObject {
    @Published var data: [Node] = [
        Node(name: "My Bool", value: .bool(false)),
        Node(name: "My Int", value: .int(25)),
        Node(name: "My float", value: .float(3.123)),
        Node(name: "My parent node", value: .none, children: [
            Node(name: "Default name", value: .string("Untitled")),
            Node(name: "Fluid animations", value: .bool(true)),
            Node(name: "More children??", value: .none, children: [
                Node(name: "Hello", value: .string("There"))
            ])
        ])
    ]
    
    private let encoder = JSONEncoder()
    
    init() {
        encoder.outputFormatting = .prettyPrinted
    }
    
    var jsonString: String {
        String(data: try! encoder.encode(data), encoding: .utf8)!
    }
}


struct ContentView: View {
    @StateObject private var model = Model()
    
    var body: some View {
        VStack {
            ScrollView {
                Text(model.jsonString)
            }
            
            List($model.data, id: \.name, children: \.children) { $node in
                ValueView(name: node.name, value: $node.value)
                    .animation(.default, value: node.value)
            }
        }
    }
}

@main
struct MyApp: App {
    @StateObject private var model = Model()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

事实上,拥有几个非常相似的属性是一种代码味道,而且添加这些属性只是因为 UI 层需要它们。

但是,由于您已经切换了值类型,您可以将绑定逻辑推送到该 (UI) 层。这是一个可能的实现:

struct ValueView: View {
    let name: String
    @Binding var value: Value
    
    var body: some View {
        HStack {
            switch value {
            case .none:
                Spacer()
            case let .bool(bool):
                Toggle(name, isOn: Binding(get: { bool }, set: { value = .bool([=10=]) } ))
            case let .int(int):
                Stepper("\(int)", value: Binding(get: { int }, set: { value = .int([=10=]) }))
            case let .float(float):
                Text(float.description)
            case let .string(string):
                Text(string)
            }
        }
    }
}

我还冒昧地将代码提取到专用视图并将该视图与 Node 类型解耦,这在 Swift 中更加惯用 UI 并使您的代码更易读和更容易维护。

考虑到以上几点,ContentView 就变成了:

用法:

struct ContentView: View {
    @StateObject private var model = Model()
    
    var body: some View {
        VStack {
            ScrollView {
                Text(model.jsonString)
            }
            
            List($model.data, id: \.name, children: \.children) { $node in
                ValueView(name: node.name, value: $node.value)
            }
        }
    }
}

,您可以安全地从 Value 枚举中删除“重复的”属性。