SwiftUI中遍历@AppStorage变量创建TextField的报错排查请求
遍历@AppStorage变量创建TextField的问题分析与解决
问题描述
尝试遍历@AppStorage变量并生成带预设值的TextField,调整$符号后仍无法正常运行,简化后的代码如下:
import SwiftUI struct ContentView: View { static let store = UserDefaults(suiteName: "group.not-important.etc") @AppStorage("variable1", store: store) var variable1 = "a" @AppStorage("variable2", store: store) var variable2 = "b" @AppStorage("variable3", store: store) var variable3 = "c" @AppStorage("variable4", store: store) var variable4 = "d" @AppStorage("variable5", store: store) var variable5 = "e" var body: some View { NavigationStack { Form { Section(header: Text("Variables")) { HStack (alignment: .center) { ForEach([ $variable1, // 也试过直接写variable1等 $variable2, $variable3, $variable4, $variable5 ], id: \.self) { variable in // 也试过$variable TextField( "", text: variable // 也试过$variable、variable.wrappedName等 ) } } } } } } }
错误原因拆解
1. Generic struct 'ForEach' requires that 'Binding<String>' conform to 'Hashable'
ForEach使用id: \.self时,要求数组元素必须遵循Hashable协议,但Binding<String>本身并未实现该协议,直接把$variable1这类Binding对象放进数组遍历会触发类型不匹配错误。
2. Inferred projection type 'String' is not a property wrapper
把闭包参数写成$variable in是错误写法——闭包传入的参数已经是Binding<String>类型,$符号仅用于属性包装器的投影操作,普通变量(即使是Binding类型)不能加$做投影,因此触发语法错误。
3. Cannot convert value of type 'String' to expected argument type 'Binding<String>'
如果去掉$直接传variable1这类值,数组元素就是普通String,而TextField的text参数要求的是Binding<String>;此时写$variable相当于试图对普通String值做投影,既无对应语法,类型也完全不匹配。
解决方案
方案1:通过键名动态生成Binding(推荐)
把变量的键名存进数组,遍历键名时动态创建对应UserDefaults的Binding,无需单独定义多个@AppStorage变量:
import SwiftUI struct ContentView: View { static let store = UserDefaults(suiteName: "group.not-important.etc")! // 存储每个键对应的默认值 private let defaultValues = [ "variable1": "a", "variable2": "b", "variable3": "c", "variable4": "d", "variable5": "e" ] // 所有变量的键名数组 private let variableKeys = ["variable1", "variable2", "variable3", "variable4", "variable5"] var body: some View { NavigationStack { Form { Section(header: Text("Variables")) { HStack(alignment: .center) { ForEach(variableKeys, id: \.self) { key in TextField( "", text: Binding( get: { Self.store.string(forKey: key) ?? self.defaultValues[key]! }, set: { Self.store.set($0, forKey: key) } ) ) } } } } } } }
方案2:给Binding做Hashable包装
如果不想修改原有@AppStorage变量的定义,可以创建一个遵循Hashable的包装结构体,把Binding对象包装后再遍历:
import SwiftUI // 自定义可Hashable的Binding包装器 struct HashableBinding<T: Hashable>: Hashable { let binding: Binding<T> private let id: T init(_ binding: Binding<T>) { self.binding = binding self.id = binding.wrappedValue } func hash(into hasher: inout Hasher) { hasher.combine(id) } static func == (lhs: HashableBinding<T>, rhs: HashableBinding<T>) -> Bool { lhs.id == rhs.id } } struct ContentView: View { static let store = UserDefaults(suiteName: "group.not-important.etc") @AppStorage("variable1", store: store) var variable1 = "a" @AppStorage("variable2", store: store) var variable2 = "b" @AppStorage("variable3", store: store) var variable3 = "c" @AppStorage("variable4", store: store) var variable4 = "d" @AppStorage("variable5", store: store) var variable5 = "e" var body: some View { NavigationStack { Form { Section(header: Text("Variables")) { HStack(alignment: .center) { ForEach([ HashableBinding($variable1), HashableBinding($variable2), HashableBinding($variable3), HashableBinding($variable4), HashableBinding($variable5) ], id: \.self) { wrapper in TextField("", text: wrapper.binding) } } } } } } }
内容的提问来源于stack exchange,提问作者AleXiuS
相关产品推荐
相关产品推荐

