在 ForEach 循环中绑定时,如何阻止 SwiftUI TextField 失去焦点?
Posted
技术标签:
【中文标题】在 ForEach 循环中绑定时,如何阻止 SwiftUI TextField 失去焦点?【英文标题】:How can I stop a SwiftUI TextField from losing focus when binding within a ForEach loop? 【发布时间】:2021-05-16 07:14:41 【问题描述】:在表单中,我希望用户能够动态维护电话号码列表,包括根据需要添加/删除号码。
我目前正在维护 ObservableObject 类的已发布数组属性中的数字列表,这样当向数组中添加新数字时,SwiftUI 表单将通过其 ForEach 循环重建列表。 (每个电话号码都表示为一个PhoneDetails
结构,具有号码本身和电话类型[工作、手机等] 的属性。)
添加/删除非常好,但是当我尝试在 TextField 中编辑电话号码时,只要我输入一个字符,TextField 就会失去焦点。
我的直觉是,由于 TextField 绑定到其中一个数组项的 phoneNumber 属性,所以一旦我修改它,类中的整个数组就会发布它已被更改的事实,因此 SwiftUI 会尽职尽责地重建ForEach 循环,从而失去焦点。尝试输入新电话号码时,这种行为并不理想!
我还尝试直接循环遍历 PhoneDetails
对象的数组,而不使用 ObservedObject 类作为中间存储库,并且相同的行为仍然存在。
以下是最小可重现的示例代码;如前所述,添加/删除项目效果很好,但尝试输入任何 TextField 会立即失去焦点。
有人可以帮我指出我做错了什么的正确方向吗?
class PhoneDetailsStore: ObservableObject
@Published var allPhones: [PhoneDetails]
init(phones: [PhoneDetails])
allPhones = phones
func addNewPhoneNumber()
allPhones.append(PhoneDetails(phoneNumber: "", phoneType: "cell"))
func deletePhoneNumber(at index: Int)
if allPhones.indices.contains(index)
allPhones.remove(at: index)
struct PhoneDetails: Equatable, Hashable
var phoneNumber: String
var phoneType: String
struct ContentView: View
@ObservedObject var userPhonesManager: PhoneDetailsStore = PhoneDetailsStore(
phones: [
PhoneDetails(phoneNumber: "800–692–7753", phoneType: "cell"),
PhoneDetails(phoneNumber: "867-5309", phoneType: "home"),
PhoneDetails(phoneNumber: "1-900-649-2568", phoneType: "office")
]
)
var body: some View
List
ForEach(userPhonesManager.allPhones, id: \.self) phoneDetails in
let index = userPhonesManager.allPhones.firstIndex(of: phoneDetails)!
HStack
Button(action: userPhonesManager.deletePhoneNumber(at: index) )
Image(systemName: "minus.circle.fill")
.buttonStyle(BorderlessButtonStyle())
TextField("Phone", text: $userPhonesManager.allPhones[index].phoneNumber)
Button(action: userPhonesManager.addNewPhoneNumber() )
Label
Text("Add Phone Number")
icon:
Image(systemName: "plus.circle.fill")
.buttonStyle(BorderlessButtonStyle())
【问题讨论】:
我确实发现 this SO post 提出了类似的问题,但其接受的答案无助于解决我的问题。 【参考方案1】:试试这个:
ForEach(userPhonesManager.allPhones.indices, id: \.self) index in
HStack
Button(action:
userPhonesManager.deletePhoneNumber(at: index)
)
Image(systemName: "minus.circle.fill")
.buttonStyle(BorderlessButtonStyle())
TextField("Phone", text: $userPhonesManager.allPhones[index].phoneNumber)
EDIT-1:
查看我的评论并考虑到新的兴趣,这里是一个不使用indices
的版本。
它使用ForEach
与 SwiftUI 3 for ios 15+ 的绑定功能:
class PhoneDetailsStore: ObservableObject
@Published var allPhones: [PhoneDetails]
init(phones: [PhoneDetails])
allPhones = phones
func addNewPhoneNumber()
allPhones.append(PhoneDetails(phoneNumber: "", phoneType: "cell"))
// -- here --
func deletePhoneNumber(of phone: PhoneDetails)
allPhones.removeAll(where: $0.id == phone.id )
struct PhoneDetails: Identifiable, Equatable, Hashable
let id = UUID() // <--- here
var phoneNumber: String
var phoneType: String
struct ContentView: View
@ObservedObject var userPhonesManager: PhoneDetailsStore = PhoneDetailsStore(
phones: [
PhoneDetails(phoneNumber: "800–692–7753", phoneType: "cell"),
PhoneDetails(phoneNumber: "867-5309", phoneType: "home"),
PhoneDetails(phoneNumber: "1-900-649-2568", phoneType: "office")
]
)
var body: some View
List
ForEach($userPhonesManager.allPhones) $phone in // <--- here
HStack
Button(action:
userPhonesManager.deletePhoneNumber(of: phone) // <--- here
)
Image(systemName: "minus.circle.fill")
.buttonStyle(BorderlessButtonStyle())
TextField("Phone", text: $phone.phoneNumber) // <--- here
Button(action: userPhonesManager.addNewPhoneNumber() )
Label
Text("Add Phone Number")
icon:
Image(systemName: "plus.circle.fill")
.buttonStyle(BorderlessButtonStyle())
【讨论】:
谢谢!您的解决方案确实有效,但根据another SO post's comment,不建议在ForEach
中使用.indices
,因为它可能导致超出范围的错误。为什么使用.indices
不会导致我的代码崩溃?
我已经看到声称在添加或删除元素时使用索引不是“推荐的”,理由是范围发生了变化。好吧,这不是我的经验,示例表明它可以正常工作而不会崩溃。据我所知,当 SwiftUI 重新计算视图时,它将获得新的索引范围,从而避免任何崩溃。这就是我在这个例子中看到的。但话又说回来,我可能错了。
这救了我的命!我们知道为什么 OP 的代码会导致这个问题,或者它是一个错误吗?这已经困扰我好几个小时了
用不使用indices
的(更强大的)方法更新了我的答案以上是关于在 ForEach 循环中绑定时,如何阻止 SwiftUI TextField 失去焦点?的主要内容,如果未能解决你的问题,请参考以下文章