如何在 SwiftUI 中使用枚举绑定数据?

Posted

技术标签:

【中文标题】如何在 SwiftUI 中使用枚举绑定数据?【英文标题】:How can I use enum to Binding data in SwiftUI? 【发布时间】:2021-03-23 02:26:50 【问题描述】:

我正在尝试绑定到枚举实例的 rawValue

enum Brand: Int, CaseIterable, Codable 
    case toyota = 0
    case mazda
    case suzuki


struct Car 
    var brand: Brand


struct CarView: View  

    @Binding car: Car  
 
    var body: some View 
        SomeView(selectionInt: $car.brand)
    

但我收到此错误:

无法将“绑定”类型的值转换为预期参数 输入“绑定”。

如果尝试使用 $car.brand.rawValue 代替(在 SomeView 的参数中),但我收到此错误:

无法分配给属性:“rawValue”是不可变的。

如何将我的视图绑定到模型实例的 rawValue?

【问题讨论】:

让您的 Car 严格成为 ObservableObject 类并让品牌发布。您将为自己省去解决方法的所有麻烦。 Binding是双向连接,它需要一个可以存储并且可以被Observed的父级。 @lorem-ipsum 要使 Car 结构成为 ObservableObject,我必须将其转换为类。在我的实际代码中,我根本不处理汽车,在这个例子中我用汽车表示的模型实际上是一个符合 Codable 的结构,它是另一个或两个 Codable 结构的子结构。以汽车类比为例,当我访问汽车品牌时,它实际上是 Vehicle 模型的成员... vehicle.groundVehicle.car.brand。 “Vehicle”是从 XML 文件中解码出来的,如果我偏离当前设置太远,恐怕我可能会搞砸我目前设置的所有内容。 没问题我想我会建议它。您始终可以使用class 手动符合Codable。当您可能在struct 中处理许多var 时,答案中的解决方法似乎很多工作,下面的解决方法只是每次都创建一个新的Car。您不妨将整个Car@State 作为父级和@Binding 作为子级传递,并为自己避免错误的可能性(由多个变量引起)。其余的现有变量会发生什么?像一个新的车牌号码?孩子手头没有原始资料 是的。我觉得我需要一种新的方法。与其尝试将 selectionInt 绑定到 Car.brand,不如将其保留为简单的 @State var selectionInt: Int 然后通过函数调用设置汽车实例的品牌?通过与 SomeView() 寻呼机绑定的某种 onChange 调用? 如果你使用@State(在View中)或@PublishedObservableObjectclass中,你只能有效地绑定到单个变量。 【参考方案1】:

使用brandProxy 来维护其他Car 变量的完整性

import SwiftUI
enum Brand: Int, CaseIterable, Codable 
    case toyota = 0
    case mazda
    case suzuki
    case unknown
    
    func description() -> String 
        var result = "unknown"
        switch self 
        case .toyota:
            result = "toyota"
        case .mazda:
            result = "mazda"
        case .suzuki:
            result = "suzuki"
        case .unknown:
            result = "unknown"
        
        return result
    

struct CarView: View 
    //This makes it mutable
    @State var car: Car
    var brandProxy: Binding<Int> 
        Binding<Int>(
            get: 
                car.brand.rawValue
            ,
            set: 
                car.brand = Brand(rawValue: $0) ?? Brand.unknown
            
        )
    
    var body: some View 
        VStack
            Text(car.brand.description())
            SomeView(selectedIndex: brandProxy)
        
    

【讨论】:

干杯。第一个选项看起来与我目前拥有的代码非常相似,但我没有使用 Picker。我正在使用第三方寻呼机,它只接受一个 Int 作为它的“选择”,因此我尝试检索品牌的 rawValue。 我不知道为什么我没有早点想到这个。使用代理。我更新了代码。这不会替换您的整个 Car 完美。这是我希望的简单解决方案。干杯! @Ribena:我的代码基本上有什么问题或者这个答案有什么新的?! @swiftPunk 您的回答本身没有问题。这很有帮助!这对我来说更容易理解,除了声明一个新变量之外,没有对任何代码进行任何更改。因为我的代码实际上根本不处理汽车或汽车模型,而是更复杂,所以 lorem ipsum 的解决方案更适合我保持简单。将 Binding.init(get: () 等行(来自您的解决方案)输入到 SomeView 的参数中对我来说有点吓人。毫无疑问,其他提出这个问题的人会更喜欢您的答案,而是赞成!【参考方案2】:

对于这种绑定,你需要一个自定义的 Binding 来帮助 SwiftUI 绑定数据!

信息:您不能更改枚举的 rawValue,因为它们是不可变的! 并且尝试以绑定方式更改它们对您没有帮助,它们是不可变的! 你可以让他们不设置他们!

因此,有了这些信息,您就可以知道为什么 SwiftUI 不希望将它绑定在第一个位置!因为如果 SwiftUI 使该 Binding 成为可能,那将是对枚举 rawValues 的重大违反!然后我们将尝试以这种方式使用绑定能力,即我们改变汽车的 Int 而不是 Enum。以代码为例。



import SwiftUI

struct ContentView: View 
    
    @State private var selectedCar: Car = Car(brand: Brand.suzuki)
    
    var body: some View 

        VStack 
            
            Picker("", selection: $selectedCar.brand) 
                
                ForEach(Brand.allCases, id: \.self)  car in
                    
                    Text(car.description)
                    
                
                
            
            .pickerStyle(SegmentedPickerStyle())
        
        .padding()

        
        CarView(selectionInt: Binding.init(get:  () -> Int in return selectedCar.brand.rawValue ,
                                           set:  newValue in
                                            
                                            if let unwrappedBrand = Brand(rawValue: newValue)  selectedCar = Car(brand: unwrappedBrand)  ) )
        
    


struct CarView: View 
    
    @Binding var selectionInt: Int
    
    var body: some View 
        
        Text("selected Int: " + selectionInt.description)
            .padding()
        
        let selectedCar = (Brand(rawValue: selectionInt)?.description ?? "unknown")
        
        Text( "selected car: " + selectedCar)
            .padding()
        
        
        VStack(spacing: 20.0) 
            
            Button("select toyota")  selectionInt = 0 
            Button("select mazda")  selectionInt = 1 
            Button("select suzuki")  selectionInt = 2 
            
        
        .font(Font.body.weight(Font.Weight.bold))
        
    


enum Brand: Int, CaseIterable, Codable, CustomStringConvertible 
    
    case toyota = 0
    case mazda = 1
    case suzuki = 2
    
    var description: String 
        
        switch self 
        case .toyota: return "toyota"
        case .mazda: return "mazda"
        case .suzuki: return "suzuki"
        
    


struct Car 
    var brand: Brand

【讨论】:

用我的代码尝试一下,它的工作方式几乎与我预期的一样。 SomeView 根据汽车品牌正确显示相关图像。但 SomeView 实际上是某种选择器/寻呼机。当我转到另一个品牌时,它似乎正在获取但没有设置品牌。所以我将如何拥有它,使其成为双向绑定。 @Ribena:所以新问题与您提出的问题无关,因为我们知道问题是关于 在 SwiftUI 中绑定到枚举的 rawValue,我想我用工作示例对其进行了解释。您要问的新问题可能与您的代码调试有关,我认为最好在单独的问题中提出。使用代码和视图使该问题发生。谢谢。 很公平。我应该根据我想要实现的目标来构建最初的问题。感谢您提供更新的答案。 @Ribena:我刚刚以这种方式更新了自定义绑定,您也可以更改汽车并稍微清理代码。 干杯。您的答案有效,但最终我将接受的答案更改为 lorem ipsum 的,因为它更好地回答了我的问题的精神。不过,下次我会尽量说得更清楚,因为我同意我在问题标题和正文中并没有完全问同样的问题。

以上是关于如何在 SwiftUI 中使用枚举绑定数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 的视图控制器中实现数据绑定?

如何将数字动态绑定到枚举

如何在 SwiftUI 中使用可观察对象并与 NavigationLink 绑定?

WPF 数据绑定:如何使用 XAML 将枚举数据绑定到组合框? [复制]

如何使用 SwiftUI 将数组绑定到列表

在 SwiftUI 中的 ForEach 中绑定