SwiftUI NavigationLink doesn't trigger with onReceive method - ios

I'm trying to learn how to do basic login in swift with Firebase and it's causing me to lose my mind over navigating to the main page of the app once the login is complete. I have a ViewModel that manages the login, and in the view I have added an onReceive property to listen on a viewModel's boolean to detect sign in and trigger the navigation. I don't understand why it's not working, any help would be greatly appreciated!
ViewModel:
class LoginViewModel: ObservableObject {
let auth = Auth.auth()
var userInfo: User?
#Published var isSignedIn = false
func signIn(email: String, password: String) {
auth.signIn(withEmail: email, password: password) { _, error in
if let error = error as? NSError {
print("Error happened on login"+error.description)
} else {
print("Login successful")
if let user = self.auth.currentUser {
print("We have a user")
self.userInfo = user
self.isSignedIn.toggle()
}
}
}
}
}
View:
struct LoginPage: View {
#State var email = ""
#State var password = ""
#ObservedObject var viewModel = LoginViewModel()
var body: some View {
NavigationView {
ZStack {
VStack {
TextField("Email", text: $email).padding()
.background(Color(.secondarySystemBackground))
.padding()
TextField("Password", text: $password).padding()
.background(Color(.secondarySystemBackground))
.padding()
Button(action: {
guard !email.isEmpty, !password.isEmpty else {
return
}
viewModel.signIn(email: email, password: password)
}, label: {
Text("Sign in")
.padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(15)
})
}
}.navigationTitle("Login")
}.onReceive(viewModel.$isSignedIn) { isSignedIn in
if isSignedIn {
print("ok damm")
NavigationLink(destination: HomePage()) {
EmptyView()
}
}
}
}
}
Note that "ok damm" prints every time.

Try the below code it will help you to solve your issue:
NavigationLink(destination: Text("Second View"), isActive: $isSignedIn){
EmptyView()
}
View:
struct LoginPage: View {
#State var email = ""
#State var password = ""
#ObservedObject var viewModel = LoginViewModel()
var body: some View {
NavigationView {
ZStack {
VStack {
TextField("Email", text: $email).padding()
.background(Color(.secondarySystemBackground))
.padding()
TextField("Password", text: $password).padding()
.background(Color(.secondarySystemBackground))
.padding()
Button(action: {
guard !email.isEmpty, !password.isEmpty else {
return
}
viewModel.signIn(email: email, password: password)
}, label: {
Text("Sign in")
.padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(15)
})
}
NavigationLink(destination: HomePage()), isActive: $isSignedIn){
EmptyView()
}
}.navigationTitle("Login")
}
}
}

Answer by Jatin Bhuva works but is deprecated for iOS 16. Here's the solution for new iOS versions:
struct LoginPage: View {
#State var email = ""
#State var password = ""
#ObservedObject var viewModel = LoginViewModel()
var body: some View {
NavigationStack {
ZStack {
VStack {
TextField("Email", text: $email).padding()
.background(Color(.secondarySystemBackground))
.padding()
TextField("Password", text: $password).padding()
.background(Color(.secondarySystemBackground))
.padding()
Button(action: {
guard !email.isEmpty, !password.isEmpty else {
return
}
viewModel.signIn(email: email, password: password)
}, label: {
Text("Sign in")
.padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(15)
})
Text("Don't have an account yet ?").padding(EdgeInsets(top: 20, leading: 0, bottom: 10, trailing: 0))
Button(action: {
guard !email.isEmpty, !password.isEmpty else {
return
}
viewModel.signUp(email: email, password: password)
}, label: {
Text("Create an account")
.padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(15)
})
}
}.navigationTitle("Login")
.navigationDestination(isPresented: $viewModel.isSignedIn) {
HomePage()
}
}
}
}
Notice how I replaced the NavigationView with a NavigationStack and added .navigationDestination(isPresented:) that listens for changes on my model's isSignedIn published property.

switch statement in swiftui can handle that easily. I use it all the time;
//Added code
enum LoginStages {
case preLogin, postLogin
}
class LoginViewModel: ObservableObject {
let auth = Auth.auth()
var userInfo: User?
#Published var loginStage: LoginStages = LoginStages.preLogin
//Added code
#Published var isSignedIn = false
func signIn(email: String, password: String) {
auth.signIn(withEmail: email, password: password) { _, error in
if let error = error as? NSError {
print("Error happened on login"+error.description)
} else {
print("Login successful")
if let user = self.auth.currentUser {
print("We have a user")
self.userInfo = user
self.isSignedIn.toggle()
loginStage = .postLogin //Added code
}
}
}
}
}
And your view should look like this;
struct LoginPage: View {
#State var email = ""
#State var password = ""
#ObservedObject var viewModel = LoginViewModel()
var body: some View {
switch viewModel.loginStage {
case .preLogin:
//This is ur initial code which the user sees before login.
// the same as the code you have above.
case .postLogin:
//Here, just call the post login view after a successful login;
// I am assuming it is called home view for this example.
HomeView()
}
}
}

Related

KeyboardSceneDelegate: Animation styles were not empty on user driven presentation

I'm having problem with my app. When I tap on a TextField, trying to fill data, in the sheet that just show up, my app freezes and four errors appear on console:
> [Assert] Animation styles expected to be empty on user driven
> presentation. Actually contains: (
> "<<_UIViewControllerKeyboardAnimationStyle: 0x281ee4d00>; animated = YES; duration = 0.35; force = NO>" )
>
> [KeyboardSceneDelegate] Animation styles were not empty on user driven
> presentation!
>
> Fit_Vein/ProfileView.swift:69: Fatal error: Unexpectedly found nil
> while unwrapping an Optional value
>
> Fit_Vein/ProfileView.swift:69: Fatal error: Unexpectedly found nil
> while unwrapping an Optional value
This situation takes place when I go from ProfileView to SettingsView, then try to do one of options: change email, password or delete account and input data in those sheets.
My code:
ProfileView:
struct ProfileView: View {
#ObservedObject private var profileViewModel: ProfileViewModel
#EnvironmentObject private var sessionStore: SessionStore
#Environment(\.colorScheme) var colorScheme
#State private var image = UIImage()
#State private var shouldPresentAddActionSheet = false
#State private var shouldPresentImagePicker = false
#State private var shouldPresentCamera = false
#State private var shouldPresentSettings = false
init(profileViewModel: ProfileViewModel) {
self.profileViewModel = profileViewModel
}
var body: some View {
GeometryReader { geometry in
let screenWidth = geometry.size.width
let screenHeight = geometry.size.height
ScrollView(.vertical) {
HStack {
if profileViewModel.profilePicturePhotoURL != nil {
AsyncImage(url: profileViewModel.profilePicturePhotoURL!) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 50))
.shadow(color: .gray, radius: 7)
.frame(width: screenWidth * 0.4, height: screenHeight * 0.2)
.onTapGesture {
self.shouldPresentAddActionSheet = true
}
} placeholder: {
Image(uiImage: UIImage(named: "blank-profile-hi")!)
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 50))
.shadow(color: .gray, radius: 7)
.frame(width: screenWidth * 0.4, height: screenHeight * 0.2)
}
} else {
Image(uiImage: UIImage(named: "blank-profile-hi")!)
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 50))
.shadow(color: .gray, radius: 7)
.frame(width: screenWidth * 0.4, height: screenHeight * 0.2)
.onTapGesture {
self.shouldPresentAddActionSheet = true
}
}
Spacer(minLength: screenWidth * 0.05)
VStack {
HStack {
Text(profileViewModel.profile!.firstName)
.foregroundColor(.green)
.font(.system(size: screenHeight * 0.03))
.fontWeight(.bold)
Spacer()
NavigationLink(destination: SettingsView(profile: profileViewModel).environmentObject(sessionStore), isActive: $shouldPresentSettings) {
Button(action: {
shouldPresentSettings = true
}, label: {
Image(systemName: "slider.vertical.3")
.resizable()
.aspectRatio(contentMode: .fit)
})
.frame(width: screenWidth * 0.12, height: screenHeight * 0.04)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
.padding(.top, screenHeight * 0.02)
HStack {
Text(profileViewModel.profile!.username)
.foregroundColor(Color(uiColor: UIColor.lightGray))
Spacer()
}
Spacer()
}
}
.padding()
VStack {
Text("Level 1")
.font(.system(size: screenHeight * 0.03))
.fontWeight(.bold)
RoundedRectangle(cornerRadius: 25)
.frame(width: screenWidth * 0.9)
.padding()
.overlay(
HStack {
RoundedRectangle(cornerRadius: 25)
.foregroundColor(.green)
.padding()
.frame(width: screenWidth * 0.7)
Spacer()
}
)
.shadow(color: .gray, radius: 7)
Text("7 / 10 Workouts")
}
Spacer()
}
.sheet(isPresented: $shouldPresentImagePicker) {
ImagePicker(sourceType: self.shouldPresentCamera ? .camera : .photoLibrary, selectedImage: self.$image)
.onDisappear {
profileViewModel.uploadPhoto(image: image)
}
}
.actionSheet(isPresented: $shouldPresentAddActionSheet) {
ActionSheet(title: Text("Add a new photo"), message: nil, buttons: [
.default(Text("Take a new photo"), action: {
self.shouldPresentImagePicker = true
self.shouldPresentCamera = true
}),
.default(Text("Upload a new photo"), action: {
self.shouldPresentImagePicker = true
self.shouldPresentCamera = false
}),
ActionSheet.Button.cancel()
])
}
}
}
}
SettingsView:
import SwiftUI
struct SettingsView: View {
#ObservedObject private var profileViewModel: ProfileViewModel
#StateObject private var sheetManager = SheetManager()
#State private var shouldPresentActionSheet = false
#Environment(\.dismiss) var dismiss
private class SheetManager: ObservableObject {
enum Sheet {
case email
case password
case logout
case signout
}
#Published var showSheet = false
#Published var whichSheet: Sheet? = nil
}
init(profile: ProfileViewModel) {
self.profileViewModel = profile
}
var body: some View {
GeometryReader { geometry in
let screenWidth = geometry.size.width
let screenHeight = geometry.size.height
Form {
Section(header: Text("Chats")) {
Toggle(isOn: .constant(false), label: {
Text("Hide my activity status")
})
}
Section(header: Text("Account")) {
Button(action: {
sheetManager.whichSheet = .email
sheetManager.showSheet.toggle()
}, label: {
Text("Change e-mail address")
})
Button(action: {
sheetManager.whichSheet = .password
sheetManager.showSheet.toggle()
}, label: {
Text("Change password")
})
Button(action: {
sheetManager.whichSheet = .logout
shouldPresentActionSheet = true
}, label: {
Text("Logout")
.foregroundColor(.red)
})
Button(action: {
sheetManager.whichSheet = .signout
shouldPresentActionSheet = true
}, label: {
Text("Delete account")
.foregroundColor(.red)
})
}
Section(header: Text("Additional")) {
Label("Follow me on GitHub:", systemImage: "link")
.font(.system(size: 17, weight: .semibold))
Link("#Vader20FF", destination: URL(string: "https://github.com/Vader20FF")!)
.font(.system(size: 17, weight: .semibold))
}
}
.navigationBarTitle("Settings")
.navigationBarTitleDisplayMode(.large)
.sheet(isPresented: $sheetManager.showSheet) {
switch sheetManager.whichSheet {
case .email:
ChangeEmailAddressSheetView(profile: profileViewModel)
case .password:
ChangePasswordSheetView(profile: profileViewModel)
case .signout:
DeleteAccountSheetView(profile: profileViewModel)
default:
Text("No view")
}
}
.confirmationDialog(sheetManager.whichSheet == .logout ? "Are you sure you want to logout?" : "Are you sure you want to delete your account? All data will be lost.", isPresented: $shouldPresentActionSheet, titleVisibility: .visible) {
if sheetManager.whichSheet == .logout {
Button("Logout", role: .destructive) {
profileViewModel.sessionStore!.signOut()
dismiss()
}
Button("Cancel", role: .cancel) {}
} else {
Button("Delete Account", role: .destructive) {
sheetManager.showSheet.toggle()
}
Button("Cancel", role: .cancel) {}
}
}
}
}
}
struct DeleteAccountSheetView: View {
#ObservedObject private var profileViewModel: ProfileViewModel
#Environment(\.dismiss) var dismiss
#State private var email = ""
#State private var password = ""
init(profile: ProfileViewModel) {
self.profileViewModel = profile
}
var body: some View {
GeometryReader { geometry in
let screenWidth = geometry.size.width
let screenHeight = geometry.size.height
NavigationView {
VStack {
Form {
Section(footer: Text("Before you delete your account please provide your login credentials to confirm it is really you.")) {
TextField("E-mail", text: $email)
SecureField("Password", text: $password)
}
}
Button(action: {
withAnimation {
dismiss()
profileViewModel.deleteUserData() {
profileViewModel.sessionStore!.deleteUser(email: email, password: password) {
print("Successfully deleted user.")
}
}
}
}, label: {
Text("Delete account permanently")
})
.frame(width: screenWidth * 0.7, height: screenHeight * 0.08)
.background(Color.green)
.cornerRadius(15.0)
.font(.system(size: screenHeight * 0.026))
.foregroundColor(.white)
.padding()
}
.navigationBarHidden(true)
.ignoresSafeArea(.keyboard)
}
}
}
}
struct ChangeEmailAddressSheetView: View {
#ObservedObject private var profileViewModel: ProfileViewModel
#Environment(\.dismiss) var dismiss
#State private var oldEmail = ""
#State private var password = ""
#State private var newEmail = ""
init(profile: ProfileViewModel) {
self.profileViewModel = profile
}
var body: some View {
GeometryReader { geometry in
let screenWidth = geometry.size.width
let screenHeight = geometry.size.height
NavigationView {
VStack {
Form {
Section(footer: Text("Before you change your e-mail address please provide your login credentials to confirm it is really you.")) {
TextField("Old e-mail address", text: $oldEmail)
SecureField("Password", text: $password)
TextField("New e-mail address", text: $newEmail)
}
}
Button(action: {
withAnimation {
dismiss()
profileViewModel.emailAddressChange(oldEmailAddress: oldEmail, password: password, newEmailAddress: newEmail) {}
}
}, label: {
Text("Change e-mail address")
})
.frame(width: screenWidth * 0.7, height: screenHeight * 0.08)
.background(Color.green)
.cornerRadius(15.0)
.font(.system(size: screenHeight * 0.026))
.foregroundColor(.white)
.padding()
}
.navigationBarHidden(true)
.ignoresSafeArea(.keyboard)
}
}
}
}
struct ChangePasswordSheetView: View {
#ObservedObject private var profileViewModel: ProfileViewModel
#Environment(\.dismiss) var dismiss
#State private var email = ""
#State private var oldPassword = ""
#State private var newPassword = ""
init(profile: ProfileViewModel) {
self.profileViewModel = profile
}
var body: some View {
GeometryReader { geometry in
let screenWidth = geometry.size.width
let screenHeight = geometry.size.height
NavigationView {
VStack {
Form {
Section(footer: Text("Before you change your password please provide your login credentials to confirm it is really you.")) {
TextField("E-mail", text: $email)
SecureField("Old password", text: $oldPassword)
SecureField("New password", text: $newPassword)
}
}
Button(action: {
withAnimation {
dismiss()
profileViewModel.passwordChange(emailAddress: email, oldPassword: oldPassword, newPassword: newPassword) {}
}
}, label: {
Text("Change password")
})
.frame(width: screenWidth * 0.7, height: screenHeight * 0.08)
.background(Color.green)
.cornerRadius(15.0)
.font(.system(size: screenHeight * 0.026))
.foregroundColor(.white)
.padding()
}
.navigationBarHidden(true)
.ignoresSafeArea(.keyboard)
}
}
}
}
ProfileViewModel:
import Foundation
import SwiftUI
#MainActor
class ProfileViewModel: ObservableObject {
#Published var sessionStore: SessionStore?
private let firestoreManager = FirestoreManager()
private let firebaseStorageManager = FirebaseStorageManager()
#Published var profile: Profile?
#Published var profilePicturePhotoURL: URL?
#Published var fetchingData = true
init(forPreviews: Bool) {
self.profile = Profile(id: "sessionStore!.currentUser!.uid", firstName: "firstname", username: "username", birthDate: Date(), age: 18, country: "country", city: "city", language: "language", gender: "gender", email: "email", profilePictureURL: nil)
}
init() {
Task {
try await fetchData()
}
}
func setup(sessionStore: SessionStore) {
self.sessionStore = sessionStore
}
func fetchData() async throws {
if sessionStore != nil {
if sessionStore!.currentUser != nil {
print("Fetching Data")
fetchingData = true
let (firstname, username, birthDate, age, country, city, language, gender, email, profilePictureURL) = try await self.firestoreManager.fetchDataForProfileViewModel(userID: sessionStore!.currentUser!.uid)
self.profile = Profile(id: sessionStore!.currentUser!.uid, firstName: firstname, username: username, birthDate: birthDate, age: age, country: country, city: city, language: language, gender: gender, email: email, profilePictureURL: profilePictureURL)
if profilePictureURL != nil {
self.firebaseStorageManager.getDownloadURLForImage(stringURL: profilePictureURL!, userID: sessionStore!.currentUser!.uid) { photoURL in
self.profilePicturePhotoURL = photoURL
}
}
Task {
fetchingData = false
}
}
} else {
fetchingData = false
}
}
func uploadPhoto(image: UIImage) {
if self.profile!.profilePictureURL != nil {
Task {
try await self.firebaseStorageManager.deleteImageFromStorage(userPhotoURL: self.profile!.profilePictureURL!, userID: self.profile!.id)
}
}
self.firebaseStorageManager.uploadImageToStorage(image: image, userID: self.profile!.id) { photoURL in
self.firestoreManager.addProfilePictureURLToUsersData(photoURL: photoURL) {
Task {
try await self.fetchData()
}
}
}
}
func emailAddressChange(oldEmailAddress: String, password: String, newEmailAddress: String, completion: #escaping (() -> ())) {
self.sessionStore!.changeEmailAddress(oldEmailAddress: oldEmailAddress, password: password, newEmailAddress: newEmailAddress) {
print("Successfully changed user's e-mail address")
}
}
func passwordChange(emailAddress: String, oldPassword: String, newPassword: String, completion: #escaping (() -> ())) {
self.sessionStore!.changePassword(emailAddress: emailAddress, oldPassword: oldPassword, newPassword: newPassword) {
print("Successfully changed user's password")
}
}
func deleteUserData(completion: #escaping (() -> ())) {
if self.profile!.profilePictureURL != nil {
self.firestoreManager.deleteUserData(userUID: sessionStore!.currentUser!.uid) {
print("Successfully deleted user data")
Task {
try await self.firebaseStorageManager.deleteImageFromStorage(userPhotoURL: self.profile!.profilePictureURL!, userID: self.sessionStore!.currentUser!.uid)
completion()
}
}
}
}
}
ProfileModel:
import Foundation
import SwiftUI
struct Profile: Codable, Identifiable {
var id: String
var firstName: String
var username: String
var birthDate: Date
var age: Int
var country: String
var city: String
var language: String
var gender: String
var email: String
var profilePictureURL: String?
}

I'm having an issue changing Views in SwiftUI using an if/else statement for a login view

I'm having trouble changing views when a username and password is successfully entered. I know the username and password works because the print statement is executed. Its a basic login type form.
import SwiftUI
struct LoginView: View {
#ObservedObject var networkManager: NetworkManager = NetworkManager()
#State var username: String = ""
#State var password: String = ""
var body: some View {
NavigationView {
VStack {
Image("dashlogovert")
.resizable()
.scaledToFit()
.frame(width: 280.0, height: 280.0, alignment: .top)
Form {
TextField("Username", text: $username)
SecureField("Password", text: $password)
Button(action: {
self.AttempLogin()
})
{
Text("Login")
.fontWeight(.bold)
.frame(width: 300, height: 30, alignment: .center)
.font(.title)
.padding()
.background(Color(red: 132/255, green: 203/255, blue: 161/255))
.foregroundColor(.white)
}
}
}
.navigationBarTitle("Login")
}
}
func AttempLogin(){
self.networkManager.loginFunction(username: self.username, password: self.password){
if self.networkManager.loggedIn {
print("You are logging in");
Dashboard()
} else {
print("You aren't logging in");
FailedLogin()
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
Dashboard() is the new view I will take the user to when they successful logon, and fail is also a view.
Also what is the best way to do this if I have done it a really bad way.
You can pass back your logged in state from loginFunction() using #escaping closure if it’s asynchronous task, and once you have that state you can pass it back to your view body by using closure again as a parameter in AttemptLogin() function, and assign that new state value to #State property wrapper, which will call body refresh and check for updated state.
Check below code-:
import SwiftUI
enum LoginState:String {
case failed
case success
case unknownState
}
class NetworkManager:ObservableObject{
func loginFunction(username:String,password:String,closure: #escaping (String)->()){
closure(LoginState.failed.rawValue)
}
}
struct LoginView: View {
#ObservedObject var networkManager: NetworkManager = NetworkManager()
#State var username: String = ""
#State var password: String = ""
#State var isLoggedIn:String = LoginState.unknownState.rawValue
var body: some View {
NavigationView {
VStack {
Image("dashlogovert")
.resizable()
.scaledToFit()
.frame(width: 280.0, height: 280.0, alignment: .top)
Form {
TextField("Username", text: $username)
SecureField("Password", text: $password)
Button(action: {
self.AttempLogin { state in
isLoggedIn = state
}
})
{
Text("Login")
.fontWeight(.bold)
.frame(width: 300, height: 30, alignment: .center)
.font(.title)
.padding()
.background(Color(red: 132/255, green: 203/255, blue: 161/255))
.foregroundColor(.white)
}
if isLoggedIn == LoginState.success.rawValue{
Dashboard()
}
if isLoggedIn == LoginState.failed.rawValue{
FailedLogin()
}
}
}
.navigationBarTitle("Login")
}
}
func AttempLogin(_ closure: #escaping (String)->()){
self.networkManager.loginFunction(username: self.username, password: self.password){ loginState in
if loginState == LoginState.success.rawValue {
print("You are logging in");
closure(loginState)
}
if loginState == LoginState.failed.rawValue {
print("You aren't logging in");
closure(loginState)
}
}
}
}
struct Dashboard:View{
var body: some View{
Text("Dashboard")
}
}
struct FailedLogin:View{
var body: some View{
Text("Login")
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
I have taken NetworkManager and other classes as dummy on my side.You need to write your logic accordingly.

Why does Text disappear when i click submit?

When I type in my EditText view to fill out all the required information and then click submit. Everything I typed disappears. I want this text to remain. I am guessing there is something wrong with my #State objects but cannot figure out what.
SignUpViewModel
class SignUpViewModel : ObservableObject {
#Published
var error: String? = nil
#Published
var goHome: Bool = false
#Published
var goToLogin: Bool = false
func onGoToLoginClicked() {
self.goToLogin = true
}
func signUp(firstName: String, lastName: String, email: String, birthday: String, phoneNumber: String, password: String, confirmPassword: String) {
if (firstName.count < 3) {
error = "Please enter first name"
return
}
if (lastName.count < 3) {
error = "Please enter last name"
return
}
if (!email.isEmail()) {
error = "Pleaes enter valid email"
return
}
if (birthday.isEmpty) {
error = "Pleae enter valid birthday"
return
}
if (!phoneNumber.isDigits) {
error = "Please enter valid phone number"
return
}
if (password.count < 8) {
error = "Please enter a password that is at least 8 characters long"
}
if (password != confirmPassword) {
error = "Password do not match"
}
Auth.auth().createUser(withEmail: email, password: password, completion: { authResult, error in
if authResult != nil {
self.goHome = true
} else {
self.error = error?.localizedDescription
}
})
}
}
SignUp View
struct SignUpScreen: View {
#State
var firstName: String = ""
#State
var lastName: String = ""
#State
var birthday: String = ""
#State
var number: String = ""
#State
var email: String = ""
#State
var password: String = ""
#State
var confirmPassword: String = ""
#EnvironmentObject
var viewModel: SignUpViewModel
var body: some View {
ZStack {
VStack {
VClearBackground()
Spacer()
}
ScrollView {
VStack(alignment: .leading) {
Group {
PreHeaderText(header: "Get Started")
.alignmentGuide(.leading, computeValue: { d in
d[.leading]
})
.padding(EdgeInsets.init(top: 32, leading: 0, bottom: 0, trailing: 0))
HeaderText(header: "Create Account")
EditText(hint: "Huey", text: $firstName, label: "FIRST NAME", textContentType: UITextContentType.name)
EditText(hint: "Freeman", text: $lastName, label: "LAST NAME", textContentType: UITextContentType.name)
EditText(hint: "04-19-1994", text: $birthday, label: "BIRTHDAY")
EditText(hint: "(281) 456-7890)", text: $number, label: "MOBILE NUMBER", textContentType: UITextContentType.telephoneNumber, keyboardType: UIKeyboardType.phonePad)
EditText(hint: "email#exmaple.com", text: $email, label: "EMAIL", textContentType: UITextContentType.emailAddress)
EditText(hint: "********", text: $password, label: "PASSWORD", textContentType: UITextContentType.newPassword)
EditText(hint: "********", text: $confirmPassword, label: "CONFIRM PASSWORD", textContentType: UITextContentType.newPassword)
}
Group {
if self.viewModel.error != nil {
HStack {
Spacer()
Text(viewModel.error ?? "")
.foregroundColor(ColorTheme.error.color)
Spacer()
}
.padding()
}
HStack {
Spacer()
VowerButton(text: "Submit") {
self.viewModel.signUp(firstName: self.firstName, lastName: self.lastName, email: self.email, birthday: self.birthday, phoneNumber: self.number, password: self.password, confirmPassword: self.confirmPassword)
}
Spacer()
}
.padding()
HStack {
Spacer()
NavigationLink(destination: LoginScreen(), isActive: $viewModel.goToLogin) {
CtaText(text: "Have an account?", cta: "Login") {
self.viewModel.onGoToLoginClicked()
}
}
.padding()
Spacer()
}
Spacer()
}
}
}
.padding(EdgeInsets.init(top: 16, leading: 16, bottom: 16, trailing: 16))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
}
.background(LinearGradient(gradient: Gradient(colors: [.black, ColorTheme.brandPurple.color]), startPoint: .top, endPoint: .bottom))
.edgesIgnoringSafeArea(.all)
}
}
EditText View
struct EditText: View {
var hint: String
#Binding
var text: String
var label: String = ""
var defaultValue = ""
var textContentType: UITextContentType? = .none
var keyboardType: UIKeyboardType = .default
private func initializeDefaultValue() {
DispatchQueue.main.async {
self.text = self.defaultValue
}
}
var body: some View {
initializeDefaultValue()
return VStack(alignment: .leading) {
Text(label).font(.system(size: 12)).bold()
.foregroundColor(ColorTheme.text.color)
HStack {
TextField(hint, text: $text)
.lineLimit(1)
.textContentType(textContentType)
.keyboardType(keyboardType)
.foregroundColor(ColorTheme.text.color)
}
Divider().background(Color(ColorTheme.brandBlue.value))
}
.padding(EdgeInsets.init(top: 12, leading: 0, bottom: 8, trailing: 0))
}
}
The problem is in your EditText view.
struct EditText: View {
var hint: String
#Binding
var text: String
var label: String = ""
var defaultValue = ""
var textContentType: UITextContentType? = .none
var keyboardType: UIKeyboardType = .default
private func initializeDefaultValue() {
DispatchQueue.main.async {
self.text = self.defaultValue
}
}
var body: some View {
initializeDefaultValue()
return VStack(alignment: .leading) {
Text(label).font(.system(size: 12)).bold()
.foregroundColor(ColorTheme.text.color)
HStack {
TextField(hint, text: $text)
.lineLimit(1)
.textContentType(textContentType)
.keyboardType(keyboardType)
.foregroundColor(ColorTheme.text.color)
}
Divider().background(Color(ColorTheme.brandBlue.value))
}
.padding(EdgeInsets.init(top: 12, leading: 0, bottom: 8, trailing: 0))
}
}
Specifically, it's in the body property. This is a computed property that is fetched by SwiftUI whenever the view is recomputed. In this case, this happens when the error property of the SignUpScreen view changes, as all the subviews are recomputed.
When this EditText view is recomputed, the initializeDefaultValue() function is called (it's the first line in the body property). This clears the text fields.
As for a solution, I'm not sure why you actually need the initializeDefaultValue function in here at all. It seems best suited for the ViewModel or some other location.
Also, just some other things that I saw:
func signUp(firstName: String, lastName: String, email: String, birthday: String, phoneNumber: String, password: String, confirmPassword: String) {
if (firstName.count < 3) {
error = "Please enter first name"
return
}
if (lastName.count < 3) {
error = "Please enter last name"
return
}
if (!email.isEmail()) {
error = "Pleaes enter valid email"
return
}
if (birthday.isEmpty) {
error = "Pleae enter valid birthday"
return
}
if (!phoneNumber.isDigits) {
error = "Please enter valid phone number"
return
}
if (password.count < 8) {
error = "Please enter a password that is at least 8 characters long"
}
if (password != confirmPassword) {
error = "Password do not match"
}
Auth.auth().createUser(withEmail: email, password: password, completion: { authResult, error in
if authResult != nil {
self.goHome = true
} else {
self.error = error?.localizedDescription
}
})
}
This function returns early in all error cases except the last two — I believe this was a mistake.
if self.viewModel.error != nil {
HStack {
Spacer()
Text(viewModel.error ?? "")
.foregroundColor(ColorTheme.error.color)
Spacer()
}
.padding()
}
This part of the SignUpScreen view should be able to be simplified to this:
if let err = self.viewModel.error {
HStack {
Spacer()
Text(err)
.foregroundColor(ColorTheme.error.color)
Spacer()
}
.padding()
}
or, if if-let statements are not allowed in this case:
if self.viewModel.error != nil {
HStack {
Spacer()
Text(viewModel.error!)
.foregroundColor(ColorTheme.error.color)
Spacer()
}
.padding()
}
as you know that the error is non-nil.
Hope all of this helps!
The problem code is in SignUpScreen:
#ObservedObject
var viewModel: SignUpViewModel = SignUpViewModel()
Whenever the view is re-evaluated, a new SignUpViewModel is created.
You can create the view model outside the view, and either pass it to the directly to the constructor, or inject it to the environment using environmentObject().
To use an environment object instead, change the above declaration to:
#EnvironmentObject
var viewModel: SignUpViewModel
And then create your view like this:
var signUpViewModel = SignUpViewModel()
// ...
SignUpScreen()
.environmentObject(signUpViewModel)

SwiftUI open another view on button click

Am trying to navigate to another view page. I tried many methods but none worked. If there is a way to navigate to another page without navigationtool... then it will be much help full
struct ContentView: View {
#State var fname: String = ""
#State var lname: String = ""
#State var num: String = ""
#State var pass: String = ""
#State private var registerionAlert = false
#State private var registerionAlertActive: ActiveAlert = .success
var body: some View {
VStack(alignment: .center) {
Form {
Text("Registeration Form")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.blue)
.multilineTextAlignment(.center)
.padding(.leading, 25.0)
TextField("Enter your first name...", text: $fname)
.padding()
.border(/*#START_MENU_TOKEN#*/Color.blue/*#END_MENU_TOKEN#*/, width: /*#START_MENU_TOKEN#*/1/*#END_MENU_TOKEN#*/)
TextField("Enter your last name...", text: $lname)
.padding()
.border(/*#START_MENU_TOKEN#*/Color.blue/*#END_MENU_TOKEN#*/, width: /*#START_MENU_TOKEN#*/1/*#END_MENU_TOKEN#*/)
TextField("Enter your phone number...", text: $num)
.padding()
.border(/*#START_MENU_TOKEN#*/Color.blue/*#END_MENU_TOKEN#*/, width: /*#START_MENU_TOKEN#*/1/*#END_MENU_TOKEN#*/)
TextField("Enter a password...", text: $pass)
.padding()
.border(/*#START_MENU_TOKEN#*/Color.blue/*#END_MENU_TOKEN#*/, width: /*#START_MENU_TOKEN#*/1/*#END_MENU_TOKEN#*/)
Button(action: {
self.registerTapped()
}) {
Text("Register")
.multilineTextAlignment(.center)
}
.padding(0.0)
.frame(width: 150.0, height: 30.0)
.background(/*#START_MENU_TOKEN#*/Color.green/*#END_MENU_TOKEN#*/)
.accentColor(/*#START_MENU_TOKEN#*/.white/*#END_MENU_TOKEN#*/)
.cornerRadius(/*#START_MENU_TOKEN#*/5.0/*#END_MENU_TOKEN#*/)
.offset(x: 95.0, y: /*#START_MENU_TOKEN#*/0.0/*#END_MENU_TOKEN#*/)
.alert(isPresented: $registerionAlert){
switch registerionAlertActive {
case .success:
return Alert(
title: Text("Register"),
message: Text("Registered Successfully"),
dismissButton: .default(
Text("Got it!"),
action: {
loginView()
print("success")
}
)
)
case .failed:
return Alert(
title: Text("Register"),
message: Text("Registered Unsuccessful, Try again later."),
dismissButton: .default(
Text("Got it!"))
)
}
}
}
.padding(10.0)
.background(/*#START_MENU_TOKEN#*/Color.blue/*#END_MENU_TOKEN#*/)
}
.padding()
.background(/*#START_MENU_TOKEN#*/Color.orange/*#END_MENU_TOKEN#*/)
}
func registerTapped() {
let params: [String: Any] = [
"first_name": "\(fname)",
"last_name": "\(lname)",
"phone": "\(num)",
"password": "\(pass)"
]
AF.request(
"http://192.168.0.9/mobile-api/public/register",
method: .post,
parameters: params,
encoding: JSONEncoding.default
).validate().responseString() {
response in
switch response.result {
case .success(_):
self.registerionAlertActive = .success
break
case .failure(_):
self.registerionAlertActive = .failed
break
}
self.registerionAlert = true
}
}
}
my another page is given below in which i haven't add much of things is only sample views
import SwiftUI
struct loginView: View {
var body: some View {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
}
}
struct loginView_Previews: PreviewProvider {
static var previews: some View {
loginView()
}
}
please help me navigate to another page
you can do this
struct ContentView: View {
#State var isOpen: Bool = false
var body: some View {
ZStack {
VStack {
Button(action: {
self.isOpen = true
}, label: {
Text("Tap me")
.foregroundColor(obj_saved_color.set_color)
}).sheet(isPresented: $isOpen, content: {
SecondView()
})
}
}
}}
struct SecondView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("Second View")
}
}}

Async Next Screen Presentation in SwiftUI

i want to signup users when they click the signup button in my app. When the signup is complete and successfully created a user on the server side I want to present the next screen and only then.
In normal way I have a PresentationButton and set the destination and when somebody clicked the button the next screen is presented directly, but now it's async.
How to handle that?
Currently I have this PresentationButton:
PresentationButton(
Text(isSignIn ? "SignIn" : "Next").font(.headline).bold()
.frame(width: 100)
.padding(10)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(20)
, destination: HomeScreen()
)
That's with the suggestion by Uro Arangino:
struct PasswordView : View {
#State private var password = ""
#State private var showNextScreen = false
var loginMode: LoginType
#ObjectBinding var signupManager = SignUpManager()
var body: some View {
VStack {
// if self.signupManager.showModal { self.showNextScreen.toggle() } <- Can't do this
TwitterNavigationView(backBtnOn: false)
middleView()
Spacer()
bottomView()
}
}
func bottomView() -> some View {
return VStack(alignment: .trailing) {
Divider()
BindedPresentationButton(
showModal: $showNextScreen,
label: getCustomText((Text("Registrieren"))),
destination: HomeTabView(),
onTrigger: {
let user = User(name: "rezo", username: "ja lol ey", profileDescription: "YAS", email: "dbjdb#dedde.de", telephoneNumber: nil, profileImage: UIImage(named: "twitter-logo")!, bannerImage: UIImage(systemName: "star")!)
self.signupManager.signIn(forUser: user, password: "ultraSecure", loginMode: .email)
// UserDefaults.standard.set(true, forKey: "loggedIn")
})
}.padding([.leading, .trailing]).padding(.top, 5).padding(.bottom, 10)
}
}
My bindable object class
final class SignUpManager: BindableObject {
let didChange = PassthroughSubject<SignUpManager, Never>()
var showModal: Bool = false {
didSet {
didChange.send(self)
}
}
func signIn(forUser user: User, password: String, loginMode: LoginType) {
if loginMode == .email {
LoginService.instance.signupWithEmail(user: user, andPassword: password, completion: handleCompletion)
} else {
LoginService.instance.login(withPhoneNumber: user.telephoneNumber!, completion: handleCompletion)
}
}
private func handleCompletion(_ status: Bool) {
if status {
showModal = true
}
}
}
You can use a presentation button with a binding.
See: https://stackoverflow.com/a/56547016/3716612
struct ContentView: View {
#State var showModal = false
var body: some View {
BindedPresentationButton(
showModal: $isSignIn,
label: Text(isSignIn ? "SignIn" : "Next")
.font(.headline)
.bold()
.frame(width: 100)
.padding(10)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(20),
destination: HomeScreen()
)
}
}

Resources