“在视图更新期间修改状态,这将导致未定义的行为。”在文本字段(SwiftUI)上键入时出错

时间:2019-08-03 18:12:39

标签: state swiftui

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

@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)
        }
}
}

我知道如果在状态被修改时(在文本字段中键入)更新视图时出现此错误。但是我不会在“登录”屏幕中的任何位置更新视图。那么为什么会发生此错误。帮助将不胜感激!

4 个答案:

答案 0 :(得分:3)

这对我有用,您甚至不需要导入Combine!当您使用@Published时,SwiftUI将自动合成objectWillChange主题,并且只要该属性发生突变,就会调用send。您仍然可以根据需要手动致电.send(),但在大多数情况下您不会这样做。

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

摘录自beta 5发行说明:

  

您可以通过定义一个   在对象更改之前发出的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)
        }
    }
}

答案 1 :(得分:1)

我猜这是一个错误。您在此简单视图上也收到了此消息,该视图通过用户输入过滤出列表条目。只是在文本字段中快速键入会导致此问题。如果您在文本字段中输入第一个字符,则用户界面会停留一段时间。

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()
    }
}

答案 2 :(得分:0)

这可能与您的问题无关,但是在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(())
     }
 }
}

答案 3 :(得分:0)

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

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