【问题标题】:'Modifying state during view update, this will cause undefined behavior.' error when typing on a textfield (SwiftUI)“在视图更新期间修改状态,这将导致未定义的行为。”在文本字段上键入时出错 (SwiftUI)
【发布时间】:2019-12-11 22:29:15
【问题描述】:

我有两个文本字段,分配给:

@State  private var emailAddress: String = ""
@State  private var password: String = ""

现在每当我在上面打字时,应用程序似乎卡住并给我这个错误:

'在视图更新期间修改状态,这将导致未定义的行为。'

我有一个StartView()

class UserSettings: ObservableObject {

var didChange = PassthroughSubject<Void, Never>()

@Published var loggedIn : Bool = false {
     didSet {
         didChange.send(())
     }
 }
}

struct StartView: View {
@EnvironmentObject var settings: UserSettings

var body: some View {
    if settings.loggedIn {
        return AnyView(TabbarView())
    }else {
        return AnyView(ContentView())
    }
}

}

我创建了一个具有loggedIn bool 值的 UserSettings 的 ObservableObject 类。当用户点击LogInView() 中的“登录”按钮时,此布尔值变为true 并出现一个新视图(TabbarView()

这是 LogInView():

struct LogInView: View {
@EnvironmentObject var settings: UserSettings

@State  private var emailAddress: String = ""
@State  private var password: String = ""

var body: some View {
GeometryReader { geometry in
        VStack (alignment: .center){
            HStack {
                Image("2")
                .resizable()
                .frame(width: 20, height: 20)
                Text("Social App")
                    .font(.system(size: 12))

            }.padding(.top, 30)
                .padding(.bottom, 10)

            Text("Log In to Your Account")
                .font(.title)
                .font(.system(size: 14, weight: .bold, design: Font.Design.default))
                .padding(.bottom, 50)

            TextField("Email", text: self.$emailAddress)
                .frame(width: geometry.size.width - 45, height: 50)
                .textContentType(.emailAddress)
                .padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))
                .accentColor(.red)
                .background(Color(red: 242 / 255, green: 242 / 255, blue: 242 / 255))
                .cornerRadius(5)


            TextField("Password", text: self.$password)
                .frame(width: geometry.size.width - 45, height: 50)
                .padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))
                .foregroundColor(.gray)
                .background(Color(red: 242 / 255, green: 242 / 255, blue: 242 / 255))
                .textContentType(.password)
                .cornerRadius(5)

             Button(action: {
                self.settings.loggedIn = true
             }) {
                    HStack {
                        Text("Log In")
                    }
                        .padding()
                        .frame(width: geometry.size.width - 40, height: 40)
                        .foregroundColor(Color.white)
                        .background(Color.blue)
                        .cornerRadius(5)
                }
                 .padding(.bottom, 40)

            Divider()

            Button(action: {
                    print("Take to forget password VC")
                    }) {
                    Text("Forgot your password?")
            }

            Spacer()

            }
            .padding(.bottom, 90)
        }
}
}

我知道如果我在修改状态时更新视图(在文本字段中输入时)会出现此错误。但我没有在登录屏幕的任何地方更新视图。那么为什么会出现这个错误。我们将不胜感激!

【问题讨论】:

    标签: state swiftui


    【解决方案1】:

    这对我有用,你甚至不需要导入 Combine!当你使用@Published 时,SwiftUI 会自动合成objectWillChange 的主题,并在属性发生变化时调用 send。如果需要,您仍然可以手动拨打.send(),但在大多数情况下您不会这样做。

    class UserSettings: ObservableObject {
        @Published var loggedIn : Bool = false
    }
    

    beta 5 发行说明摘录:

    您可以通过定义一个手动符合 ObservableObject 在对象更改之前发出的 objectWillChange 发布者。 但是,默认情况下,ObservableObject 会自动合成 objectWillChange 并在任何 @Published 属性更改之前发出。

    这是对我来说可以正常工作的完整代码(iPhone Xr 和真实设备,iPad 6th Gen):

    window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(UserSettings()))
    
    import SwiftUI
    
    struct ContentView: View {
        var body: some View {
            StartView()
        }
    }
    
    class UserSettings: ObservableObject {
        @Published var loggedIn : Bool = false
    }
    
    struct StartView: View {
        @EnvironmentObject var settings: UserSettings
    
        var body: some View {
            if settings.loggedIn {
                return AnyView(Text("LOGGED IN"))
            } else {
                return AnyView(LogInView())
            }
        }
    }
    
    struct LogInView: View {
        @EnvironmentObject var settings: UserSettings
    
    
        @State  private var emailAddress: String = ""
        @State  private var password: String = ""
    
        var body: some View {
            GeometryReader { geometry in
                VStack (alignment: .center){
                    HStack {
                        Image(systemName: "2.circle.fill")
                            .resizable()
                            .frame(width: 20, height: 20)
                        Text("Social App")
                            .font(.system(size: 12))
    
                    }.padding(.top, 30)
                        .padding(.bottom, 10)
    
                    Text("Log In to Your Account")
                        .font(.title)
                        .font(.system(size: 14, weight: .bold, design: Font.Design.default))
                        .padding(.bottom, 50)
    
                    TextField("Email", text: self.$emailAddress)
                        .frame(width: geometry.size.width - 45, height: 50)
                        .textContentType(.emailAddress)
                        .padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))
                        .accentColor(.red)
                        .background(Color(red: 242 / 255, green: 242 / 255, blue: 242 / 255))
                        .cornerRadius(5)
    
    
                    TextField("Password", text: self.$password)
                        .frame(width: geometry.size.width - 45, height: 50)
                        .padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))
                        .foregroundColor(.gray)
                        .background(Color(red: 242 / 255, green: 242 / 255, blue: 242 / 255))
                        .textContentType(.password)
                        .cornerRadius(5)
    
                    Button(action: {
                        self.settings.loggedIn = true
                    }) {
                        HStack {
                            Text("Log In")
                        }
                        .padding()
                        .frame(width: geometry.size.width - 40, height: 40)
                        .foregroundColor(Color.white)
                        .background(Color.blue)
                        .cornerRadius(5)
                    }
                    .padding(.bottom, 40)
    
                    Divider()
    
                    Button(action: {
                        print("Take to forget password VC")
                    }) {
                        Text("Forgot your password?")
                    }
    
                    Spacer()
    
                }
                .padding(.bottom, 90)
            }
        }
    }
    

    【讨论】:

    • 这似乎解决了它。谢谢!我仍然在 Xcode 上遇到错误,但 UI 响应灵敏且没有卡住。
    • 您的 Xcode 错误是否显示在任何特定行中?
    • 不。仅将我带到 AppDelegate。
    • 尝试创建一个新项目并粘贴我的代码并运行。如果这不会触发错误,那么您知道您应该专注于存在的任何差异。不管它们看起来多么微不足道。顺便说一句,我正在使用 Mojave(不是 Catalina)的 Mac 上运行 Xcode 11,beta 5。
    【解决方案2】:

    我猜这是一个错误。您收到的这条消息也发生在这个简单的视图上,它通过用户输入过滤掉列表条目。只是在文本字段中快速输入会导致此问题。如果在文本字段中输入第一个字符,UI 会卡住一段时间。

    struct ContentView: View {
        @State private var list: [String] = (0..<500).map { "Text \($0)" }
        @State private var searchText: String = ""
    
        var filteredList: [String] {
            guard !searchText.isEmpty else { return list }
            return list.filter({ $0.contains(self.searchText) })
        }
    
        var body: some View {
            VStack {
                TextField("Search", text: $searchText)
                List(filteredList, id: \String.self) { t in Text(t) }
            }
            .padding()
        }
    }
    

    一种解决方法是将@State 变量移动到模型中。所以这似乎是@State 的问题:

    class Model: ObservableObject {
        @Published var list: [String] = (0..<500).map { "Text \($0)" }
        @Published var searchText: String = ""
    
        var filteredList: [String] {
            guard !searchText.isEmpty else { return list }
            return list.filter({ $0.contains(self.searchText) })
        }
    }
    
    struct ContentView: View {
        @ObservedObject var model: Model
    
        var body: some View {
            VStack {
                TextField("Search", text: $model.searchText)
                List(model.filteredList, id: \String.self) { t in Text(t) }
            }
            .padding()
        }
    }
    

    【讨论】:

      【解决方案3】:

      这可能与您的问题无关,但在 Xcode 11 Beta 4 中,Apple 将“didset”更改为“willset”,将“didChange”更改为“willChange” 在 Xcode 11 Beta 5 中,苹果将“willChange”改为“objectWillChange”。

      因此 StartView() 应该是:

      class UserSettings: ObservableObject {
      
      var objectWillChange = PassthroughSubject<Void, Never>()
      
      @Published var loggedIn : Bool = false {
           willSet {
               objectWillChange.send(())
           }
       }
      }
      

      【讨论】:

      • 已更正!谢谢。问题仍然存在!
      • 同时使用 @Published 和 objectWillChange 是多余的。您可以一起删除 willSet 并将其重写为@Published var loggedIn: Bool = false,您也可以删除objectWillChange。两者都会自动合成。
      • 谢谢!但是知道为什么我会收到问题中提到的上述错误。 ?
      【解决方案4】:

      不要使用 if 进行分支,使用 .opacity(_:)

          @ViewBuilder
          var body: some View {
      //        if settings.loggedIn {
                  TabbarView().opacity(settings.loggedIn ? 1 : 0)
      //        } else {
                  ContentView().opacity(settings.loggedIn ? 0 : 1)
      //        }
          }
      

      【讨论】:

      猜你喜欢
      • 2021-09-17
      • 2023-02-06
      • 2020-03-11
      • 2020-05-15
      • 2020-10-27
      • 2021-01-10
      • 2021-12-18
      • 2022-11-13
      • 2020-06-12
      相关资源
      最近更新 更多