I have a button which creates a user when 'Sign Up' / Register is selected.
I have an error message show when there has been an issue with registration and this works successfully. Unfortunately, I want and need to alert the user that the account has been created successfully and to check their email for their verification.
The second alert shows for a split second then disappears, I'm not sure whether this is an issue with it being a second alert in the sequence. Do I need to create an enum, or will this not really matter?
import SwiftUI
import Firebase
struct SignUpView: View {
#EnvironmentObject var userInfo: UserInfo
#State var user: UserViewModel = UserViewModel()
#Environment(\.presentationMode) var presentationMode
#State private var showError = false
#State private var showVerifyEmail = false
#State private var errorString = ""
var body: some View {
NavigationView {
VStack {
Group {
VStack(alignment: .leading) {
TextField("Full Name", text: self.$user.fullname).autocapitalization(.words)
if !user.validNameText.isEmpty {
Text(user.validNameText).font(.caption).foregroundColor(.red)
}
}
VStack(alignment: .leading) {
TextField("Email Address", text: self.$user.email).autocapitalization(.none).keyboardType(.emailAddress)
if !user.validEmailAddressText.isEmpty {
Text(user.validEmailAddressText).font(.caption).foregroundColor(.red)
}
}
VStack(alignment: .leading) {
SecureField("Password", text: self.$user.password)
if !user.validPasswordText.isEmpty {
Text(user.validPasswordText).font(.caption).foregroundColor(.red)
}
}
VStack(alignment: .leading) {
SecureField("Confirm Password", text: self.$user.confirmPassword)
if !user.passwordsMatch(_confirmPW: user.confirmPassword) {
Text(user.validConfirmPasswordText).font(.caption).foregroundColor(.red)
}
}
}.frame(width: 300)
.textFieldStyle(RoundedBorderTextFieldStyle())
VStack(spacing: 20 ) {
Button(action: {
FBAuth.createUser(withEmail: self.user.email,
name: self.user.fullname,
password: self.user.password) { (result) in
switch result {
case .failure(let error):
self.errorString = error.localizedDescription
self.showError = true
case .success( _):
print("Account creation successful")
}
self.showVerifyEmail = true
}
}) {
Text("Register")
.frame(width: 200)
.padding(.vertical, 15)
.background(Color(.systemBlue))
.cornerRadius(8)
.foregroundColor(.white)
.opacity(user.isSignInComplete ? 1 : 0.75)
}
.disabled(!user.isSignInComplete)
Spacer()
}.padding()
}.padding(.top)
.alert(isPresented: $showError) {
Alert(title: Text("Error creating accout"), message: Text(self.errorString), dismissButton: .default(Text("OK")))
}
.alert(isPresented: $showVerifyEmail) {
Alert(title: Text("Email Sent"), message: Text("Verificiation email sent, please also check your spam"), dismissButton: .default(Text("OK")))
}
.navigationBarTitle("Sign Up", displayMode: .inline)
.navigationBarItems(trailing: Button("Dismiss") {
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
struct SignUpView_Previews: PreviewProvider {
static var previews: some View {
SignUpView()
}
}
I'm aware there is a lot of code missing to compile a complete view, but hopefully it can provide enough insight as to why the alert disappears.
Thank you all!
Your code is setting both showError and showVerifyEmail when you get an error. I assume you don't want to do that. So you might try this:
if !self.showError {
self.showVerifyEmail = true
}
Related
I have multiple views in a swift project I am trying to change Views My idea is to make an enum, and change state. I do not want to use navigationLinks.
Here is my code:
struct NightOutApp: App {
var body: some Scene {
WindowGroup {
ViewNavigator()
}
}
}
enum ViewState{
case LoginView
case UserProfileView
}
struct ViewNavigator: View {
var body: some View {
#State var ViewState = ViewState.LoginView
return Group{
switch ViewState{
case .LoginView:
LoginView()
case .UserProfileView:
UserProfileView()
}
}
}
}
I have a variable
#Binding var ViewState: ViewState at the top of the LoginView
some logic on the LoginView that would change ViewState from LoginView to UserProfileView:
self.ViewState = .UserProfileView
I tried using binding variables. this gave me a warning: Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.
Edit-
Here is what happens when I run it. I press a button to login, It takes me to this breakpoint. The code seems to process, but the view does not change.
Code
Ok I've edited my answer in an attempt to understand what you're looking for. It seems you are needing a login flow but I don't think I can understand your problem fully without seeing more of your code. Here is an example that you should be able to copy and paste and play around with while you figure out exactly what you're needing.
import SwiftUI
struct ViewNavigator: View {
#State var viewState = 0
#State var withEmail: String = ""
#State var withPassword: String = ""
#State var showAlert: Bool = false
#State var alertTitle: String = ""
#State var alertMessage: String = ""
var body: some View {
ZStack {
switch viewState {
case 0:
loginView
case 1:
profileView
default:
RoundedRectangle(cornerRadius: 25)
.foregroundColor(.red)
}
VStack {
Spacer()
bottomButton
}
.padding()
}
.alert(alertTitle, isPresented: $showAlert) { } message: {
Text(alertMessage)
}
}
func handleBottomButtonPressed() {
switch viewState {
case 0:
guard !withEmail.isEmpty else {
showAlert(title: "Wait!", message: "Your email is required.")
return
}
guard withPassword == "password" else {
showAlert(title: "Hold Up!", message: "Your password is incorrect.")
return
}
default:
break
}
if viewState == 0 {
viewState += 1
withPassword = ""
} else {
viewState -= 1
}
}
func showAlert(title: String, message: String) {
alertTitle = title
alertMessage = message
showAlert.toggle()
}
}
struct ViewNavigator_Previews: PreviewProvider {
static var previews: some View {
ViewNavigator()
}
}
extension ViewNavigator {
private var bottomButton: some View {
Button {
handleBottomButtonPressed()
} label: {
Text(viewState == 0 ? "LOGIN" : "LOGOUT")
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(15)
.shadow(radius: 10)
}
}
private var loginView: some View {
ZStack {
Color.green.ignoresSafeArea()
VStack {
Image(systemName: "1.square")
.font(.largeTitle)
Text("LOGIN")
.font(.largeTitle)
VStack {
TextField("email...", text: $withEmail)
.padding()
.background(Color(UIColor.systemGray5))
.cornerRadius(10)
.textInputAutocapitalization(.never)
SecureField("password...", text: $withPassword)
.padding()
.background(Color(UIColor.systemGray5))
.cornerRadius(10)
.textInputAutocapitalization(.never)
}
.padding()
}
}
}
private var profileView: some View {
ZStack {
Color.orange.ignoresSafeArea()
VStack {
Image(systemName: "2.square")
.font(.largeTitle)
Text("Profile View")
.font(.largeTitle)
}
}
}
}
I'm trying to make navigation link, here I'm creating NavigationLink with isActive based on State variable isLoggedIn. But without setting isLoggedIn true getting navigating to next screen.
also, it's navigating on tap of Email Textfield which is wrong.
My expectation is it should navigate only after isLoggedIn setting to true.
struct ContentView: View {
#State private var isLoggedIn = false
#State private var email = ""
var body: some View {
NavigationView {
NavigationLink(destination: Text("Second View"), isActive: $isLoggedIn) {
VStack {
TextField("Email", text: $email)
.frame(maxWidth: .infinity, alignment: .leading)
.border(.gray, width: 1)
.foregroundColor(.blue)
Button("Send") {
isLoggedIn = true
}
}
.padding()
}
}
}
}
The expectation is wrong, NavigationLink handles user input independently (but also, additionally, can be activated programmatically).
In this scenario, to leave only programmatic activation, we need to hide navigation link, like
NavigationView {
VStack {
TextField("Email", text: $email)
.frame(maxWidth: .infinity, alignment: .leading)
.border(.gray, width: 1)
.foregroundColor(.blue)
Button("Send") {
isLoggedIn = true
}
.background(NavigationLink(destination: // << here !!
Text("Second View"), isActive: $isLoggedIn) { EmptyView() })
}
.padding()
}
Here it's working fine with this
struct MoviesListView: View {
#State var navigate = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Hi"), isActive: $navigate) {
Button("Add") {
navigate.toggle()
}
}
}
}
}
}
I made a SearchBarView view to use in various other views (for clarity, I removed all the layout modifiers, such as color and padding):
struct SearchBarView: View {
#Binding var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search…", text: $text, onCommit: didPressReturn)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
}
}
}
)
}
func didPressReturn() {
print("did press return")
}
}
It looks and works great to filter data in a List.
But now I'd like to use the SearchBarView to search an external database.
struct SearchDatabaseView: View {
#Binding var isPresented: Bool
#State var searchText: String = ""
var body: some View {
NavigationView {
VStack {
SearchBarView(text: $searchText)
// need something here to respond to onCommit and initiate a network call.
}
.navigationBarTitle("Search...")
.navigationBarItems(trailing:
Button(action: { self.isPresented = false }) {
Text("Done")
})
}
}
}
For this, I only want to start the network access when the user hits return. So I added the onCommit part to SearchBarView, and the didPressReturn() function is indeed only called when tapping return. So far, so good.
What I don't understand is how SearchDatabaseView that contains the SearchBarView can respond to onCommit and initiate the database searh - how do I do that?
Here is possible approach
struct SearchBarView: View {
#Binding var text: String
var onCommit: () -> () = {} // inject callback
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search…", text: $text, onCommit: didPressReturn)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
}
}
}
)
}
func didPressReturn() {
print("did press return")
// do internal things...
self.onCommit() // << external callback
}
}
so now in SearchDatabaseView you can
VStack {
SearchBarView(text: $searchText) {
// do needed things here ...
}
}
after I dismiss a sheet my buttons on the screen above do not work anymore. Only after pressing on a non-interacting surface the buttons work again. I use swift version 5 and the error occurs in the simulator and on the device.
#Edit
Code Snippets
AddView this will be displayed in a sheet
struct AddView: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode
// some state
var body: some View {
NavigationView {
Form {
Section(header: Text("Name")) {
TextField("Task-Name (e.g. Eat the 🍰)", text: $title)
}
Section(header: Text("Settings")) {
DatePicker("Date", selection: $timestamp, displayedComponents: .date)
Toggle(isOn: $highPrio) {
Text("High Priority")
}
}
}
.navigationBarItems(trailing: Button("Add"){
// logic
do {
try self.moc.save()
} catch {
print(error.localizedDescription)
}
self.presentationMode.wrappedValue.dismiss()
}.alert(isPresented: $showAlert) {
Alert(title: Text("Name field is empty"), message: Text("Please enter a name"), dismissButton: .default(Text("Got it!")))
})
.navigationBarTitle("New Task")
}
}
}
struct AddView_Previews: PreviewProvider {
static var previews: some View {
AddView()
}
}
ContentView includes a FetchRequest with some functions and nothing more.
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Task.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Task.timestamp, ascending: true),
NSSortDescriptor(keyPath: \Task.status, ascending: false),
NSSortDescriptor(keyPath: \Task.highPriority, ascending: false),
]) var tasks: FetchedResults<Task>
#State private var showingAddSheet = false
#State private var showAlert = false
#State private var editMode = false
var body: some View {
NavigationView {
List {
ForEach(tasks.filter{return self.filterTasks(task: $0)}, id: \.self) { task in
HStack {
TaskRowView(
title: task.wrappedTitle,
status: task.wrappedStatus,
timestamp: task.wrappedTimestamp,
highPriority: task.highPriority,
showDetail: self.editMode
).onTapGesture {
self.toggleStatus(item: task)
}
}
}
.onDelete(perform: removeTask)
}
.navigationBarTitle(self.editMode ? "All Tasks" : "Today")
.navigationBarItems(leading: Button(self.editMode ? "Done" : "Edit") {self.editMode.toggle()}, trailing: Button("Add") {self.showingAddSheet.toggle()})
.sheet(isPresented: $showingAddSheet) {
AddView().environment(\.managedObjectContext, self.moc)
}
}.onAppear(perform: {
self.cleanupTasks()
}).alert(isPresented: $showAlert) {
Alert(title: Text("Unfished Task found"),
message: Text("Do you want to take over the old tasks or delete them?"),
primaryButton: .destructive(Text("Delete all")) {
self.removeOldTasks()
},
secondaryButton: .default(Text("Take over")) {
self.takeOldTasksOver()
}
)
}
}
// functions...
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return ContentView().environment(\.managedObjectContext, context)
}
}
#endif
Solution
This is a Bug that is related to the .large navigationBarItem. You can set that to .inline to go around it for now:
NavigationView {
,,,
.navigationBarTitle(Text(""), displayMode: .inline)
}
Related Thread: SwiftUI - Navigation bar button not clickable after sheet has been presented
The problem happens when there is a navigationView inside your "AddView" struct. From what I have tested, If you remove the navigationView and just use a button (for dismissal) somewhere else inside the AddView it works perfectly. as below:
var body: some View {
VStack{
HStack {
Spacer()
Button(action: {
// logic ..
self.presentationMode.wrappedValue.dismiss()
}){
Text("Add")
}.alert(isPresented: $showAlert) {
Alert(title: Text("Name field is empty"), message: Text("Please enter a name"), dismissButton: .default(Text("Got it!")))
}
.padding(24)
}
Form {
Section(header: Text("Name")) {
TextField("Task-Name (e.g. Eat the 🍰)", text: $title)
}
Section(header: Text("Settings")) {
DatePicker("Date", selection: $timestamp, displayedComponents: .date)
Toggle(isOn: $highPrio) {
Text("High Priority")
}
}
}
}
}
I have this problem in the simulator, but on a real device it works well. Consider updating of xcode, mac OS or iOS.
It's working on device with latest Xcode.
I am trying to send alert based on a condition, but the Navigation link is executing regardless of the condition. I was hoping for an intercept.
Goal:
If condition is not me then do not launch new view
New View is launching and then alert.
I am sure my code is incorrect, but I am unsure how I should achieve this
Thanks in advance.
var body: some View {
NavigationView {
VStack {
Button(action: {}) {
//NavigationLink(destination: secondView()) {
NavigationLink(destination: checkState()) {
Text("Add to Cart")
}.padding()
.font(.system(size: 14))
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(6)
}
}
}.padding()
} // End of the GetOrder Struct
struct GetdOrderView_Previews: PreviewProvider {
static var previews: some View {
GetdOrderView()
}
}
}
struct checkState: View {
#ObservedObject var calcCheck = MealOrder()
#State var showingAlert = false
#State var myToggle = false
var body: some View {
NavigationView {
VStack {
Button(action: {
//Enter Action here
if self.myToggle == true {
self.showingAlert = true
} else {
self.showingAlert = true
}
}) {
Text("This is a test")
}.padding()
.font(.system(size: 14))
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(6)
//Insert Alerts
.alert(isPresented: $showingAlert) {
if self.myToggle {
return Alert(title: Text("Showing Message"), message: Text("Cart is valid"), dismissButton: .default(Text("OK")))
} else {
return Alert(title: Text("Showing Alert"), message: Text("Cart Empty"), dismissButton: .default(Text("Cancel")))
}
}
}
}
}
}
struct secondView: View {
var body: some View {
VStack {
Text("This is the second test")
}
}
}
Try the following approach
#State var activateLink = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: checkState(), isActive: $activateLink) {
EmptyView()
}
Button(action: {
if _YOUR_CONDITION_HERE_ {
self.activateLink = true
}
}) {
Text("Add to Cart")
.padding()
.font(.system(size: 14))
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(6)
}
}
.onAppear { self.activateLink = false }
}.padding()
}// End of the GetOrder Struct