【问题标题】:How can I stop a SwiftUI TextField from losing focus when binding within a ForEach loop?在 ForEach 循环中绑定时,如何阻止 SwiftUI TextField 失去焦点?
【发布时间】: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 提出了类似的问题,但其接受的答案无助于解决我的问题。

标签: swiftui


【解决方案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的(更强大的)方法更新了我的答案
猜你喜欢
  • 1970-01-01
  • 2020-05-27
  • 1970-01-01
  • 1970-01-01
  • 2018-01-08
  • 2020-09-02
  • 2022-11-21
  • 2020-05-24
  • 1970-01-01
相关资源
最近更新 更多