3

I have the following scenario. I have a text field and a button, what I would need is to show an error message in case the field is empty and if not, navigate the user to the next screen.

I have tried showing the error message conditionally by using the field value and checking if it is empty on button press, but then, I don't know how to navigate to the next screen.

struct SomeView: View {

    @State var fieldValue = ""
    @State var showErrorMessage = false

    var body: some View {
        NavigationView {
            VStack {
                TextField("My Field", text: $fieldValue).textFieldStyle(RoundedBorderTextFieldStyle())

                if showErrorMessage {
                    Text("Error, please enter value")
                }

                Button(action: {
                    if self.fieldValue.isEmpty {
                        self.showErrorMessage = true
                    } else {
                        self.showErrorMessage = false
                        //How do I put navigation here, navigation link does not work, if I tap, nothing happens
                    }
                }) {
                    Text("Next")
                }
            }
        }
    }
}

Using UIKit would be easy since I could use self.navigationController.pushViewController

swifty
  • 971
  • 1
  • 8
  • 15

3 Answers3

3

Thanks to part of an answer here, here's some working code.

First, I moved everything into an EnvronmentObject to make things easier to pass to your second view. I also added a second toggle variable:

class Model: ObservableObject {
    @Published var fieldValue = ""
    @Published var showErrorMessage = false
    @Published var showSecondView = false
}

Next, change two things in your ContentView. I added a hidden NavigationLink (with a isActive parameter) to actually trigger the push, along with changing your Button action to execute a local function:

struct ContentView: View {
    @EnvironmentObject var model: Model

    var body: some View {
        NavigationView {
            VStack {
                TextField("My Field", text: $model.fieldValue).textFieldStyle(RoundedBorderTextFieldStyle())
                NavigationLink(destination: SecondView(), isActive: $model.showSecondView) {
                    Text("NavLink")
                }.hidden()
                Button(action: {
                    self.checkForText()
                }) {
                    Text("Next")
                }
                .alert(isPresented: self.$model.showErrorMessage) {
                    Alert(title: Text("Error"), message: Text("Please enter some text!"), dismissButton: .default(Text("OK")))
                }
            }
        }
    }
    func checkForText() {
        if model.fieldValue.isEmpty {
            model.showErrorMessage.toggle()
        } else {
            model.showSecondView.toggle()
        }
    }
}

Toggling showErrorMessage will show the Alert and toggling `showSecondView will take you to the next view.

Finally, the second view:

struct SecondView: View {
    @EnvironmentObject var model: Model
    var body: some View {
        ZStack {
            Rectangle().fill(Color.green)
            // workaround
            .navigationBarBackButtonHidden(true) // not needed, but just in case
            .navigationBarItems(leading: MyBackButton(label: "Back!") {
                self.model.showSecondView = false
            })
            Text(model.fieldValue)
        }
    }
    func popSecondView() {
        model.showSecondView.toggle()
    }
}
struct MyBackButton: View {
    let label: String
    let closure: () -> ()

    var body: some View {
        Button(action: { self.closure() }) {
            HStack {
                Image(systemName: "chevron.left")
                Text(label)
            }
        }
    }
}

This is where the above linked answer helped me. It appears there's a bug in navigation back that still exists in beta 6. Without this workaround (that toggles showSecondView) you will get sent back to the second view one more time.

You didn't post any details on the second view contents, so I took the liberty to add someText into the model to show you how to easily pass things into it can be using an EnvironmentObject. There is one bit of setup needed to do this in SceneDelegate:

var window: UIWindow?
var model = Model()

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    let contentView = ContentView()

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView.environmentObject(model))
        self.window = window
        window.makeKeyAndVisible()
    }
}

I noticed a slight change in this, depending on when your project was created (beta 6 declares an instance of contentView where older versions do not). Either way, declare an instance of model and then add the envoronmentObject modifier to contentView.

2

Another approach is to make the "Next" button conditionally a Button when the fieldValue is empty and a NavigationLink when the fieldValue is valid. The Button case will trigger your error message view and the NavigationLink will do the navigation for you. Keeping this close to your sample, the following seems to do the trick.

struct SomeView: View {

    @State var fieldValue = ""
    @State var showErrorMessage = false

    var body: some View {
        NavigationView {
            VStack {
                TextField("My Field", text: $fieldValue).textFieldStyle(RoundedBorderTextFieldStyle())

                if showErrorMessage {
                    Text("Please Enter Data")
                }

                if fieldValue == "" {
                    Button(action: {
                        if self.fieldValue == "" {
                            self.showErrorMessage = true
                        }

                    }, label: {
                        Text("Next")
                    })
                } else {
                    // move on case
                    NavigationLink("Next", destination: Text("Next View"))
                }
            }
        }
    }
}
KB-YYZ
  • 712
  • 7
  • 7
1

By using this code we can display the alert if the fields are empty else . it will navigate.

struct SomeView: View {

    @State var userName = ""
    @State var password = ""
    @State var showErrorMessage = false

    var body: some View {
        NavigationView {
            VStack {
                TextField("Enter Username", text: $userName).textFieldStyle(RoundedBorderTextFieldStyle())
                SecureField("Enter Your Password", text: $password)
                    .textFieldStyle(RoundedBorderTextFieldStyle())


                if userName == "" || password == "" {
                    Button(action: {
                        if self.userName == ""  || self.password == "" {
                            self.showErrorMessage = true
                        }
                   }, label: {
                        Text("Login")
                    })
                } else {
                    // move case
                    NavigationLink("Login", destination: Text("Login successful"))
                }
            }.alert(isPresented: $showErrorMessage) { () -> Alert in
                Alert(title: Text("Important Message"), message: Text("Please Fill all the Fields"), primaryButton: .default(Text("Ok")), secondaryButton: .destructive(Text("Cancel")))
            }
        }
    }
}
zmag
  • 7,825
  • 12
  • 32
  • 42