View does not update

Hi peeps,

I am beginner and I am trying to build an app. I followed the beginners course here and another one. I am getting stuck when I put a Toggle to true in View1 and View2 should display some changes with “if statement” but the nothing happens. I used the ObservableObject because I am planning to use more views that needs information from View 1. I hope some one can help me out.
Here is a example of my code:

View 1:
import SwiftUI

class ChangeViews: ObservableObject {
@Published var showAdd = false
}

struct SettingsConfig: View {

@ObservedObject var changeViews: ChangeViews

var body: some View {
    NavigationView {
        VStack {
            
            Toggle("Add Guest", isOn: $changeViews.showAdd)
                
            }

View 2:
import SwiftUI

struct AddView: View {

@ObservedObject var changeViews: ChangeViews
@State  var name = ""
@State  var gender = ""
@State  var date = ""


var body: some View {

    if (changeViews.showAdd == true) {

        VStack {
            TextField("name", text: $name)
            TextField("gender", text: $gender)
            TextField("list date", text: $date)
            
            // TODO: add function to navigate
            ZStack {
                ButtonView()
                    .foregroundColor(.blue)
                
                Text("Add")

@Tito

Hi Claudio,

Welcome to the community.

Can you provide more of the code that you have in your AddView since it looks like the bottom has been chopped off.

Also what is the code in your initial View? Is it ContentView and if so can you paste in the code for that too?

Hi Chris,

Thnx for the respond.

This is the code of AddView:

import SwiftUI

struct AddView: View {

@ObservedObject var changeViews: ChangeViews
@ObservedObject var data: Data
@State  var name = ""
@State  var gender = ""
@State  var date = ""

var body: some View {

    //VStack {  // Testting the if statement to set an alert.
    
    if (changeViews.showAdd == true) {

        VStack {
            TextField("name", text: $name)
            TextField("gender", text: $gender)
            TextField("list date", text: $date)
            
            // TODO: add function to navigate
            ZStack {
                ButtonView()
                    .foregroundColor(.blue)

                Text("Add")
                    .foregroundColor(.white)
                    .font(.title3)
            }
            .padding()
            Spacer()
            Spacer()
            Spacer()
            NavigationView {
                
                ScrollView {
                    NavigationLink(destination: ListView(data: Data())) {
                        ZStack {
                            ButtonView()
                                .foregroundColor(.green)
                            Text("Show List")
                                .foregroundColor(.white)
                                .font(.title3)
                        }
                    }
                }

            }

            
        }
        .font(.title2)
        .padding(30)

    }
    else {
        ZStack {
            PopView()
        VStack {
            Text("Access Denied")
                .foregroundColor(.red)
                .font(.title)
                .bold()
            Text("You need to enable 'Add Guest' in Settings")
        }
        .padding(10)
      
        }

}
    }

The initial view is ContentView and as you can see in the code there are 4 tabs. The last tab SettingsConfig I want to use a toggle and when this is set to true I want to grant access to AddView.

Here is the code of ContentView:
import SwiftUI

struct ContentView: View {

@StateObject var changeViews = ChangeViews()


var body: some View {
    VStack {
        
        Spacer()
        TabView {
            //Text("Search")
            SearchView(data: Data())
                .tabItem {
                    Image(systemName: "magnifyingglass")
                        .foregroundColor(.red)
                }

            ListView(data: Data())
                .tabItem {
                    Image(systemName: "list.bullet")
                        .navigationTitle("List")
                }
                
            AddView(changeViews: ChangeViews(), data: Data())
                .tabItem {
                    Image(systemName: "person.fill.badge.plus")
                  }
                
            NewPass(handler: { _,_  in })
                .tabItem {
                    Image(systemName: "gearshape.2")
                }
        }

    }
    
}

}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(changeViews: ChangeViews())
}
}

Does NewPass replace your SettingsConfig that you had previously?

Can you provide the code for that NewPass View?

What does Data do? You have that as an ObservedObject in your AddView code.

Getting back to the original question regarding making a change to an ObservableObject Published property in View1 and then wanting to see that change reflected in View2 which is monitoring the same Published property.

When using a ViewModel (ObservableObject) both of those Views need access to the same instance of the ViewModel. In each of those Views you declared an instance of the ViewModel (ChangeViews) by saying:

@ObservedObject var changeViews: ChangeViews

which is fine.

But, in your Content view you have declared the StateObject as follows:

@StateObject var changeViews = ChangeViews()

then in your tab item AddView you have this:

AddView(changeViews: ChangeViews(), data: Data())

whereas you should be saying this:

AddView(changeViews: changeViews, data: Data())

The subtle difference is that using the variable changeViews rather than a completely different instance of ChangeViews() will ensure that both views are referencing the same ViewModel.

Do you see what I am getting at?

Chris,

Yes, I understand it now where the problem was. I changed in ContentView:

@StateObject var changeViews = ChangeViews() into : @ObservedObject var changeViews: ChangeViews

and in AddView what you mentioned, but I don’t understand that AddView change to:

AddView(changeViews: changeViews, data: Data()). Beside that when I changed everything I get the following error:

Compile GuestListApp.swift (x86_64):
/Users/Desktop/Online Training/VIP App/GuestList/GuestList/GuestListApp.swift:14:25: error: missing argument for parameter ‘changeViews’ in call
ContentView()
^
changeViews: <#ChangeViews#>

The NewPass is where you enter your pin code to enter SettingsConfig. The pin code is for now hard coded but I will change it when I am at that point. I have to say that the code is not mine, I got it from Google and I change it a bit and it is working fine so far. Still got a bug that I need to fix but that will me be later on.

Data is where the name of the persons that are on the list are stored and are for now hardcoded.

NewPass code is:

import SwiftUI
// import Introspect

public struct NewPass: View {

var maxDigits: Int = 4
var label = "Enter Pin"

@State var pin: String = ""
@State var showPin = false
@State var isDisabled = false
@State var loginSettings = false



var handler:(String, (Bool) -> Void) -> Void

public var body: some View {
    
    NavigationView {
        VStack(spacing: 10) {
            Text(label).font(.title)
            ZStack {
                pinDots
                backgroundField
            }
            showPinStack
            
            // Button to go to next view after correct pin
            Button {
                if (pin == "1234"){
                    loginSettings.toggle()
                }
                
                
            } label: {
                
                NavigationLink(destination: SettingsConfig(changeViews: ChangeViews()), isActive: $loginSettings, label: {Text("")})
                
                
                ZStack {
                    
                    if (isDisabled == true) {
                        
                        ButtonView()
                            .foregroundColor(.blue)
                     Text("Login")
                            .foregroundColor(.white)
                            .font(.title3)
                    
                    }
                    else {
                        
                        //ButtonView()
                          //  .foregroundColor(.gray)
                        //Text("Login")
                          //  .foregroundColor(.white)
                          //  .font(.title3)
                    }
                    
                    
                }
            }
            //.sheet(isPresented: $loginSettings) {
            //  SettingsConfig()
            // TODO need to resolve sheet into other view
            // }
            
            
        }
    }
    .navigationBarBackButtonHidden(true)
}

private var pinDots: some View {
    HStack {
        Spacer()
        ForEach(0..<maxDigits) { index in
            Image(systemName: self.getImageName(at: index))
                .font(.title)
            Spacer()
        }
    }
}

private var backgroundField: some View {
    let boundPin = Binding<String>(get: { self.pin }, set: { newValue in
        self.pin = newValue
        self.submitPin()
    })
    
    return TextField("", text: boundPin, onCommit: submitPin)
  
  // Introspect library can used to make the textField become first resonder on appearing
  // if you decide to add the pod 'Introspect' and import it, comment #50 to #53 and uncomment #55 to #61
  
       .accentColor(.clear)
       .foregroundColor(.clear)
       .keyboardType(.numberPad)
      // .disabled(isDisabled)

// .introspectTextField { textField in
// textField.tintColor = .clear
// textField.textColor = .clear
// textField.keyboardType = .numberPad
// textField.becomeFirstResponder()
// textField.isEnabled = !self.isDisabled
// }
}

private var showPinStack: some View {
    HStack {
        Spacer()
        if !pin.isEmpty {
            showPinButton
        }
    }
    .frame(height: 20)
    .padding([.trailing])
}

private var showPinButton: some View {
    Button(action: {
        self.showPin.toggle()
    }, label: {
        self.showPin ?
            Image(systemName: "eye.slash.fill").foregroundColor(.red) :
            Image(systemName: "eye.fill").foregroundColor(.green)

    })

    
   
}


private func submitPin() {
    guard !pin.isEmpty else {
        showPin = false
        return
    }
    //TODO: Check if next view button will work here
    if pin.count == maxDigits {
        isDisabled = true
        
        handler(pin) { isSuccess in
            if isSuccess {
                print("pin matched, go to next page, no action to perfrom here")
            } else {
                pin = ""
                isDisabled = false
                print("this has to called after showing toast why is the failure")
            }
        }
    }
    
    // this code is never reached under  normal circumstances. If the user pastes a text with count higher than the
    // max digits, we remove the additional characters and make a recursive call.
    if pin.count > maxDigits {
        pin = String(pin.prefix(maxDigits))
        submitPin()
    }
}

private func getImageName(at index: Int) -> String {
    if index >= self.pin.count {
        return "circle"
    }
    
    if self.showPin {
        return self.pin.digits[index].numberString + ".circle"
    }
    
    return "circle.fill"
}

}

extension String {

var digits: [Int] {
    var result = [Int]()
    
    for char in self {
        if let number = Int(String(char)) {
            result.append(number)
        }
    }
    
    return result
}

}

extension Int {

var numberString: String {
    
    guard self < 10 else { return "0" }
    
    return String(self)
}

}
struct NewPass_Previews: PreviewProvider {
static var previews: some View {
NewPass(handler: { , in })
}

             }

Your ObservedObject in ContentView should be defined as:

@ObservedObject var changeViews = ChangeViews()

Done and the error is gone, but still not updating the AddView after I set the toggle of SettingsConfig to true

You might be best advised to set up your ChangeViews to be an EnvironmentObject

In ContentView:

struct ContentView: View {
    
    var body: some View {
        TabView {
            SearchView()
                .tabItem {
                    Image(systemName: "magnifyingglass")
                    Text("Search")
                }
            ListView()
                .tabItem {
                    Image(systemName: "list.bullet")
                    Text("List")
                }

            AddView()
                .tabItem {
                    Image(systemName: "plus")
                    Text("Add")
                }

            SettingsConfig()
                .tabItem {
                    Image(systemName: "gearshape.2")
                    Text("Settings")
                }

        }
        // This is where the ChangeViews ObservableObject is injected
        // the View hierarchy
        .environmentObject(ChangeViews())
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(ChangeViews())
    }
}

and in SettingsConfig:

struct SettingsConfig: View {
    // This is where the injected ChangeViews is accessed 
    @EnvironmentObject var changeViews: ChangeViews

    var body: some View {
        NavigationView {
            VStack {

                Toggle("Add Guest", isOn: $changeViews.showAdd)

            }
        }
    }
}

struct SettingsConfig_Previews: PreviewProvider {
    static var previews: some View {
        SettingsConfig()
            .environmentObject(ChangeViews())
    }
}

and in AddView:

struct AddView: View {
    // This is where the injected ChangeViews is accessed
    @EnvironmentObject var changeViews: ChangeViews
    //  Other declarations
    //  .....
    
    var body: some View {
        if (changeViews.showAdd == true) {
            VStack {
                // The rest of you body code goes here.  
                // I left it out just to save time and space
                ....
                ....
                ....
                ....
             }
        } else {
            ZStack {
                // Your code goes here......
            }
        }
    }
}


struct AddView_Previews: PreviewProvider {
    static var previews: some View {
        AddView()
            .environmentObject(ChangeViews())
    }
}

This approach should simplify what you are trying to do.

Note that the Previews will be simplified as well since all that you do in them is inject an instance of ChangeViews() as an .environmentObject(). Remember that Previews are not part of the final project that you build. They are only there to allow you to take a look at your view in the Canvas so that as you build it, you can make sure that you are getting the right layout.

Chris,

Thank you so much for your help. It is working and I know now also why it wasn’t working. I should do the same for data: Data