NavigationLink moving to intended view then reverting back to previous view - swift

Im working on a new social media app and Im having and issue with my navigation code.
Once a user fills out the registration form I want them to be prompted to upload the profile picture. The issue I am having is that it shows the intended view for a half second then moves right back to the registration view.
I have a RegistrationView that handles the UI and and a AuthViewModel that is taking care of the server side communications. Essentially when the user finishes entering the information and hits the button. The AuthViewModel takes over and send the info to firebase then triggers a Bool to be true.
I then had a NagivationLink on the RegistrationView that listens for that bool and when true, changes the view on the UI. Here is the code for that.
NavigationLink(destination: ProfilePhotoSelectorView(), isActive: $viewModel.didAuthenticateUser, label:{} )
XCode is spitting out that its been deprecated in iOS 16 and to move to the NavigationStack system they developed. But, with every guide I can see I cant get this to work. The only time I can get it to work is though the code above and returns this UI glitch.
Here is the full code for the RegistrationView
import SwiftUI
struct RegistrationView: View {
#State private var email = ""
#State private var username = ""
#State private var fullName = ""
#State private var password = ""
#State private var isVerified = false
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var viewModel: AuthViewModel
var body: some View {
VStack{
NavigationLink(destination: ProfilePhotoSelectorView(), isActive: $viewModel.didAuthenticateUser, label:{} )
AuthHeaderView(title1: "Get Started.", title2: "Create Your Account.")
VStack(spacing: 40) {
CustomInputFields(imageName: "envelope", placeholderText: "Email", isSecureField: false, text: $email)
CustomInputFields(imageName: "person", placeholderText: "Username", isSecureField: false, text: $username)
CustomInputFields(imageName: "person", placeholderText: "Full Name", isSecureField: false, text: $fullName)
CustomInputFields(imageName: "lock", placeholderText: "Password", isSecureField: true, text: $password)
}
.padding(32)
Button {
viewModel.register(withEmail: email, password: password, fullname: fullName, username: username, isVerified: isVerified)
} label: {
Text("Sign Up")
.font(.headline)
.foregroundColor(.white)
.frame(width: 340, height: 50)
.background(Color("AppGreen"))
.clipShape(Capsule())
.padding()
}
.shadow(color: .gray.opacity(0.5), radius: 10, x:0, y:0)
Spacer()
Button {
presentationMode.wrappedValue.dismiss()
} label: {
HStack {
Text("Already Have And Account?")
.font(.caption)
Text("Sign In")
.font(.footnote)
.fontWeight(.semibold)
}
}
.padding(.bottom, 32)
.foregroundColor(Color("AppGreen"))
}
.ignoresSafeArea()
.preferredColorScheme(.dark)
}
}
struct RegistrationView_Previews: PreviewProvider {
static var previews: some View {
RegistrationView()
}
}
And here is the full code for the AuthViewModel
import SwiftUI
import Firebase
class AuthViewModel: ObservableObject {
#Published var userSession: Firebase.User?
#Published var didAuthenticateUser = false
init() {
self.userSession = Auth.auth().currentUser
print("DEBUG: User session is \(String(describing: self.userSession?.uid))")
}
func login(withEmail email: String, password: String){
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to sign in with error\(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
self.userSession = user
print("Did log user in")
}
}
func register(withEmail email: String, password: String, fullname: String, username: String, isVerified: Bool){
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to register with error\(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
print("DEBUG: Registerd User Succesfully")
let data = ["email": email, "username" :username.lowercased(), "fullname": fullname, "isVerified": isVerified, "uid": user.uid]
Firestore.firestore().collection("users")
.document(user.uid)
.setData(data) { _ in
self.didAuthenticateUser = true
}
}
}
func signOut() {
userSession = nil
try? Auth.auth().signOut()
}
}
Here is the code for the ProfilePhotoSelectorView
import SwiftUI
struct ProfilePhotoSelectorView: View {
var body: some View {
VStack {
AuthHeaderView(title1: "Account Creation:", title2: "Add A Profile Picture")
Button {
print("Pick Photo Here")
} label: {
VStack{
Image("PhotoIcon")
.resizable()
.renderingMode(.template)
.frame(width: 180, height: 180)
.scaledToFill()
.padding(.top, 44)
.foregroundColor(Color("AppGreen"))
Text("Tap To Add Photo")
.font(.title3).bold()
.padding(.top, 10)
.foregroundColor(Color("AppGreen"))
}
}
Spacer()
}
.ignoresSafeArea()
.preferredColorScheme(.dark)
}
}
struct ProfilePhotoSelectorView_Previews: PreviewProvider {
static var previews: some View {
ProfilePhotoSelectorView()
}
}
Tried all variations of the new NavigationStack and tried some other button code to see if i could trigger it from there. No Reso

Using NavigationLink in this way is not recommended, so I'm not surprised it would lead to buggy behavior. This is exacerbated by the fact that your RegistrationView is not placed within a NavigationView (deprecated) or NavigationStack, as these views provide most of the functionality of a navigation link.
Like you said, this usage of NavigationLink is deprecated. The isActive property has always been a little ambiguous in my opinion (from what I understand it is not meant as an 'activator' of the navigation link, rather as a way to read whether the link is active). The new way of presenting navigation links (using .navigationDestination) is much better.
Presenting a view using a boolean property
What you essentially want is to present the ProfilePhotoSelectorView when a boolean property is switched to true. This is common paradigm in SwiftUi, and there are many ways to do this, such as .sheet(isPresented:content:) or .popover(isPresented:content:). Note the isPresented parameter in both methods are boolean properties. Using .sheet, for example:
struct RegistrationView: View {
// ...
#EnvironmentObject var viewModel: AuthViewModel
var body: some View {
VStack {
// ...
}
// Presents the photo selector view when `didAuthenticateUser` is true
.sheet(isPresented: $viewModel.didAuthenticateUser) {
ProfilePhotoSelectorView()
}
}
}
Adding a new view to the Navigation Tree
If you are adamant on using navigation links (i.e. you really want the ProfilePhotoSelectorView to be a node in the navigation tree), you're going to have to learn to use the new NavigationStack and append the view onto the path. This will require some retooling (and probably some reading on your part; here and here are good starting points). The view model would be the most likely place to control the stack, although you may eventually want to create a dedicated viewmodel. Here's a simple example:
struct ContentView: View {
#StateObject var viewModel = AuthViewModel()
var body: some View {
NavigationStack(path: $viewModel.navigationPath) {
RegistrationView()
.environmentObject(viewModel)
.navigationDestination(for: RegistrationScreen.self) { screen in
switch screen {
case .photoSelection:
ProfilePhotoSelectorView()
}
}
}
}
}
class AuthViewModel: ObservableObject {
// ...
// A new enum that defines the various types of possible views in the navigation stack
enum RegistrationScreen: Hashable {
case photoSelection
}
// The navigation path
#Published var navigationPath: [RegistrationScreen] = []
// Example usages of the navigation path. These functions show how to programmatically control the navigation stack
func showPhotoSelectionScreen() {
self.navigationPath.append(.photoSelection)
}
func goToRootOfNavigation() {
self.navigationPath = []
}
}

If I understand your question correctly, you want to use NavigationStack,
but it is not working for you.
There are many missing parts, but here is my attempt of using NavigationStack
to trigger the destination, given a change in viewModel.didAuthenticateUser.
struct ContentView: View {
#StateObject var viewModel = AuthViewModel()
var body: some View {
NavigationStack(path: $viewModel.didAuthenticateUser) { // <-- here
RegistrationView()
.environmentObject(viewModel)
}
}
}
struct RegistrationView: View {
#State private var email = ""
#State private var username = ""
#State private var fullName = ""
#State private var password = ""
#State private var isVerified = false
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var viewModel: AuthViewModel
var body: some View {
VStack{
AuthHeaderView(title1: "Get Started.", title2: "Create Your Account.")
VStack(spacing: 40) {
CustomInputFields(imageName: "envelope", placeholderText: "Email", isSecureField: false, text: $email)
CustomInputFields(imageName: "person", placeholderText: "Username", isSecureField: false, text: $username)
CustomInputFields(imageName: "person", placeholderText: "Full Name", isSecureField: false, text: $fullName)
CustomInputFields(imageName: "lock", placeholderText: "Password", isSecureField: true, text: $password)
}
.padding(32)
Button {
viewModel.register(withEmail: email, password: password, fullname: fullName, username: username, isVerified: isVerified)
} label: {
Text("Sign Up")
.font(.headline)
.foregroundColor(.white)
.frame(width: 340, height: 50)
.background(Color("AppGreen"))
.clipShape(Capsule())
.padding()
}
.shadow(color: .gray.opacity(0.5), radius: 10, x:0, y:0)
Spacer()
Button {
presentationMode.wrappedValue.dismiss()
} label: {
HStack {
Text("Already Have And Account?")
.font(.caption)
Text("Sign In")
.font(.footnote)
.fontWeight(.semibold)
}
}
.padding(.bottom, 32)
.foregroundColor(Color("AppGreen"))
}
.navigationDestination(for: Bool.self) { _ in // <-- here
ProfilePhotoSelectorView()
}
.ignoresSafeArea()
.preferredColorScheme(.dark)
}
}
class AuthViewModel: ObservableObject {
#Published var userSession: Firebase.User?
#Published var didAuthenticateUser: [Bool] = [] // <-- here
init() {
self.userSession = Auth.auth().currentUser
print("DEBUG: User session is \(String(describing: self.userSession?.uid))")
}
func login(withEmail email: String, password: String){
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to sign in with error\(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
self.userSession = user
print("Did log user in")
}
}
func register(withEmail email: String, password: String, fullname: String, username: String, isVerified: Bool){
Auth.auth().createUser(withEmail: email, password: password) { result, error in
if let error = error {
print("DEBUG: Failed to register with error\(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
print("DEBUG: Registerd User Succesfully")
let data = ["email": email, "username" :username.lowercased(), "fullname": fullname, "isVerified": isVerified, "uid": user.uid]
Firestore.firestore().collection("users")
.document(user.uid)
.setData(data) { _ in
self.didAuthenticateUser = [true] // <-- here
}
}
}
func signOut() {
userSession = nil
try? Auth.auth().signOut()
}
}
struct ProfilePhotoSelectorView: View {
var body: some View {
VStack {
AuthHeaderView(title1: "Account Creation:", title2: "Add A Profile Picture")
Button {
print("Pick Photo Here")
} label: {
VStack{
Image(systemName: "globe")
.resizable()
.renderingMode(.template)
.frame(width: 180, height: 180)
.scaledToFill()
.padding(.top, 44)
.foregroundColor(Color("AppGreen"))
Text("Tap To Add Photo")
.font(.title3).bold()
.padding(.top, 10)
.foregroundColor(Color("AppGreen"))
}
}
Spacer()
}
.ignoresSafeArea()
.preferredColorScheme(.dark)
}
}

You can only have one level of NavigationLinks unless you mark the links as isDetail(false) or use .navigationStyle(.stack).
The reason is because in landscape it uses a split view and the link replaces the large detail pane on the right.

Related

How can I give a swiftUI button multiple functions when pressed?

I created a page for users to register new accounts. I created a "continue" button that is meant to push the new data to firebase and simultaneously move the users to the next view which is my mapView(). Right now the signup fucntion is working, but I cant figure out how to implement the mapView()
Button(action:{
guard !email.isEmpty, !password.isEmpty else {
return
}
viewModel.signUp(email: email, password: password)
} , label: {
Text("Continue")
.foregroundColor(.white)
.frame(width: 350, height: 35)
.background(Color.blue)
.cornerRadius(20)
})
}
Ive tried adding map view inside of the function but Xcode returns a warning that says "Result of 'mapView' initializer is unused".
Button(action:{
guard !email.isEmpty, !password.isEmpty else {
return
}
viewModel.signUp(email: email, password: password)
mapView()
} , label: {
Text("Continue")
.foregroundColor(.white)
.frame(width: 350, height: 35)
.background(Color.blue)
.cornerRadius(20)
})
There are a few ways to pull this off, but using a NavigationStack works really well. Something like this:
//
// ContentView.swift
// animation-example
//
// Created by Andrew Carter on 12/15/22.
//
import SwiftUI
enum SignUpFlow {
case name
case age
case welcome
}
struct SignUpInfo {
var name: String
var age: String
}
struct NameView: View {
#Binding var name: String
var body: some View {
VStack {
TextField("Name", text: $name)
NavigationLink("Next", value: SignUpFlow.age)
.disabled(name.isEmpty)
}
}
}
struct AgeView: View {
#Binding var age: String
var body: some View {
VStack {
TextField("Age", text: $age)
NavigationLink("Next", value: SignUpFlow.welcome)
.disabled(age.isEmpty)
}
}
}
struct WelcomeView: View {
var body: some View {
Text("Welcome")
}
}
struct ContentView: View {
#State private var path: [SignUpFlow] = []
#State private var signUpInfo = SignUpInfo(name: "", age: "")
var body: some View {
NavigationStack(path: $path) {
NavigationLink("Create Account", value: SignUpFlow.name)
.navigationDestination(for: SignUpFlow.self) { flow in
switch flow {
case .age:
AgeView(age: $signUpInfo.age)
case .name:
NameView(name: $signUpInfo.name)
case .welcome:
WelcomeView()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In the example above with mapView() just placed in the buttons action, it's unused because, well, it's unused. The Button closure expects a Void return, you're just making a map view than instantly throwing it away- it's not used in any way.

Why does the value change between views? SwiftUi

I am creating an app in which there is a list of users, with an associated button for each user that leads to another screen. This screen should display all the data of the selected user.
struct UserList: View {
#StateObject var administratorManager = AdministratorManager()
#State var ricerca = ""
#State var isPresented: Bool = false
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
ForEach(administratorManager.users.sorted(using: [
KeyPathComparator(\.surname, order: .forward)
])) { user in
if user.toCheck.contains(where: {$0.range(of: ricerca.lowercased()) != nil}) || user.toCheck.contains(where: {$0.range(of: ricerca.capitalized) != nil}) || ricerca.isEmpty {
VStack(alignment: .leading) {
Text(user.number).font(.subheadline)
Text(user.name).font(.subheadline)
Text(user.surname).font(.subheadline)
}.background(Color("Purple"))
.cornerRadius(5)
// Button to change screen ---------------------------------------------------------------------------------------------------
Button {
isPresented.toggle()
administratorManager.infoUtente[2] = user.number
} label: {
Text("Tap me!")
.padding()
.background(Color.white)
.cornerRadius(20)
}.padding(1)
// ---------------------------------------------------------------------------------------------------------------------------
}
}.navigationBarTitle("User List")
.searchable(text: $ricerca, prompt: "Search")
.onAppear() {
administratorManager.fetchData(collection: "Users")
administratorManager.checkPermission()
}
.alert(isPresented: $administratorManager.isMessage) {
Alert(title: Text(administratorManager.title), message: Text(administratorManager.message),
dismissButton: .default(Text("Ho capito!")))
}
.sheetWithDetents(
isPresented: $isPresented,
detents: [.medium(),.large()]
) {
print("The sheet has been dismissed")
} content: {
Group {
userInfo()
}
.font(.title)
}
}
}
}
This is the screen where the user information will be displayed:
struct userInfo: View {
// Oggetti.
#StateObject var administratorManager = AdministratorManager() // Oggetto riguardante i comandi riservati agli admin.
var body: some View {
HStack {
Text("Nome: \(administratorManager.infoUtente[0])")
.padding(.horizontal, 5)
Text("Cogome: \(administratorManager.infoUtente[1])")
.padding(.horizontal, 5)
}.padding(1)
Text("Numero: \(administratorManager.infoUtente[2])")
.padding(.horizontal, 5)
Button("print", action: {
print(administratorManager.infoUtente[2])
}).padding(1)
}
}
If I print in the console the value of administratorManager.infoUser [2] is empty.
I've recently been using Swift, and I haven't found anything to fix it. How can I solve?
You have two different AdministratorManager instances. One in UserList, second in userInfo. They are not connected and have different data.
Change to this in userInfo
#EnvironmentObject var administratorManager: AdministratorManager
And in UserList pass environment object to next screen
Group {
userInfo()
.environmentObject(administratorManager)
}

How can i make an individual button for each user so when its pressed it toggles the isFollowingUser Bool and changes to following?

Currently since its in a ForEach loop and i only have one State var, when i press follow they all change to following. I used a ternary operator to make the button color and text switch if the isFollowing is toggled. How would i go about making it toggle only for a specified user when the button is clicked. Would i just need to make 3 buttons outside the loop? When i used user.isFollowingUser.toggle in the button it tells me that I cant mutate user.
import SwiftUI
struct InstagramModel: Identifiable{
let id: String = UUID().uuidString
let username: String
let userImage: String
let followers: Int
let isVerified: Bool
let isFollowingUser: Bool
}
struct ModelPractice: View {
#State private var users: [InstagramModel] = [
InstagramModel(username: "aleedagenie", userImage: "AliImage", followers: 490, isVerified: true, isFollowingUser: false),
InstagramModel(username: "nicole29", userImage: "volcano2", followers: 1090, isVerified: true, isFollowingUser: false),
InstagramModel(username: "shamat81", userImage: "crashedCar", followers: 290, isVerified: false, isFollowingUser: false)
]
#State private var isFollowing: Bool = false
#State private var isShowDialog: Bool = false
#State private var background: Color = .mint
var body: some View {
NavigationView{
VStack{
List{
Section {
ForEach(users) { user in
VStack(alignment: .leading) {
HStack {
Image(user.userImage)
.resizable()
.frame(width: 35, height: 35)
.clipShape(Circle())
VStack(alignment: .leading) {
Text(user.username)
.font(.headline)
HStack {
Text("Followers:")
.font(.caption)
Text("\(user.followers)")
.font(.caption)
}
}
Spacer()
if user.isVerified{
Image(systemName: "checkmark.seal.fill")
.foregroundColor(.blue)
}
}
Button {
isFollowing.toggle()
} label: {
Text(isFollowing ? "Following" : "Follow")
.foregroundColor(isFollowing ? .black: .white)
.frame(maxWidth: 90)
.background(isFollowing ? .white: .blue)
.cornerRadius(12)
}
.padding(.horizontal, 44)
}
}
} header: {
Text("Instagram Users")
}
.listRowBackground(background)
}
Button {
isShowDialog.toggle()
} label: {
Text("Change Page Style")
.bold()
.frame(maxWidth: 140)
.background(.orange)
.cornerRadius(20)
}
.confirmationDialog("Text", isPresented: $isShowDialog, actions: {
Button {
background = .yellow
} label: {
Text("Option 1")
}
Button {
background = .gray
} label: {
Text("Option 2")
}
Button {
background = .green
} label: {
Text("Option 3")
}
})
.navigationTitle("Instagram")
}
}
}
}
struct ModelPractice_Previews: PreviewProvider {
static var previews: some View {
ModelPractice()
}
}
The problem here is you are trying to mutate the variable of closure called user. user is a temporary variable which is not linked with your users, so you can't mutate it.
Instead you should mutate the users.
Here is my demo, try it out. Code is below the image:
struct UserModel: Identifiable {
var id = UUID()
var name: String = ""
var following = false
}
struct DemoView: View {
#State var listUser = [
UserModel(name: "Lamb Chop", following: false),
UserModel(name: "Steak", following: false)
]
var body: some View {
List {
ForEach(listUser.indices) { index in
HStack {
Text(listUser[index].name)
Button {
listUser[index].following.toggle()
} label: {
Text(listUser[index].following ? "following" : "follow")
}
.padding(5)
.background(.black)
.cornerRadius(15)
}
}
}
}
}

SWIFTUI/ How to delay .task and let it work only if the data from textFields are passed

I just tried to make an API app in SwiftUI with loveCalculator from rapidapi.com
The problem is that API first needs names from me before it gives me the results.
My program works but fetching data form API earlier that I want (when I click to show my data in next view, first show default data, then show the data that should be displayed when I click).
Also Is it possible to initialize #Published var loveData (in LoveViewModel) without passing any default data or empty String?
Something like make the data from LoveData optional ?
Can You tell me when I make mistake?
MY CODE IS :
LoveData (for api)
struct LoveData: Codable {
let percentage: String
let result: String
}
LoveViewModel
class LoveViewModel: ObservableObject {
#Published var loveData = LoveData(percentage: "50", result: "aaa")
let baseURL = "https://love-calculator.p.rapidapi.com/getPercentage?"
let myApi = "c6c134a7f0msh980729b528fe273p1f337fjsnd17137cb2f24"
func loveCal (first: String, second: String) async {
let completedurl = "\(baseURL)&rapidapi-key=\(myApi)&sname=\(first)&fname=\(second)"
guard let url = URL(string: completedurl) else {return }
do {
let decoder = JSONDecoder()
let (data, _) = try await URLSession.shared.data(from: url)
if let safeData = try? decoder.decode(LoveData.self, from: data) {
print("succesfully saved data")
self.loveData = safeData
}
} catch {
print(error)
}
}
}
LoveCalculator View
struct LoveCalculator: View {
#State var firstName = ""
#State var secondName = ""
#ObservedObject var loveViewModel = LoveViewModel()
var body: some View {
NavigationView {
ZStack{
Color(.systemTeal).ignoresSafeArea()
VStack(alignment: .center) {
Text("Love Calculator")
.padding(.top, 100)
Text("Enter first name:")
.padding()
TextField("first name", text: $firstName)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
Text("Enter second name:")
.padding()
TextField("second name", text: $secondName, onEditingChanged: { isBegin in
if isBegin == false {
print("finish get names")
}
})
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
NavigationLink {
LoveResults(percentage: loveViewModel.loveData.percentage, description: loveViewModel.loveData.result)
} label: {
Text("CHECK IT!")
}
Spacer()
}
}
.task {
await loveViewModel.loveCal(first: firstName, second: secondName)
}
}
}
}
LoveResults View
struct LoveResults: View {
var percentage: String
var description: String
var body: some View {
ZStack{
Color(.green).ignoresSafeArea()
VStack{
Text("RESULTS :")
.padding()
Text(percentage)
.font(.system(size: 80, weight: .heavy))
.foregroundColor(.red)
.padding()
Text(description)
}
}
}
}
Thanks for help!
Regards,
Michal
.task is the same modifier as .onAppear in terms of view lifecycle, meaning it will fire immediately when the view appears. You have to move your API call to a more controlled place, where you can call it once you have all the required data.
If you want to fire the API request only after both names are entered, you can create a computed variable, that checks for desired state of TextFields and when that variable turns to true, then you call the API.
Like in this example:
struct SomeView: View {
#State var firstName: String = ""
#State var secondName: String = ""
var namesAreValid: Bool {
!firstName.isEmpty && !secondName.isEmpty // << validation logic here
}
var body: some View {
Form {
TextField("Enter first name", text: $firstName)
TextField("Enter second name", text: $secondName)
}
.onChange(of: namesAreValid) { isValid in
if isValid {
// Call API
}
}
}
}
You can also set your loveData to optional using #Published var loveData: LoveData? and disable/hide the navigation link, until your data is not nil. In that case, you might need to provide default values with ?? to handle optional string errors in LoveResults view initializer
It was very helpful but still not enough for me, .onChange work even if I was type 1 letter in my Form.
I find out how to use Task { } and .onCommit on my TextField, now everything working well !
My code now looks like that :
LoveCalculator View :
struct LoveCalculator: View {
#State var firstName = ""
#State var secondName = ""
var namesAreValid: Bool {
!firstName.isEmpty && !secondName.isEmpty
}
func makeApiCall() {
if namesAreValid {
DispatchQueue.main.async {
Task {
await self.loveViewModel.loveCal(first: firstName, second: secondName)
}
}
}
}
#ObservedObject var loveViewModel = LoveViewModel()
var body: some View {
NavigationView {
ZStack{
Color(.systemTeal).ignoresSafeArea()
VStack(alignment: .center) {
Text("Love Calculator")
.padding(.top, 100)
Text("Enter first name:")
.padding()
TextField("first name", text: $firstName, onCommit: {makeApiCall()})
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
Text("Enter second name:")
.padding()
TextField("second name", text: $secondName, onCommit: {
makeApiCall()
})
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
NavigationLink {
LoveResults(percentage: loveViewModel.loveData.percentage ?? "", description: loveViewModel.loveData.result ?? "")
} label: {
Text("CHECK IT!")
}
Spacer()
}
}
}
}
}
Thank You one more time for very helpful tip!
Peace!
MichaƂ ;)
Editing :
Just look at Apple documentation and I see that they say that .onCommit is deprecated whatever it means.
So instead of this I use .onSubmit and works the same !
TextField("second name", text: $secondName)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
.onSubmit {
makeApiCall()
}
Peace! :)

Swift: Navigating user after successfully signing up is not working?

I have a login and sign up view. If the user successfully logins they are taken to the homeMainView via NavigationLink, if they need to signup, the signup view is presented with a sheet. Authentication on both login and signup works fine but when the signup sheet is presented and the user successfully sign in, I can't get the view to Navlink to homeMainView from the presented sheet. The view navigate fine from the login view but not from the sign up. I tried couple of different methods but not success. Here is my code
struct LoginView: View {
#State private var email = ""
#State private var password = ""
#State private var formOffset: CGFloat = 0
#State private var presentSignupSheet = false
#State private var presentPasswordRecoverySheet = false
#State var isNavigationBarHidden: Bool = true
#State var isLinkActive = false // <- add here
var body: some View {
NavigationView { // <- wrap in the `NavigationView`
VStack(spacing: 40) {
VStack {
LCTextfield(value: self.$email, placeholder: "Email", icon: Image(systemName: "at"), onEditingChanged: { flag in
withAnimation {
self.formOffset = flag ? -150 : 0
}
})
LCTextfield(value: self.$password, placeholder: "Password", icon: Image(systemName: "lock"), isSecure: true)
LCButton(text: "Login") {
self.handleLogin(username: self.email, password: self.password)
}
}
Button(action: {
self.presentSignupSheet.toggle()
}) {
HStack {
Text("Don't have an account? Sign up.").accentColor(Color.PrimaryColor)
}
}.sheet(isPresented: self.$presentSignupSheet) {
SignupView()
}
Button(action: {
self.presentPasswordRecoverySheet.toggle()
}) {
HStack {
Text("Forgot your password?").accentColor(Color.PrimaryColor)
}
}.sheet(isPresented: self.$presentPasswordRecoverySheet) {
RecoverPasswordView(presentPasswordRecoverySheet: self.$presentPasswordRecoverySheet)
}
}.padding().offset(y: self.formOffset)
.background(Color.snowColor).edgesIgnoringSafeArea(.all)
.background( // add a hidden `NavigationLink` in the background
NavigationLink(destination: homeMainView(), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}.navigationBarTitle("", displayMode: .inline) // optionally set title
.navigationBarHidden(self.isNavigationBarHidden)
.onAppear {
self.isNavigationBarHidden = true
}
}
when the signup is button is clicked it brings up the signup view
struct SignupView: View {
#State private var fname = ""
#State private var lname = ""
#State private var email = ""
#State private var password = ""
#State private var confirmedPassword = ""
#Environment(\.presentationMode) var presentationMode
#State private var formOffset: CGFloat = 0
#State var isLinkActive = false // <- add here
var body: some View {
VStack(spacing: 40) {
VStack {
HStack {
LCTextfield(value: self.$fname, placeholder: "First Name")
LCTextfield(value: self.$lname, placeholder: "Last Name")
}
LCTextfield(value: self.$email, placeholder: "Email", icon: Image(systemName: "at"), onEditingChanged: { flag in
withAnimation {
self.formOffset = flag ? -150 : 0
}
})
LCTextfield(value: self.$password, placeholder: "Password", icon: Image(systemName: "lock"), isSecure: true)
LCButton(text: "Sign up") {
self.handleSignUp(username: self.email, password: self.password)
}
}
Button(action: {
}) {
HStack {
Text("Already have an account?").accentColor(Color.accentColor)
}
}
}.padding().offset(y: self.formOffset)
.navigationBarTitle("", displayMode: .inline) // optionally set title
.navigationBarHidden(true)
.background( // add a hidden `NavigationLink` in the background
NavigationLink(destination: homeMainView(), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
.background(Color.snowColor).edgesIgnoringSafeArea(.all)
}
func handleSignUp(username: String, password: String) {
app.usernamePasswordProviderClient().registerEmail(email, password: password, completion: { (error) in
// Completion handlers are not necessarily called on the UI thread.
// This call to DispatchQueue.main.sync ensures that any changes to the UI,
print("Signup successful!")
// Registering just registers. Now we need to sign in, but we can reuse the existing username and password.
self.presentationMode.wrappedValue.dismiss()
LoginView().handleLogin(username: self.email, password: self.password)
self.isLinkActive = true // <- activate the `NavigationLink`
}
})
}
what is the best way to navigate the user to the homeMainView once they have successfully signup?