I want to show my new view after the success login process with Firebase, my signUp and Recover password are already working because I'm showing it as a sheet but in this one, I want to show a new view, I have tried with NavigationLink, with onReceive but I have been unable to do this work.
struct LoginView: View {
#ObservedObject var viewModel = ViewModel()
#State private var formOffset: CGFloat = 0
#State private var presentSignUpSheet = false
#State private var presentPasswordRecoverySheet = false
#State private var presentLobbySheet = false
var body: some View {
VStack {
HeaderView(title: Constants.appName)
Spacer()
Divider()
Group {
BodyView(value: viewModel).viewSelection(view: Constants.QuestionnaireView.signIn.rawValue)
LCButton(text: Constants.login) {
self.viewModel.signIn()
}.alert(isPresented: $viewModel.thereIsAnError) {
Alert(title: Text(Constants.alert), message: Text(viewModel.errorMessage), dismissButton: .default(Text(Constants.ok)))
}
Button(action: {
self.presentSignUpSheet.toggle()
}) {
Text(Constants.signUp)
}.sheet(isPresented: $presentSignUpSheet) {
SignUpView()
}.padding()
Button(action: {
self.presentPasswordRecoverySheet.toggle()
}) {
Text(Constants.forgotPassword)
}.sheet(isPresented: $presentPasswordRecoverySheet) {
RecoverPasswordView()
}.padding()
}
}.edgesIgnoringSafeArea(.top)
.padding()
.offset(y: self.formOffset)
}
}
class ViewModel: ObservableObject {
#Published var user = User()
#Published var confirmPassword = ""
#Published var thereIsAnError = false
#Published var errorMessage = ""
var viewDismissalModePublisher = PassthroughSubject<Bool, Never>()
var onSuccessLogin = PassthroughSubject<Bool, Never>()
private var shouldPopView = false {
didSet {
viewDismissalModePublisher.send(shouldPopView)
}
}
private var shouldShowLobbyView = false {
didSet {
onSuccessLogin.send(shouldShowLobbyView)
}
}
func registerSuccess() {
self.user.email = ""
self.user.password = ""
self.confirmPassword = ""
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.shouldPopView = true
}
}
func signUpProcess() {
if user.password != confirmPassword {
errorMessage = Constants.passConfirmWrong
thereIsAnError.toggle()
} else {
signUp()
}
}
func signUp() {
Auth.auth().createUser(withEmail: user.email, password: user.password) { (result, error) in
if error != nil {
self.errorMessage = error!.localizedDescription
self.thereIsAnError.toggle()
} else {
self.registerSuccess()
}
}
}
func signIn() {
Auth.auth().signIn(withEmail: user.email, password: user.password) { (result, error) in
if error != nil {
self.errorMessage = error!.localizedDescription
self.thereIsAnError.toggle()
} else {
self.shouldShowLobbyView.toggle()
self.user.email = ""
self.user.password = ""
}
}
}
func recoverPassword() {
Auth.auth().sendPasswordReset(withEmail: user.email) { (error) in
if error != nil {
self.errorMessage = error!.localizedDescription
self.thereIsAnError.toggle()
} else {
self.user.email = ""
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.shouldPopView = true
}
}
}
}
}
I had a similar problem and I found a solution that mimics some navigation
I was going to write the basics here but nothing better then posting the source with explanation.
ViewRouter Tutorial
This consists in a ObservableObject as EnvironmentObject and it allows you to show views in full screen, rather than sheets.
In your case you would have a LoginView and in this view you could open sheets for the Signup and Recover Password views and the LobbyView opening as fullscreen view, like it would in UIKit with the present method
Related
I've had a problem on and off for the past week where my else statement is executing in the MainTabView upon login/signup (meaning it can't find the currentuser??) So rather than logging in and showing the main navigation, I see a white "loading.." screen after logging in. It's odd cause some log in's have worked fine and others crash the app. Any help is very appreciated!
I don't think the problem is within EmailAuth or CreateAccountAuth but let me know if you'd like to see the code for those too.
AuthViewModel:
import SwiftUI
import FirebaseAuth
import FirebaseCore
import FirebaseStorage
import FirebaseFirestore
import FirebaseFirestoreSwift
class AuthViewModel: NSObject, ObservableObject {
#Published var userSession: FirebaseAuth.User?
#Published var currentUser: User?
#Published var selectedImage: UIImage?
private let service = UserService()
static let shared = AuthViewModel()
override init() {
super.init()
userSession = Auth.auth().currentUser
fetchUser()
}
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
}
self.userSession = result?.user
self.fetchUser()
}
}
func register(withEmail email: String, password: String, fullname: String) {
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 }
self.userSession = user
let data: [String: Any] = ["email": email,
"fullname": fullname]
COLLECTION_USERS
.document(user.uid)
.setData(data)
self.uploadProfileImage(self.selectedImage)
}
}
func signOut() {
// sets user session to nil so we show login view
self.userSession = nil
// signs user out on server
try? Auth.auth().signOut()
}
func uploadProfileImage(_ image: UIImage?) {
guard let uid = userSession?.uid else { return }
ImageUploader.uploadImage(image: image) { profileImageUrl in
COLLECTION_USERS
.document(uid)
.updateData(["profileImageUrl": profileImageUrl])
//{ _ in self.userSession = user }
}
}
func fetchUser() {
guard let uid = userSession?.uid else { return }
COLLECTION_USERS.document(uid).getDocument { snapshot, _ in
guard let user = try? snapshot?.data(as: User.self) else { return }
self.currentUser = user
}
}
}
App File:
struct Page_TurnerApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
NavigationView {
ContentView().environmentObject(AuthViewModel())
}
}
}
}
ContentView
struct ContentView: View {
#EnvironmentObject var viewModel: AuthViewModel
var body: some View {
Group {
if viewModel.userSession != nil {
MainTabView()
} else {
EmailAuth()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
MainTabView
struct MainTabView: View {
#State private var selectedIndex = 0
#EnvironmentObject var viewModel: AuthViewModel
var body: some View {
if let user = viewModel.currentUser {
TabView(selection: $selectedIndex) {
ExploreView()
.onTapGesture {
self.selectedIndex = 0
}
.tabItem {
Image(systemName: "house")
}.tag(0)
SearchView()
.onTapGesture {
self.selectedIndex = 1
}
.tabItem {
Image(systemName: "magnifyingglass")
}.tag(1)
ConversationsView()
.onTapGesture {
self.selectedIndex = 2
}
.tabItem {
Image(systemName: "message")
}.tag(2)
AccountView(user: user)
.onTapGesture {
self.selectedIndex = 3
}
.tabItem {
Image(systemName: "person.crop.circle")
}.tag(3)
}
} else {
Text("loading...")
}
}
}
My problem was that my User wasn't being created unless there was a profile image. That's why it worked for certain users and crashed for others. I changed let profileImageUrl: String
to let profileImageUrl: String? and all users can log in now
Struggling to get a simple example up and running in swiftui:
Load default list view (working)
click button that launches picker/filtering options (working)
select options, then click button to dismiss and call function with selected options (call is working)
display new list of objects returned from call (not working)
I'm stuck on #4 where the returned query isn't making it to the view. I suspect I'm creating a different instance when making the call in step #3 but it's not making sense to me where/how/why that matters.
I tried to simplify the code some, but it's still a bit, sorry for that.
Appreciate any help!
Main View with HStack and button to filter with:
import SwiftUI
import FirebaseFirestore
struct TestView: View {
#ObservedObject var query = Query()
#State var showMonPicker = false
#State var monFilter = "filter"
var body: some View {
VStack {
HStack(alignment: .center) {
Text("Monday")
Spacer()
Button(action: {
self.showMonPicker.toggle()
}, label: {
Text("\(monFilter)")
})
}
.padding()
ScrollView(.horizontal) {
LazyHStack(spacing: 35) {
ForEach(query.queriedList) { menuItems in
MenuItemView(menuItem: menuItems)
}
}
}
}
.sheet(isPresented: $showMonPicker, onDismiss: {
//optional function when picker dismissed
}, content: {
CuisineTypePicker(selectedCuisineType: $monFilter)
})
}
}
The Query() file that calls a base query with all results, and optional function to return specific results:
import Foundation
import FirebaseFirestore
class Query: ObservableObject {
#Published var queriedList: [MenuItem] = []
init() {
baseQuery()
}
func baseQuery() {
let queryRef = Firestore.firestore().collection("menuItems").limit(to: 50)
queryRef
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.queriedList = querySnapshot?.documents.compactMap { document in
try? document.data(as: MenuItem.self)
} ?? []
}
}
}
func filteredQuery(category: String?, glutenFree: Bool?) {
var filtered = Firestore.firestore().collection("menuItems").limit(to: 50)
// Sorting and Filtering Data
if let category = category, !category.isEmpty {
filtered = filtered.whereField("cuisineType", isEqualTo: category)
}
if let glutenFree = glutenFree, !glutenFree {
filtered = filtered.whereField("glutenFree", isEqualTo: true)
}
filtered
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.queriedList = querySnapshot?.documents.compactMap { document in
try? document.data(as: MenuItem.self);
} ?? []
print(self.queriedList.count)
}
}
}
}
Picker view where I'm calling the filtered query:
import SwiftUI
struct CuisineTypePicker: View {
#State private var cuisineTypes = ["filter", "American", "Chinese", "French"]
#Environment(\.presentationMode) var presentationMode
#Binding var selectedCuisineType: String
#State var gfSelected = false
let query = Query()
var body: some View {
VStack(alignment: .center) {
//Buttons and formatting code removed to simplify..
}
.padding(.top)
Picker("", selection: $selectedCuisineType) {
ForEach(cuisineTypes, id: \.self) {
Text($0)
}
}
Spacer()
Button(action: {
self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text( "apply filters")
})
}
.padding()
}
}
I suspect that the issue stems from the fact that you aren't sharing the same instance of Query between your TestView and your CuisineTypePicker. So, when you start a new Firebase query on the instance contained in CuisineTypePicker, the results are never reflected in the main view.
Here's an example of how to solve that (with the Firebase code replaced with some non-asynchronous sample code for now):
struct MenuItem : Identifiable {
var id = UUID()
var cuisineType : String
var title : String
var glutenFree : Bool
}
struct ContentView: View {
#ObservedObject var query = Query()
#State var showMonPicker = false
#State var monFilter = "filter"
var body: some View {
VStack {
HStack(alignment: .center) {
Text("Monday")
Spacer()
Button(action: {
self.showMonPicker.toggle()
}, label: {
Text("\(monFilter)")
})
}
.padding()
ScrollView(.horizontal) {
LazyHStack(spacing: 35) {
ForEach(query.queriedList) { menuItem in
Text("\(menuItem.title) - \(menuItem.cuisineType)")
}
}
}
}
.sheet(isPresented: $showMonPicker, onDismiss: {
//optional function when picker dismissed
}, content: {
CuisineTypePicker(query: query, selectedCuisineType: $monFilter)
})
}
}
class Query: ObservableObject {
#Published var queriedList: [MenuItem] = []
private let allItems: [MenuItem] = [.init(cuisineType: "American", title: "Hamburger", glutenFree: false),.init(cuisineType: "Chinese", title: "Fried Rice", glutenFree: true)]
init() {
baseQuery()
}
func baseQuery() {
self.queriedList = allItems
}
func filteredQuery(category: String?, glutenFree: Bool?) {
queriedList = allItems.filter({ item in
if let category = category {
return item.cuisineType == category
} else {
return true
}
}).filter({item in
if let glutenFree = glutenFree {
return item.glutenFree == glutenFree
} else {
return true
}
})
}
}
struct CuisineTypePicker: View {
#ObservedObject var query : Query
#Binding var selectedCuisineType: String
#State private var gfSelected = false
private let cuisineTypes = ["filter", "American", "Chinese", "French"]
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack(alignment: .center) {
//Buttons and formatting code removed to simplify..
}
.padding(.top)
Picker("", selection: $selectedCuisineType) {
ForEach(cuisineTypes, id: \.self) {
Text($0)
}
}
Spacer()
Button(action: {
self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text( "apply filters")
})
}
}
On Xcode 13 Beta 3, I am trying to find a good solution for an edit detail view presented in a sheet that needs to explicitly be confirmed.
In the DetailEditView, I initialise a #State property (editingModel) which is initialised from a #Binding (model) that I hand down.
struct DetailEditView: View {
#Binding var model: Model
#Binding var isEditing: Bool
#State private var editingModel: Model
init(model: Binding<Model>, isEditing: Binding<Bool>) {
self._model = model
self._isEditing = isEditing
self._editingModel = State(initialValue: model.wrappedValue)
}
//...
When I tap/press the confirm button in my sheet, I want to assign the altered editingModel to the passed model.
Button {
#warning("My expectation (saving changes by assigning `editingModel` to `model`) fails here…")
model = editingModel
isEditing = false
} label: {
Text("Done")
}
//...
While I do not have any build errors, the code does not work as expected–and I don't understand why. Look out for my #warning: that's where my code does not work as expected.
For all I know this could be a bug in the Xcode 13 Beta–or am I misunderstanding something fundamentally?
Here's all the code:
import SwiftUI
//MARK: - Main
#main
struct so_multipleSheetsApp: App {
#StateObject private var modelStore = ModelStore()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(modelStore)
}
}
}
//MARK: - Views
struct ContentView: View {
#SceneStorage("selection") var selection: Model.ID?
var body: some View {
NavigationView {
SidebarView(selection: $selection)
DetailView(modelSelection: $selection)
}
}
}
struct SidebarView: View {
#EnvironmentObject var modelStore: ModelStore
#Binding var selection: Model.ID?
var body: some View {
List {
ForEach($modelStore.models) { $modelItem in
NavigationLink {
DetailView(modelSelection: $selection)
} label: {
Text(modelItem.id)
}
}
}
}
}
struct DetailView: View {
#EnvironmentObject var modelStore: ModelStore
#Binding var modelSelection: Model.ID?
#State private var isEditing = false
var body: some View {
Form {
Text(modelBinding.wrappedValue.id)
}
.sheet(isPresented: $isEditing) {
DetailEditView(model: modelBinding, isEditing: $isEditing)
}
.toolbar {
ToolbarItem {
Button {
isEditing = true
} label: {
Label("Edit", systemImage: "pencil")
}
}
}
}
var modelBinding: Binding<Model> {
$modelStore[modelSelection]
}
}
struct DetailEditView: View {
#Binding var model: Model
#Binding var isEditing: Bool
#State private var editingModel: Model
init(model: Binding<Model>, isEditing: Binding<Bool>) {
self._model = model
self._isEditing = isEditing
self._editingModel = State(initialValue: model.wrappedValue)
}
var body: some View {
VStack {
Form {
TextField("Model Id", text: $editingModel.id)
}
Spacer()
Divider()
HStack {
Button {
isEditing = false
} label: {
Text("Cancel")
}
Spacer()
Button {
#warning("My expectation (saving changes by assigning `editingModel` to `model`) fails here…")
model = editingModel
isEditing = false
} label: {
Text("Done")
}
}
.padding()
}
}
}
//MARK: - Store
class ModelStore: ObservableObject {
#Published var models: [Model] = Model.mockModelArray()
subscript(modelId: Model.ID?) -> Model {
get {
if let id = modelId {
if let modelIndex = models.firstIndex(where: { $0.id == id }) {
return models[modelIndex]
}
}
if models.isEmpty {
return Model(id: UUID().uuidString)
} else {
return models[0]
}
}
set(newValue) {
if let id = modelId {
if let modelIndex = models.firstIndex(where: { $0.id == id }) {
models[modelIndex] = newValue
}
}
}
}
}
//MARK: - Models
struct Model: Identifiable {
var id: String
static func mockModel() -> Model {
Model(id: UUID().uuidString)
}
static func mockModelArray() -> [Model] {
var array = [Model]()
for _ in 0..<5 {
array.append(mockModel())
}
return array
}
}
At first, do not edit id of Model. Instead use a new property and edit it.
//MARK: - Models
struct Model: Identifiable {
let id = UUID()
var content: String
static func mockModel() -> Model {
Model(content: UUID().uuidString)
}
static func mockModelArray() -> [Model] {
var array = [Model]()
for _ in 0..<5 {
array.append(mockModel())
}
return array
}
}
For the first time you are in DetailView, selected model is not among the $modelStore.models. You need to send the first object of `` to the DetailsView.
#main
struct so_multipleSheetsApp: App {
#StateObject private var modelStore = ModelStore()
var body: some Scene {
WindowGroup {
ContentView(selection: $modelStore.models.first!)
.environmentObject(modelStore)
}
}
}
When you choose a model from SidebarView, the model in DetailView does not get updated. Send $modelItem to DetailView instead.
struct SidebarView: View {
#EnvironmentObject var modelStore: ModelStore
var body: some View {
List {
ForEach($modelStore.models) { $modelItem in
NavigationLink {
DetailView(modelSelection: $modelItem)
} label: {
Text(modelItem.content)
}
}
}
}
}
In DetailView, remove modelBinding and send modelSelection to DetailEditView.
struct DetailEditView: View {
#Binding var model: Model
#Binding var isEditing: Bool
#State private var editingModel: Model
init(model: Binding<Model>, isEditing: Binding<Bool>) {
self._model = model
self._isEditing = isEditing
self._editingModel = State(initialValue: model.wrappedValue)
}
var body: some View {
VStack {
Form {
TextField("Model Id", text: $editingModel.content)
}
Spacer()
Divider()
HStack {
Button {
isEditing = false
} label: {
Text("Cancel")
}
Spacer()
Button {
model = editingModel
isEditing = false
} label: {
Text("Done")
}
}
.padding()
}
}
}
All the code
#main
struct so_multipleSheetsApp: App {
#StateObject private var modelStore = ModelStore()
var body: some Scene {
WindowGroup {
ContentView(selection: $modelStore.models.first!)
.environmentObject(modelStore)
}
}
}
//MARK: - Views
struct ContentView: View {
#Binding var selection: Model
var body: some View {
NavigationView {
SidebarView()
DetailView(modelSelection: $selection)
}
}
}
struct SidebarView: View {
#EnvironmentObject var modelStore: ModelStore
var body: some View {
List {
ForEach($modelStore.models) { $modelItem in
NavigationLink {
DetailView(modelSelection: $modelItem)
} label: {
Text(modelItem.content)
}
}
}
}
}
struct DetailView: View {
#EnvironmentObject var modelStore: ModelStore
#Binding var modelSelection: Model
#State private var isEditing = false
var body: some View {
Form {
Text(modelSelection.content)
}
.sheet(isPresented: $isEditing) {
DetailEditView(model: $modelSelection, isEditing: $isEditing)
}
.toolbar {
ToolbarItem {
Button {
isEditing = true
} label: {
Label("Edit", systemImage: "pencil")
}
}
}
}
}
struct DetailEditView: View {
#Binding var model: Model
#Binding var isEditing: Bool
#State private var editingModel: Model
init(model: Binding<Model>, isEditing: Binding<Bool>) {
self._model = model
self._isEditing = isEditing
self._editingModel = State(initialValue: model.wrappedValue)
}
var body: some View {
VStack {
Form {
TextField("Model Id", text: $editingModel.content)
}
Spacer()
Divider()
HStack {
Button {
isEditing = false
} label: {
Text("Cancel")
}
Spacer()
Button {
model = editingModel
isEditing = false
} label: {
Text("Done")
}
}
.padding()
}
}
}
//MARK: - Store
class ModelStore: ObservableObject {
#Published var models: [Model] = Model.mockModelArray()
subscript(modelId: Model.ID?) -> Model {
get {
if let id = modelId {
if let modelIndex = models.firstIndex(where: { $0.id == id }) {
return models[modelIndex]
}
}
if models.isEmpty {
return Model(content: UUID().uuidString)
} else {
return models[0]
}
}
set(newValue) {
if let id = modelId {
if let modelIndex = models.firstIndex(where: { $0.id == id }) {
models[modelIndex] = newValue
}
}
}
}
}
//MARK: - Models
struct Model: Identifiable {
let id = UUID()
var content: String
static func mockModel() -> Model {
Model(content: UUID().uuidString)
}
static func mockModelArray() -> [Model] {
var array = [Model]()
for _ in 0..<5 {
array.append(mockModel())
}
return array
}
}
Now upon confirmation the selected model is edited in all the views.
I probably worked too much today..
In this tiny app when the button is clicked a new view must appear after 3 seconds hVideoURL variable is assigned a value (which is not NIL). So a new view (sheet) should appear with the text "IS NOT NIL"..
But for some reason when it's shown i see "wow, it's nil", which means variable still has no value.
Why i that? What am i missing?
struct ContentView: View {
#State var hVideoURL: URL? = nil
#State var isPaused: Bool = false
var body: some View {
Button("Let's Go!") {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("settings isPaused to TRUE")
self.hVideoURL = URL(string: "https://firebasestorage.googleapis.com/v0/b/fitma-e3043.appspot.com/o/flamelink%2Fmedia%2F1-horizontal.mov?alt=media&token=8f7dfc0f-0261-4a78-9eb0-6154ce1d9dfe")
print("[debug] hVideoURL = \(hVideoURL)")
self.isPaused = true
}
}
.sheet(isPresented: self.$isPaused, onDismiss: {
self.isPaused = false
print("resume playing main video")
}) {
detailedVideoView
}
}
#ViewBuilder
var detailedVideoView: some View {
if self.hVideoURL != nil {
VStack {
Text("IS NOT NIL")
}
} else {
Text("wow, it's nil")
}
}
}
I have two classes SubmitPhoneView and VerifyPhoneView.
For some reason, I noticed that whenever I input a digit into the textfield of SubmitPhoneView, it calls the init method of VerifyPhoneView. I want it to only be called once (when I press the continue button on SubmitPhoneView)
Why would this be?
Class SubmitPhoneView:
import SwiftUI
import Firebase
struct SubmitPhoneView: View {
#State private var phoneNumber: String = ""
#State private var verificationID : String = ""
#State private var presentMe = false
var body: some View {
ZStack {
Text("My number is")
HStack(spacing: 20){
Text("+1")
TextField("Enter phone number", text: $phoneNumber)
.keyboardType(.numberPad)
}
VStack {
NavigationLink(destination: VerifyPhoneView(phoneNumber: $phoneNumber.wrappedValue, verificationID: $verificationID.wrappedValue), isActive: $presentMe) { EmptyView() }
Button(action: {
self.submitPhoneNumber()
self.presentMe = true
}) {
Text("Continue")
}
}
}
func submitPhoneNumber() {
PhoneAuthProvider.provider().verifyPhoneNumber("+1" + phoneNumber, uiDelegate: nil) { (verificationID, error) in
if error != nil {
print(error.debugDescription)
return
}
else {
self.verificationID = verificationID!
}
}
}
}
Class VerifyPhoneView:
import SwiftUI
import Firebase
struct VerifyPhoneView: View {
private var phoneNumber: String
#State private var verificationID: String
#State private var verificationCode: String = ""
#State private var loginSuccesful: Bool = false
#EnvironmentObject var ls: LoginStatus
#EnvironmentObject var currentUser: CurrentUser
init(phoneNumber: String, verificationID: String) {
print("the init method was called for VerifyPhoneView")
self.phoneNumber = phoneNumber
_verificationID = State(initialValue: verificationID)
print(self.verificationID)
}
var body: some View {
ZStack {
Text("My code is")
TextField("Enter code", text: $verificationCode)
Button(action: {
self.submitVerificationCode()
}) {
Text("Continue")
}
}
}
func submitPhoneNumber() {
// doesn't matter
}
func submitVerificationCode() {
// doesn't matter
}
}
}
Use DeferView, as below
VStack {
NavigationLink(destination: DeferView {
VerifyPhoneView(phoneNumber: $phoneNumber.wrappedValue, verificationID: $verificationID.wrappedValue)
}, isActive: $presentMe) { EmptyView() }
Button(action: {
self.submitPhoneNumber()
self.presentMe = true
}) {
Text("Continue")
}