为什么 SwiftUI "Toggle.onChange" 没有启动?

Why isn't SwiftUI "Toggle.onChange" firing?

我在列表中有一个切换 UI 元素,我需要在切换状态更改时执行代码。据我了解,这是通过在切换上使用绑定,然后在绑定变量上添加“.onChange:of:”来实现的。

完成:

//

import Foundation
import SwiftUI

struct User : Hashable, Identifiable {
    
    var isToggled = false
    
    var id: String
    var firstName: String?
    var lastName: String?
    var loggedIn: Bool = false
    var loggedInSince: String?
    var isActive: Bool?
    var isAdmin: Bool?
    
}

struct ListRow: View {
 @EnvironmentObject var userListModel: UserListModel

 @Binding var user: User

 var body: some View {
     Toggle(isOn: $user.loggedIn) {
         VStack {
           Text("\(user.firstName!) \(user.lastName!)")
             if user.loggedIn {
                 Text("Logged in since \(user.loggedInSince!)")
                     .font(Font.system(size: 10))
             }
             else {
                 Text("Logged out since \(user.loggedInSince!)")
                     .font(Font.system(size: 10))
             }
         }
     }
     .disabled(self.cantLogInOut())
     .onChange(of: user.loggedIn) { value in  // THIS ISN'T WORKING, IT'S NOT GETTING CALLED
         // action...
         print(value)
         userListModel.changeLogStatus(user: user)
     }
 }

因此,永远不会调用 onChange ("print", "userListModel.changeLogStatus") 中的代码。

我在控制台中得到这个:

2022-02-12 22:56:39.860044-0500 TimeCard[10104:4072116] invalid mode 'kCFRunLoopCommonModes' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution.

我不知道那是什么意思,谷歌搜索没有帮助,当我在该错误上放置符号断点时,调试器中没有显示任何有用的信息(它显示的堆栈跟踪是两个汇编代码段不在主要范围内。)

您尝试将 Toggle 绑定到模型,但也有一个副作用,它也会更改模型,这是行不通的。

您必须将 Toggle 替换为 Button 并在处理程序调用中 userListModel.changeLogStatus。并删除 onChange。这样,当按下登录按钮时,模型可以保持注销状态,并且在完成登录操作并更改模型后,由于更改将重新计算主体,并且按钮将更新以显示注销。您还可以将 @Binding var user 更改为 let user,因为您不再需要写权限。

这里有一些代码,您可以使用它们来“...当切换状态改变时执行代码。” 由于您正在使用 userListModel.changeLogStatus(user: user) 来记录 loggedIn 中的更改, 没有必要 @Binding var user: User 而是使用 @State var user: User

class UserListModel: ObservableObject {
    @Published var users = [User(isToggled: false, id: "1", firstName: "firstName-1", lastName: "lastName-1", loggedIn: false, loggedInSince: "yesterday", isActive: false, isAdmin: false),
                            User(isToggled: false, id: "2", firstName: "firstName-2", lastName: "lastName-2", loggedIn: false, loggedInSince: "yesterday", isActive: false, isAdmin: false),
                            User(isToggled: false, id: "3", firstName: "firstName-3", lastName: "lastName-3", loggedIn: false, loggedInSince: "yesterday", isActive: false, isAdmin: false)
    ]
    
    func changeLogStatus(user: User) {
        if let ndx = users.firstIndex(of: user) {
            users[ndx].loggedIn = user.loggedIn
        }
    }
}

struct User: Hashable, Identifiable {
    var isToggled = false
    var id: String
    var firstName: String?
    var lastName: String?
    var loggedIn: Bool = false
    var loggedInSince: String?
    var isActive: Bool?
    var isAdmin: Bool?
}

struct ListRow: View {
    @EnvironmentObject var userListModel: UserListModel
    
    @State var user: User  // <-- use @State
    
    var body: some View {
        Toggle(isOn: $user.loggedIn) {
            VStack {
                Text("\(user.firstName!) \(user.lastName!)")
                if user.loggedIn {
                    Text("Logged in since \(user.loggedInSince!)").font(Font.system(size: 10))
                }
                else {
                    Text("Logged in since \(user.loggedInSince!)").font(Font.system(size: 10))
                }
            }
        }
    //    .disabled(self.cantLogInOut())
        .onChange(of: user.loggedIn) { value in  // <-- THIS IS WORKING
            print("in onChange value: \(value)")
            userListModel.changeLogStatus(user: user)
        }
    }
}
    
    struct ContentView: View {
        @StateObject var model = UserListModel()
        
        var body: some View {
            List(model.users) { user in
                ListRow(user: user)
            }.environmentObject(model)
        }
    }
   

您也可以反其道而行之,仅使用 binding,不使用 EnvironmentObject UserListModel,如以下代码示例所示:

struct ListRow: View {
    @Binding var user: User  // <-- use @Binding, no need for userListModel here
    
    var body: some View {
        Toggle(isOn: $user.loggedIn) {
            VStack {
                Text("\(user.firstName!) \(user.lastName!)")
                if user.loggedIn {
                    Text("Logged in since \(user.loggedInSince!)").font(Font.system(size: 10))
                }
                else {
                    Text("Logged in since \(user.loggedInSince!)").font(Font.system(size: 10))
                }
            }
        }
        //.disabled(self.cantLogInOut())
        .onChange(of: user.loggedIn) { value in  // <-- THIS IS WORKING
            print("in onChange value: \(value)")
        }
    }
}

struct ContentView: View {
    @StateObject var model = UserListModel()
    
    var body: some View {
        List($model.users) { $user in   // <-- note the $ 
            ListRow(user: $user)
        }
    }
}