为什么 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)
}
}
}
我在列表中有一个切换 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)
}
}
}