Updating #Published variable of an ObservableObject inside child view - swift

I have a published variable isLoggedIn inside a ObservableObject class as follows:
import Combine
class UserAuth: ObservableObject{
#Published var isLoggedIn: Bool = false
}
I want to update this variable to true in a particular view (LoginView). This variable determines what view I show the user depending if the user has logged in or not:
struct ContentView: View {
#ObservedObject var userAuth = UserAuth()
var body: some View {
Group{
if(userAuth.isLoggedIn){
MainView()
}else{
AccountView()
}
}
}
}
Because userAuth.isLoggedIn is false (I haven't logged in) AccountView is displayed.
AccountView:
struct AccountView: View {
#State private var toggleSheet = false
var body: some View {
VStack {
Spacer()
Button(action: {
self.toggleSheet.toggle()
}){
Text("Toggle Modal")
.padding()
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(10)
}
.sheet(isPresented: self.$toggleSheet){
LoginView()
}
Spacer()
}
}
}
Whenever the user presses the button the LoginView Modal is shown:
struct LoginView: View {
var body: some View {
VStack{
Button(action: {
return self.login()
}){
Text("Log in")
.padding()
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(10)
}
}
}
func login(){
// update UserAuth().isLoggedIn to TRUE
}
}
In the LoginView there is a button, the logic I want is for the user to press the button, login() gets called and inside that function userAuth.isLoggedIn is set to true. What would be the best way to implement this ?
I've tried to directly change the value and I get an error along the lines of:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive

Try embedding your code in DispatchQueue.main.async like this:
func login(){
DispatchQueue.main.async {
//update UserAuth().isLoggedIn to TRUE
}
}

One possibility would be to insert the UserAuth object from the ContentView into the subviews as an EnvironmentObject.
struct ContentView: View {
#ObservedObject var userAuth = UserAuth()
var body: some View {
Group {
if userAuth.isLoggedIn {
MainView()
} else {
AccountView()
.environmentObject(userAuth)
}
}
}
}
Now userAuth is accessible in AccountView and all its subviews (e.g. LoginView):
struct LoginView: View {
// Access to the environment object
#EnvironmentObject private var userAuth: UserAuth
var body: some View {
VStack {
Button(action: {
return self.login()
}) {
Text("Log in")
.padding()
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(10)
}
}
}
func login() {
// Update the value on the main thread
DispatchQueue.main.async {
self.userAuth.isLoggedIn = true
}
}
}
It may be necessary to insert the EnvironmentObject into the LoginView manually. You can do this by accessing it in the AccountView and inserting it in the LoginView:
struct AccountView: View {
#State private var toggleSheet = false
// Access the value, that was inserted in `ContentView`
#EnvironmentObject private var userAuth: UserAuth
var body: some View {
VStack {
Spacer()
Button(action: {
self.toggleSheet.toggle()
}){
Text("Toggle Modal")
.padding()
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(10)
}
.sheet(isPresented: self.$toggleSheet){
LoginView()
// Insert UserAuth object again
.environmentObject(self.userAuth)
}
Spacer()
}
}
}
Further Reading
WWDC 2019 Video | Hacking with Swift Example

Related

Using EnvironmentObject and Binding in same struct? [Beginner]

I have managed to set up a Navigationstack that I use programatically to insert/remove views. In my FindOrderView, I want to carry over the variable "ordernumber" to my LoadingScreen(View) using Binding, but I get an error saying my LoadingScreen does not conform to type 'Equatable'. What could I be doing wrong, and is there any other efficient way to get this working?
I'm a complete beginner with SwiftUi but have searched for hours before posting this here. Thankful for any help.
NavigationRouter (Class used to control layers of views)
import Foundation
import SwiftUI
enum Route: Hashable {
case ContentView
case View1
}
final class NavigationRouter: ObservableObject {
#Published var navigationPath = NavigationPath()
func pushView(route: Route) {
navigationPath.append(route)
}
func popToRootView() {
navigationPath = .init()
}
func popToSpecificView(k: Int) {
navigationPath.removeLast(k)
}
}
Mainapp (App starts here but instantly jumps to MainScreenView())
import SwiftUI
#main
struct AvhamtningApp: App {
#StateObject var router = NavigationRouter()
var body: some Scene {
WindowGroup {
NavigationStack(path: $router.navigationPath) {
MainScreenView()
.navigationDestination(for: Route.self) { route in
switch route {
case .ContentView:
MainScreenView()
case .View1:
FindOrderView()
}
}
}.environmentObject(router)
}
}
}
MainScreenView (View with button called "Get order" that carries you over to View1 (FindOrderView))
import SwiftUI
struct MainScreenView: View {
#EnvironmentObject var router: NavigationRouter
var body: some View {
VStack() {
Text("Testing")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.yellow)
.padding(.top, 50)
Spacer()
PrimaryButton(text: "Get Order")
.onTapGesture {
router.pushView(route: .View1)
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.white)).ignoresSafeArea(.all)
}
}
struct MainScreenView_Previews: PreviewProvider {
static var previews: some View {
MainScreenView()
}
}
FindOrderView (View with Keypad, if enter 7 digits then should carry you over to Loadingscreen() with the variable "ordernumber" as binding)
import SwiftUI
var list = [["1","2","3"],["4","5","6"],["7","8","9"],["","0","⌫"]]
struct FindOrderView: View {
#State private var ordernumber: String = ""
#EnvironmentObject var router: NavigationRouter
var body: some View {
VStack(spacing: 25) {
Text(ordernumber)
.font(.largeTitle)
.foregroundColor(.black)
.onChange(of: ordernumber) { newValue in
if ordernumber.count >= 7 {
router.navigationPath.append(LoadingScreen(ordernumber: $ordernumber))
}
}
Spacer()
ForEach(list.indices, id: \.self) { listindex in
HStack{
ForEach(list[listindex], id: \.self) { variableindex in
Text(variableindex)
.foregroundColor(.blue)
.font(.system(size: 60))
.onTapGesture(perform: {
if variableindex == "⌫" && !ordernumber.isEmpty {
ordernumber.removeLast()
}
else if ordernumber.count >= 7 {return}
else if variableindex == "⌫" {
()
}
else {
ordernumber += variableindex
}})
.frame(maxWidth: .infinity)
}
}
}
}.background(.white)
}
}
struct FindOrderView_Previews: PreviewProvider {
static var previews: some View {
FindOrderView()
}
}
LoadingScreen (This is where I get the error "Type 'LoadingScreen' does not conform to protocol 'Equatable'")
import SwiftUI
struct LoadingScreen: Hashable, View {
#EnvironmentObject var router: NavigationRouter
#Binding var ordernumber: String
var body: some View {
ZStack{
Text("Loading...")
.font(Font.custom("Baskerville-Bold", size: 50))
.foregroundColor(.orange)
.padding(.bottom, 200)
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .orange))
.scaleEffect(2)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.white)).ignoresSafeArea(.all)
}
}
struct LoadingScreen_Previews: PreviewProvider {
static var previews: some View {
LoadingScreen(ordernumber: .constant("constant"))
}
}
So the app starts on the MainScreenView because it's set as the root of the NavigationStack.
Your issue with navigation is that you're trying to send the view as a navigation destination value, but you need to send the route value, and switch on the navigationDestination to generate the view to be pushed.
So in your Route enum, add a route for loading:
case loading(orderNumber: String)
Then in your NavigationLink send that new route:
NavigationLink(value: Route.loadingScreen(orderNumber: orderNumber)
Then in your navigationDestination view modifier:
.navigationDestination(for: Route.self) { route in
switch route {
case .ContentView:
MainScreenView()
case .View1:
FindOrderView()
case .loading(let orderNumber):
LoadingScreen(orderNumber: orderNumber)
.environmentObject(router)
}
}
Hope this helps!

#AppStorage property wrapper prevents from dismissing views

I have an app with four (4) views, on the first view I'm showing a list of cars pulled from CoreData, the second view is presented when a car is tapped and it shows the services for each car. The third view is presented when tapping on a service, and it shows the details of the selected service. The fourth view is presented when tapping a button and it shows records for the specified service.
The issue I'm having is that for some reason if I use an #AppStorage property wrapper within the ServicesView I cannot dismiss the fourth view (RecordsView). I don't think the issue is with CoreData but let me know if you need to see the code for Core Data.
Any idea why adding an #AppStorage property wrapper in the ServicesView would affect other views?
CarsView
struct CarsView: View {
#ObservedObject var carViewModel:CarViewModel
#State private var carInfoIsPresented = false
var body: some View {
NavigationView{
VStack{
List {
ForEach(carViewModel.cars) { car in
HStack{
VStack(alignment:.leading){
Text(car.model ?? "")
.font(.title2)
Text(car.make ?? "")
.foregroundColor(Color(UIColor.systemGray))
}
NavigationLink(destination: ServicesView(carViewModel: carViewModel, selectedCar: car)){
Spacer()
Text("Services")
.frame(width: 55)
.font(.caption)
.foregroundColor(Color.systemGray)
}
}
}
}
.listStyle(GroupedListStyle())
.navigationBarTitle("Cars")
.accentColor(.white)
.padding(.top, 20)
}
}
}
}
ServicesView
struct ServicesView: View {
#ObservedObject var carViewModel: CarViewModel
var selectedCar: Car
// ISSUE: No issues dismissing the RecordsView if I comment this out
#AppStorage("sortByNameKey") private var sortByName = true
#State private var selectedService: CarService?
var body: some View {
VStack{
List {
ForEach(carViewModel.carServices) { service in
HStack{
Text(service.name ?? "")
.font(.title3)
NavigationLink(destination: ServiceInfoView(carViewModel: carViewModel, selectedCar: selectedCar, selectedService: service)){
Spacer()
Text("Details")
.font(.caption)
.foregroundColor(Color.systemGray)
}
}
}
}
.navigationBarTitle(Text("\(selectedCar.model ?? "Services") - Services"))
.listStyle(GroupedListStyle())
}
.onAppear{
carViewModel.getServices(forCar: selectedCar)
}
}
}
ServiceInfoView
struct ServiceInfoView: View {
#ObservedObject var carViewModel: CarViewModel
#State private var recordsViewIsPresented = false
#State var selectedCar: Car
#State var selectedService: CarService
var body: some View {
VStack{
Text(selectedService.name ?? "")
.font(.largeTitle)
.padding(.bottom)
VStack{
Button(action: openRecordsView) {
Text("Service History")
}
.padding(10)
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(15)
}
}
.sheet(isPresented: $recordsViewIsPresented){
RecordsView(carViewModel: carViewModel, selectedService: selectedService)
}
}
func openRecordsView(){
recordsViewIsPresented.toggle()
}
}
RecordsView
struct RecordsView: View {
#ObservedObject var carViewModel: CarViewModel
#State var selectedService: CarService
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
VStack{
List {
Section(header: Text("Records")) {
ForEach(carViewModel.serviceRecords) { record in
HStack{
Text("Service Date:")
Text("\(record.serviceDate ?? Date(), style: .date)")
.foregroundColor(Color(UIColor.systemGray))
}
}
}
}
.background(Color.purple)
.listStyle(GroupedListStyle())
}
.navigationBarTitle("Records for \(selectedService.name ?? "")", displayMode: .inline)
.navigationBarItems(leading: Button("Cancel", action: dismissView))
.onAppear{
carViewModel.getRecords(forService: selectedService)
}
}
}
func dismissView(){
presentationMode.wrappedValue.dismiss()
}
}
NavigationView can only push one detail screen unless you set .isDetailLink(false) on the NavigationLink.
FYI we don't use view model objects in SwiftUI, you have to learn to use the View struct correctly along with #State, #Binding, #FetchRequest etc. that make the safe and efficient struct behave like an object. If you ignore this and use an object you'll experience the bugs that Swift with its value types was designed to prevent. For more info see this answer MVVM has no place in SwiftUI.

PopUp don't want to dismiss SwiftUI

I am having a problem while I want to dismiss a popup (that appears automatically depending on a specific condition) by clicking a button.
This is the PopUp struct:
struct dataPrivacyPopUp: View {
let model: OffersView.Model
let termsOfUseText = "Nutzungsbedingungen"
let privacyPolicyText = "Datenschutzerklärung"
#State var termsOfUseChecked = false
#State var privacyPolicyChecked = false
#State var buttonDisabled = true
#State private var showPopUp: Bool = false
#Binding var showModal: Bool
var body: some View {
ZStack {
if ( model.showPopUp == true) {
// PopUp Window
VStack(alignment: .center){
Image("logo")
.aspectRatio(contentMode: .fill)
.frame(alignment: .center)
.padding()
VStack(alignment: .leading) {
Text((model.acceptance?.salutation)!)
.multilineTextAlignment(.leading)
.padding()
.foregroundColor(Color.black)
Text((model.acceptance?.statement)!)
.multilineTextAlignment(.leading)
.padding()
.foregroundColor(Color.black)
Text((model.acceptance?.declarationIntro)!)
.multilineTextAlignment(.leading)
.padding()
.foregroundColor(Color.black)
if ((model.acceptance?.dpr)! == true) {
VStack(alignment: .leading){
HStack {
CheckBoxView(checked: $privacyPolicyChecked)
HStack(spacing: 0){
Text(R.string.localizable.dataPrivacyPopupText())
.foregroundColor(Color.black)
Button(privacyPolicyText) {
model.openUrl(url: API.privacyPolicyURL)
}
}
}
Text((model.acceptance?.declarationOutro)!)
.multilineTextAlignment(.leading)
.padding()
}
.padding()
Button(action: {
model.setTos()
print("showModal PopUpView2 1: \(showModal)")
self.showModal.toggle()
print("showModal PopUpView2 2: \(showModal)")
}, label: {
Text(R.string.localizable.dataPrivacyButton())
.foregroundColor(Color.white)
.font(Font.system(size: 23, weight: .semibold))
})
.disabled(model.buttonDisabledForOne(privacyPolicyChecked: privacyPolicyChecked, termsOfUseChecked: termsOfUseChecked))
.padding()
}
}
}
// .onAppear(perform: )
.background(Color.white01)
.padding()
}
}
}
}
and this is where I call it (contentView):
struct OffersView: View {
#StateObject var model = Model()
#State private var showingPopUp = false
#State private var showModal = false
#State private var showingAddUser = false
// var showPopup : Bool = true
var body: some View {
NavigationView {
Group {
switch model.sections {
case .loading:
ActivityIndicator(animate: true)
case .success(let sections):
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 0) {
Text(R.string.localizable.offersHello(model.firstName))
.aplFont(.headline02)
.padding(.bottom, 24)
VStack(spacing: 48) {
ForEach(sections) { section in
OffersSectionView(section: section, model: model)
}
}
}
.useFullWidth(alignment: .leading)
.padding()
}
default:
Color.clear
if ( model.showPopUp == true) {
ZStack {
Color.black.opacity(model.showPopUp ? 0.3 : 0).edgesIgnoringSafeArea(.all)
dataPrivacyPopUp(model: model, showModal: self.$showModal)
.onAppear(perform: {
self.showModal.toggle()
})
}
}
}
}
.navigationBarHidden(true)
.handleNavigation(model.navigationPublisher)
.onAppear(perform: model.onAppear)
.onDisappear(perform: model.onDisappear)
.environment(\.dynamicTypeEnabled, false)
.safariView(isPresented: model.showSafari) {
SafariView(url: model.safariUrl!)
}
}
}
}
I need help about this, I tried the traditional method to set a #Binding variable etc .. but that's not working, the boolean value is changing but the UI is not updating (the popup is not dismissing), thank you
I tried to look at your code - I suggest you simplify it to the bare minimum to exemplify your issue - and it seems that you are using 2 properties to show your pop-up: showingPopUp and showModal. It is quite likely that you are having trouble keeping them both in sync.
For starters, I would suggest to use only one variable, either it is true or false - "a man with two watches never knows what time it is".
For the solution:
If you prefer keeping your ZStack approach, the solution would look something like:
struct MyPrivacy: View {
#Binding var showMe: Bool
var body: some View {
VStack {
Text("The content of the pop-up")
.padding()
Button {
withAnimation {
showMe.toggle()
}
} label: {
Text("Dismiss")
}
}
}
}
struct Offers: View {
#State private var showPopup = false
var body: some View {
NavigationView {
ZStack {
VStack {
Text("View behind the pop-up")
.padding()
Button {
withAnimation {
showPopup.toggle()
}
} label: {
Text("Pop")
}
}
if showPopup {
Color.white
MyPrivacy(showMe: $showPopup)
}
}
}
}
}
If instead you want to go for a more flexible approach, if you are developing for iOS, SwiftUI has a convenient object - Sheets. You can use it as suggested in the documentation, or build a specific struct that manages all the modal views of this type and use your model to handle the presentation.
The process goes like:
Create a struct that will handle all kinds of Sheets of your app.
Add to your view-model the property to present any sheet.
Create the Views that will be the content of each sheet.
Call the .sheet(item:content:) method on each View the requires a sheet.
Here's the sample code:
SheetView handler:
struct SheetView: Identifiable {
// This struct controls what modal view will be presented.
// The enum SheetScreenType can grow to as many as different
// modal views your app needs - add the content in the switch below.
let id = UUID()
var screen: SheetScreenType
#ViewBuilder
var content: some View {
switch screen {
case .dataPrivacy:
DataPrivacy()
default:
EmptyView()
}
}
enum SheetScreenType {
case dataPrivacy
case none
}
}
Presenter in your view-model:
class MyViewModel: ObservableObject {
// This code can fit anywhere within your view-model.
// It controls the presentation of the modal view, which in
// this case is a Sheet.
private let sharedSheet = SheetView(screen: .none)
// Show the selected sheet
#Published var sheetView: SheetView?
var showSheet: SheetView.SheetScreenType {
get {
return sheetView?.screen ?? .none
}
set {
switch newValue {
case .none:
sheetView = nil
default:
sheetView = sharedSheet
}
sheetView?.screen = newValue
}
}
}
Content of your modal view:
struct DataPrivacy: View {
#EnvironmentObject var model: MyViewModel // Pass YOUR model here
var body: some View {
VStack(alignment: .center){
Text("Respecting your privacy, no details are shown here")
.padding()
Button {
print("Anything you need")
// Set the showSheet property of your model to
// present a modal view. Setting it to .none dismisses
// the modal view.
model.showSheet = .none
} label: {
Text("Time do dismiss the modal view")
}
.padding()
}
}
}
Enable your view to listen to your model to present the sheet:
struct OffersView: View {
#ObservedObject var model = MyViewModel() // Pass YOUR model here
var body: some View {
VStack {
Text("Anything you wish")
.padding()
Button {
withAnimation {
// Set the showSheet property of your model to
// present a modal view. Set it to any choice
// among the ones in the SheetScreen.SheetScreenType enum.
model.showSheet = .dataPrivacy
}
} label: {
Text("Tap here for the privacy in modal view")
}
}
// Show a modal sheet.
// Add this property at the top level of every view that
// requires a modal view presented - whatever content it might have.
.sheet(item: $model.sheetView) { sheet in
sheet.content
.environmentObject(model)
}
}
}
Good luck with your project!

Multiple Bottom sheets - the content doesn't load SwiftUI

I have made a view with two possible bottom sheets. The action works, and Bottom Sheets do open. Crazy thing is they open without the view inside. I have to close the one I opened and open the other one. When I do and than come back to the first one I will see the content. The code builds without warnings:
LogInView - where the logic is:
import SwiftUI
struct LogInView: View {
#EnvironmentObject var userInfo: UserInfo
enum Action{
case resetPW, signUp
}
#State private var showSheet = false
#State private var action:Action?
var body: some View {
LoginEmailView(showSheet: $showSheet, action: $action)
.sheet(isPresented: $showSheet){
if self.action == .resetPW{
ModalResetPWView()
}else if self.action == .signUp{
ModalSignUpView()
}
}
}
}
The view from which actions come:
import SwiftUI
struct LoginEmailView: View {
#EnvironmentObject var userInfo: UserInfo
#StateObject var user:LogInViewModel = LogInViewModel()
// ----- > THERE IS BINDING
#Binding var showSheet: Bool
#Binding var action:LogInView.Action?
// ----- >
var body: some View {
VStack{
Spacer()
Image("logo")
HStack{
Text("Adres email:")
.padding(.horizontal, 10)
.font(.title)
.foregroundColor(.black)
Spacer()
}
TextField("Enter e-mail adress", text: self.$user.email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
.padding(.horizontal, 10)
.keyboardType(.emailAddress)
HStack{
Text("Password:")
.padding(.horizontal, 10)
.font(.title)
.foregroundColor(.black)
Spacer()
}
SecureField("Enter password", text: self.$user.password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
.padding(.horizontal,10)
HStack{
Spacer()
// ----- > First Bottom sheet
Button(action: {
self.action = .resetPW
self.showSheet = true
}) {
Text("Forgot Password")
}
.padding(.top, 5)
.padding(.trailing, 10)
// ----- >
}
Button(action: {
self.userInfo.isAuthenticated = .signedIn
}) {
Text("Log in")
}
.font(.title)
.padding(5)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.padding(.top, 10)
.opacity(user.isLogInComplete ? 1 : 0.7)
.disabled(!user.isLogInComplete)
// ----- > Second bottom sheet
Button(action: {
self.action = .signUp
self.showSheet = true
}) {
Text("Sign Up")
}
// ----- >
.padding(.top, 35)
Spacer()
}
}
}
The .sheet modifier will create the sheet view as soon as LogInView() is initialized. In your 'if.. else if..' statement, there is no logic to catch 'else' situations (situations where action == nil). Therefore, since action == nil on init(), the first .sheet that will present will fail your 'if..else if' and an EmptyView will present.
But don't worry! This is a common issue and can be easily solved. Here are 2 easy ways to implement methods to fix this (I prefer the 2nd method bc it's cleaner):
METHOD 1: Present a single view & change that view's content instead of switching between which view to present.
Instead of doing the 'if.. else if..' statement within the .sheet modifier, present a static view (I've called it SecondaryView ) that has a #Binding variable connected to your action. This way, when LogInView() appears, we can ensure that it will definitely render this view and then we can simply modify this view's content by changing the #Binding action.
import SwiftUI
struct LogInView: View {
enum Action{
case resetPW, signUp
}
#State private var showSheet = false
#State private var action: Action?
var body: some View {
LoginEmailView(showSheet: $showSheet, action: $action)
.sheet(isPresented: $showSheet) {
SecondaryView(action: $action)
}
}
}
struct LoginEmailView: View {
#Binding var showSheet: Bool
#Binding var action: LogInView.Action?
var body: some View {
VStack(spacing: 40 ){
Text("Forgot Password")
.onTapGesture {
action = .resetPW
showSheet.toggle()
}
Text("Sign Up")
.onTapGesture {
action = .signUp
showSheet.toggle()
}
}
}
}
struct SecondaryView: View {
#Binding var action: LogInView.Action?
var body: some View {
if action == .signUp {
Text("SIGN UP VIEW HERE")
} else {
Text("FORGOT PASSWORD VIEW HERE")
}
}
}
METHOD 2: Make each Button it's own View, so that it can have it's own .sheet modifier.
In SwiftUI, we are limited to 1 .sheet() modifier per View. However, we can always add Views within Views and each subview is then allowed it's own .sheet() modifier as well. So the easy solution is to make each of your buttons their own view. I prefer this method because we no longer need to pass around the #State/#Binding variables between views.
struct LogInView: View {
var body: some View {
LoginEmailView()
}
}
struct LoginEmailView: View {
var body: some View {
VStack(spacing: 40 ){
ForgotPasswordButton()
SignUpButton()
}
}
}
struct ForgotPasswordButton: View {
#State var showSheet: Bool = false
var body: some View {
Text("Forgot Password")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("FORGOT PASSWORD VIEW HERE")
})
}
}
struct SignUpButton: View {
#State var showSheet: Bool = false
var body: some View {
Text("Sign Up")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("SIGN UP VIEW HERE")
})
}
}

Button, how to open a new View in swiftUI embedded in navigation bar

I embedded a button on on the NavigationBar.
I'm try to make button to open a new View called DetailView
I try to use NavigationLink but it does't work inside a button.
import SwiftUI
struct ContentView: View {
#ObservedObject var dm: DataManager
#State var isAddPresented = false
var body: some View {
NavigationView {
HStack {
List () {
ForEach (dm.storage) { data in
StileCella(dm2: data)
}
}
.navigationBarTitle("Lista Rubrica")
.navigationBarItems(trailing: Button(action: {
self.isAddPresented = true
// Load here the DetailView??? How??
DetailView()
}) {
Text("Button")
})
}
}
}
}
struct DetailView: View {
var body: some View {
VStack(alignment: .center) {
Text("CIAO").bold()
Spacer()
Image(systemName: "star")
.resizable()
}
}
}
You just need to add a sheet modifier to your view, which presents your view depending on the value of isAddPresented, just like this:
struct ContentView: View {
#State var isAddPresented = false
var body: some View {
NavigationView {
List(dm.storage){ data in
StileCella(dm2: data)
}
.navigationBarTitle("Lista Rubrica")
.navigationBarItems(trailing: Button("Button") {
self.isAddPresented = true
})
} .sheet(isPresented: $isAddPresented,
onDismiss: { self.isAddPresented = false }) {
DetailView()
}
}
}
The important bit is to remember to set isAddPresented back to false in on dismiss to prevent it form presenting again.
If you want to open a new view just like we used to open through storyboard other than sheet, you can update the code in the following way:
import SwiftUI
struct ContentView: View {
#ObservedObject var dm: DataManager
#State var isAddPresented = false
var body: some View {
NavigationView {
HStack {
List () {
ForEach (dm.storage) { data in
StileCella(dm2: data)
}
}
.navigationBarTitle("Lista Rubrica")
.navigationBarItems(leading:
NavigationLink(destination: DetailView()) {
Text("Button")
})
}
}
}
}
struct DetailView: View {
var body: some View {
VStack(alignment: .center) {
Text("CIAO").bold()
Spacer()
Image(systemName: "star")
.resizable()
}
}
}
Instead of button, simply add NavigationLink inside navigationBarItems. This would do the trick! I wrote the complete for guidance but main change point is, I used
.navigationBarItems(leading:
NavigationLink(destination: DetailView()) {
Text("Button")
})
instead of:
.navigationBarItems(trailing: Button(action: {
self.isAddPresented = true
// Load here the DetailView??? How??
DetailView()
}) {
Text("Button")
})