0

What I'm trying to achieve: I'm a new SwiftUI developer. I'm trying to build a simple Address Book app. I have three views:

  1. ContentView - The main view which contains all contacts in a List View with an Add Contact ('+') and Edit button at the top of the Navigation View
  2. AddContact View - which has a "Name" and "Email" text field and a "Submit" button
  3. DisplayContactDetails View - not relevant to this question.

I've created an Environment Object "myContacts" which is an array of "Contact" objects and passed it in the ContentView to keep track of all contacts in the Address Book

When the user navigates to AddContact View, adds a name and email and submits, I'd like the Environment Object "myContacts" to be updated and for the user to be navigated back to ContentView so they can see the Address Book with the new contact included.

Problem:

When the user presses "Submit" on AddContact View, it correctly invokes a navigation link I've created to send the user back to ContentView. But because the Environment Object "myContacts" has also been updated by submit, it immediately navigates back from ContentView to AddContact View again. So it appears to be executing the Navigation Link first but then reloading AddContact View due to the refresh of myContacts.

Code - Content view:

    struct ContentView: View {
    
    @EnvironmentObject var myContacts: Contacts
    @State var isAddButtonPressed: Bool = false
    
    var body: some View {
        
        NavigationView{
           
            List {
                
                ForEach(myContacts.contacts) { item in
                    
                    NavigationLink(
                          //Display items and send user to DisplayContactDetails

                        })
                    
                }
            
            }
            .navigationBarTitle("Address Book")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading){
                    
                    Button(action: {
                        isAddButtonPressed.toggle()
                    }, label: {
                        
                       NavigationLink(
                        destination: AddContactView(),
                        isActive: $isAddButtonPressed,
                        label: {
                            Image(systemName: "plus")
                        })
                        
                    })
                }
                ToolbarItem(placement: .navigationBarTrailing){
                    EditButton()
                }
                
                
            }
        }
    }
    
}

Code - AddContactView

struct AddContactView: View {
    
    @State var name: String = ""
    @State var email: String = ""
    @State var isButtonPressed: Bool = false
    
    @EnvironmentObject var myContacts: Contacts
    
    var body: some View {
        
       
        VStack{
            
            HStack{
                Text("Name:")
                TextField("Enter name", text: $name)
            }
            .padding(.bottom, 50)
            
            HStack{
                Text("Email:")
                TextField("Enter email", text: $email)
            }
            .padding(.bottom, 50)
            
            
            Button("Submit") {
                
                let contactToAdd = Contact(name: name, email: email)
                
                //Add is a simple function - all it does is append an item to the    myContacts array using the .append method
                myContacts.add(contact: contactToAdd)
                isButtonPressed = true
            }

            .frame(width: 80, height: 30, alignment:.center)
            .background(Color.blue)
            .foregroundColor(.white)
            .clipShape(Capsule())
                
            NavigationLink(destination: ContentView().navigationBarHidden(true),
                    isActive: $isButtonPressed,
                    label: {
                       EmptyView()
                    }).hidden()
            
        }.padding()
            
        }
    
}

What I've tried

If I comment out the the .add method and don't update the environment object, then the navigation back to ContentView works as expected. So I know that specifically is the cause of the problem.

I've tried adding a .onTapGesture modifier to the Button and invoking .add there.

I've tried adding a .onDisappear modifier to the entire view and invoking .add there.

-- Any help or clarity on resolving this would be much appreciated

Edit: Screen Recording - trying solution based on first comment:

What happens when I try the solution

Odd behaviour: The first attempt at adding a contact auto-routes back to AddContactView, producing the same error. But if I try it a second time then it routes correctly to ContactView.

1 Answers1

0

Edit update. This is the code I used to test my answer:

import SwiftUI

@main
struct TestApp: App {
    @StateObject var myContacts = Contacts()
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(myContacts)
        }
    }
}

struct Contact: Identifiable {
    var id = UUID()
    var name: String = ""
    var email: String = ""
}

class Contacts: ObservableObject {
    @Published var contacts: [Contact] = [Contact(name: "name1", email: "email1"), Contact(name: "name2", email: "email2")]
    
    func add(contact: Contact) {
        contacts.append(contact)
    }
}

struct AddContactView: View {
    @Environment(\.presentationMode) private var presentationMode
    
    @EnvironmentObject var myContacts: Contacts
    
    @State var name: String = ""
    @State var email: String = ""

    var body: some View {
        VStack{
            HStack{
                Text("Name:")
                TextField("Enter name", text: $name)
            }
            .padding(.bottom, 50)
            HStack{
                Text("Email:")
                TextField("Enter email", text: $email)
            }
            .padding(.bottom, 50)
            Button("Submit") {
                let contactToAdd = Contact(name: name, email: email)
                myContacts.add(contact: contactToAdd)
                presentationMode.wrappedValue.dismiss()
            }
            .frame(width: 80, height: 30, alignment:.center)
            .background(Color.blue)
            .foregroundColor(.white)
            .clipShape(Capsule())
        }.padding()
    }
}

struct ContentView: View {
    @EnvironmentObject var myContacts: Contacts
    @State var isAddButtonPressed: Bool = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(myContacts.contacts) { item in
                    NavigationLink(destination: AddContactView()) {
                        Text(item.name)
                    }
                }
            }
            .navigationBarTitle("Address Book")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading){
                    Button(action: {
                        isAddButtonPressed.toggle()
                    }, label: {
                        NavigationLink(
                            destination: AddContactView(),
                            isActive: $isAddButtonPressed,
                            label: {
                            Image(systemName: "plus")
                        })
                    })
                }
                ToolbarItem(placement: .navigationBarTrailing){
                    EditButton()
                }
            }
        }
    }
}
  • hmm, tried it but unfortunately it produced the same outcome. Doesn't solve the problem. – user16405656 Jul 08 '21 at 12:36
  • surprised it does not work. I've updated my answer with my test code. – workingdog support Ukraine Jul 08 '21 at 13:09
  • Thanks for the quick response. I tried your code and it produced an odd behaviour - When I go from ContentView to AddContactView and add a contact, it still produces the same error and autoroutes back to AddContactView. But from there if I add another contact, it goes to ContactView and stays there as expected. I've edited my original post and attached a link to a screen recording of this behaviour. – user16405656 Jul 09 '21 at 02:19
  • I see you are using Preview. My solution works on real devices and simulators, but maybe not in the "usually broken" Preview. I guess that's where you are testing the code, is that correct? – workingdog support Ukraine Jul 09 '21 at 03:04
  • Have you tried removing `@Environment(\.presentationMode) private var presentationMode.` I thought that is only used for presenting with sheets and no nav links? – Tim Dec 20 '21 at 10:27