使用核心数据绑定编辑TextField时不会触发“ .disabled”修饰符

问题描述

我正在开发一个游戏,玩家(本地)需要向队友描述一个数字单词,而后者又会在给定的时限内猜测尽可能多的单词。这意味着我必须让他们创建团队并向这些团队添加玩家。

我已经设置了核心数据并创建了一个SwiftUI视图,用于创建团队并向这些团队添加玩家:这是可行的。我现在正在构建一个视图,用于编辑团队属性,但是我正在努力使用.disabled修饰符进行数据验证。我想确保在少于2人的团队中,并且任何字段(团队名称或球员名称)为空时,禁用“保存”按钮。

发生的事情是,在编辑团队名称(团队实体的直接属性)时,我的.disabled修饰符会按计划触发,因此我可以检查它是否为空。但是,在编辑任何播放器名称时,传递给.disabled修饰符的函数不会触发,因此我无法检查播放器名称是否为空。

下面是我的代码

struct EditTeamsView: View {
    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedobjectContext) var managedobjectContext
    
    @Observedobject var existingTeam: Team
    
    var body: some View {
        ZStack {
            Color.init(UIColor.systemGroupedBackground)
                .edgesIgnoringSafeArea(.all)
            
            vstack {
                Form {
                    // Team name textfield
                    Section(footer: Text("A team needs a minimum of 2 players,and can have a maximum of 5 players.")) {
                        TextField("Team name...",text: $existingTeam.name ?? "")
                    }
                    
                    Section {
                        ForEach(existingTeam.playerArray,id: \.self) { player in
                            TextField("Player name...",text: Binding(
                                        get: {
                                            player.wrappedname
                                        },set: { newValue in
                                            player.name = newValue
                                        }))
                        }
                        .onDelete(perform: deletePlayers)
                    }
                }
                
                StockFullWidthButton(action: {
                    saveChanges()
                    self.presentationMode.wrappedValue.dismiss()
                },label: "Save Changes")
                .disabled(!isInputValid())
            }
        }
        .navigationBarTitle("Edit Team",displayMode: .inline)
    }
    
    func deletePlayers(at offsets: IndexSet) {
        for index in offsets {
            let playerToRemove = existingTeam.playerArray[index]
            existingTeam.removeFromPlayers(playerToRemove)
        }
    }
    
    func isInputValid() -> Bool {
        print("Input is checked")
        
        guard !existingTeam.name!.isEmpty else { return false }
        
        guard existingTeam.playerArray.count >= 2 else { return false }
        
        for player in existingTeam.playerArray {
            if player.name!.isEmpty {
                return false
            }
        }
        
        return true
    }
    
    func saveChanges() {
        if managedobjectContext.hasChanges {
            try? self.managedobjectContext.save()
        }
    }
}

对于团队名称TextField绑定,我使用了此运算符重载(取自another post on SO)。这对于球队名称文本字段效果很好,但不适用于球员名称

func ??<T>(lhs: Binding<Optional<T>>,rhs: T) -> Binding<T> {
    Binding(
        get: { lhs.wrappedValue ?? rhs },set: { lhs.wrappedValue = $0 }
    )
}

这就是为什么我对玩家名称文本字段使用自定义绑定的原因: Binding(get: { player.wrappedname },set: { newValue in player.name = newValue })

关于如何解决此问题的任何想法?预先感谢!

解决方法

如果要在某些条件下更新视图,则需要具有明确的状态并手动管理该状态。在提供的代码中,您依赖于该视图将在刷新时进行验证,但是刷新不会发生,因为未更改任何依赖关系。

这里是可能的解决方案-为错误的输入提供明确的状态,并在所需条件发生变化时明确触发它

@ObservedObject var existingTeam: Team

@State private var isBadInput = false     // << this !!
var body: some View {
    ZStack {
        Color.init(UIColor.systemGroupedBackground)
            .edgesIgnoringSafeArea(.all)
        
        VStack {
            Form {
                // Team name textfield
                Section(footer: Text("A team needs a minimum of 2 players,and can have a maximum of 5 players.")) {
                    TextField("Team name...",text: $existingTeam.name ?? "")
                       .onReceive(existingTeam.$name) { _ in
                           self.isBadInput = !isInputValid()   // << updated !!
                       }
                }
                
                Section {
                    ForEach(existingTeam.playerArray,id: \.self) { player in
                        TextField("Player name...",text: Binding(
                                    get: {
                                        player.wrappedName
                                    },set: { newValue in
                                        player.name = newValue
                                    }))
                          .onReceive(player.objectWillChange) { _ in
                               self.isBadInput = !isInputValid()   // << updated !!  
                          }
                    }
                    .onDelete(perform: deletePlayers)
                }
            }
            
            StockFullWidthButton(action: {
                saveChanges()
                self.presentationMode.wrappedValue.dismiss()
            },label: "Save Changes")
            .disabled(isBadInput)         // << used !!
        }
    }
    .navigationBarTitle("Edit Team",displayMode: .inline)
}