UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Having trouble with NavigationLink and EditMode

Forums > SwiftUI

I'm working on an app which uses NavigationView and NavigationLinks to drill down into data. Below is an extremely simple app to demonstrate the problem(s).

The real intent is to display a list of names. Clicking on a category would navigate to a detailed view of the data for that name. This works fine. I also want to be able to edit the name, but want to do so from the list view, not the detail view. Using the example below, I almost have it working except for some issues.

  1. The first time I tap on the edit symbol, the first name always appears on the sheet view. After that, it works as intended.
  2. If I bring up the sheet view and then dismiss it, the EditButton doesn't respond to taps (and continues to display "Done"). Tapping on the button a few times finally works.

I guess I'm looking for help on 2 counts.

  1. Any help in solving the issues I'm seeing is appreciated, including alternative ways to accomplish the goal.
  2. Any recommendation on UI design for this would also be appreciated. (One thought I've had is to make the names list A TextField (initially disabled) instead of a Text view.)
    //
    //  ContentView.swift
    //  testNav
    //
    //  Created by Arnold Weekley on 9/11/21.
    //

    import SwiftUI

    struct ContentView: View {
        @State var names = ["Eric", "Elton", "John", "Paul", "George", "Ringo"]

        @State private var editMode: EditMode = EditMode.inactive
        @State var showingEditSheet = false
        @State var selected = 0

        var body: some View {
            NavigationView {
                List {
                    ForEach(names.indices, id:\.self) { index in
                        HStack {
                            if editMode.isEditing {
                                Image(systemName: "square.and.pencil")
                                    .padding(5)
                                    .onTapGesture {
                                        selected = index
                                        showingEditSheet = true
                                    }
                            }
                            NavigationLink(destination: DetailView(names: names, index: index)) {
                                Text("\(names[index])")
                                    .padding(.leading)
                            }
                        }
                    }
                    .onDelete(perform: deleteName)
                }
                .navigationTitle("Categories")
                .navigationBarItems(trailing: EditButton())
                .environment(\.editMode, $editMode)
                .animation(.default)
                .sheet(isPresented: $showingEditSheet) {
                    EditView(names: $names, index: selected)
                }
            }
        }

        private func deleteName(offsets: IndexSet) {
            withAnimation {
                offsets.sorted(by: > ).forEach { (idx) in
                    names.remove(at: idx)
                }
            }
        }
    }

    struct DetailView: View {
        var names: [String]
        var index: Int
        var body: some View {
            Text("Name is: \(names[index])")
                .navigationTitle("Detail")
                .navigationBarTitleDisplayMode(.inline)
        }
    }

    struct EditView: View {
        @Environment (\.presentationMode) var presentationMode
        @Binding var names: [String]
        var index: Int
        var body: some View {
            VStack {
                Text("Edit name...")
                TextField("name...", text: $names[index])
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .navigationTitle("Editor")
                    .navigationBarTitleDisplayMode(.inline)
                    .padding(.bottom)
                Button("Cancel", action: {
                    self.presentationMode.wrappedValue.dismiss()
                })
            }
        }
    }

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

2      

First I assume you are want this in iOS 14 as with the new swipe button in iOS 15 would be a better way to go.

How to add custom swipe action buttons to a List row

If you still want to support lower then iOS 15 then I would a "EDIT" button (not the editMode) in the link to able user to edit the text

EG

struct DetailView: View {
    @Binding var names: [String]
    var index: Int
    @State private var isEditing = false

    var body: some View {
        VStack {
            if isEditing {
                TextField(names[index], text: $names[index])
                    .textFieldStyle(RoundedBorderTextFieldStyle())
            } else {
                HStack {
                    Text(names[index])
                    Spacer()
                }
            }
        }
        .padding()
        .navigationTitle(isEditing ? "Editor" : "Detail")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            Button(isEditing ? "Done" : "Edit") {
                isEditing.toggle()
            }
        }
    }
}

Leaving the EditMode to do delete and move in List as it was design to do

2      

@NigelGee, thanks for the reply.

I had forgot to say that I was still on IOS 14. What you recommended is correct, but I realize I wasn't clear on what I'm attempting to do. I had worked out code that shows the full problem scope but attempted to dumb it down for submission.

What I want to do is be able to drill down to a detail screen. Handling the drill down is easy enough. See attached images of what I have so far in the app.

  • My initial list shows various categories (i.e. "Golf Courses", "Parks", "Restaurants", etc...).
  • When I click on a category to show detail, the next view contains a list of items within the selected category.
  • Finally, if I click on one of the items in that list, the detail is shown and the "Edit" button you suggest would be the correct solution.

My problem is that I want to be able to edit the category names (i.e. to correct spelling) and it didn't make sense to do that from the next screen. Hence the edit image by the category names. Thinking about it, perhaps the solution may be just to leave the edit button showing

Top Level Categories - Want to drill down or edit category name Top level categories

Second level from selection above - Again, want to drill down or edit name Drilled down to Restaurants

Bottom level list - From here, I can just drill down to item detail Drilled down to Items

Item detail - Can add edit button to switch to edit mode on this screen Showing Item Detail

2      

I've updated the code to get rid of the editMode. It mostly works now, except for...

  • Clicking on the edit pencil takes me to edit Eric regardless which name I click on.
  • Canceling and then clicking on the same name still shows Eric.
  • Canceling and then clicking on a different name shows the correct name.
  • I discovered that if I attempt to edit Eric when it shows up incorrectly causes the TextField to update to the correct name as soon as any change is made.

Below is the updated code.

//
//  ContentView.swift
//  testNav
//
//  Created by Arnold Weekley on 9/11/21.
//

import SwiftUI

struct ContentView: View {
    @State var names = ["Eric", "Elton", "John", "Paul", "George", "Ringo"]

    @State var showingEditSheet = false
    @State var selected = 0

    var body: some View {
        NavigationView {
            List {
                ForEach(names.indices, id:\.self) { index in
                    HStack {
                        Button(action: {
                            print("before: selected = \(selected), index = \(index)")
                            selected = index
                            print("after:  selected = \(selected), index = \(index)")
                            showingEditSheet = true

                        }) {
                            Label("", systemImage: "square.and.pencil")
                        }
                        .onTapGesture {
                            print("before selected: \(selected), index: \(index)")
                            selected = index
                            print("after selected: \(selected), index: \(index)")
                            showingEditSheet = true
                        }
                        NavigationLink(destination: DetailView(names: names, index: index)) {
                            Text("\(names[index])")
                                .padding(.leading)
                        }
                    }
                }
                .onDelete(perform: deleteName)
            }
            .navigationTitle("Categories")
            .navigationBarItems(trailing: EditButton())
            .animation(.default)
            .sheet(isPresented: $showingEditSheet) {
                EditView(names: $names, index: selected)
            }
        }
    }

    private func deleteName(offsets: IndexSet) {
        withAnimation {
            offsets.sorted(by: > ).forEach { (idx) in
                names.remove(at: idx)
            }
        }
    }
}

struct DetailView: View {
    var names: [String]
    var index: Int
    var body: some View {
        VStack {
            Text("Name is: \(names[index])")
                .padding(.bottom)
            Text("Detail for editing here...")
        }
            .padding()
            .navigationTitle("Detail")
            .navigationBarTitleDisplayMode(.inline)
    }
}

struct EditView: View {
    @Environment (\.presentationMode) var presentationMode
    @Binding var names: [String]
    var index: Int
    var body: some View {
        VStack {
            Text("Edit name...")
            TextField("name...", text: $names[index])
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .navigationTitle("Editor")
                .navigationBarTitleDisplayMode(.inline)
                .padding(.bottom)
            Button("Cancel", action: {
                self.presentationMode.wrappedValue.dismiss()
            })
        }
        .padding()
    }
}

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

2      

Update:

I discovered that if I replace

    var index: Int

in the EditView struct with

    @Binding var index: Int

The edit screen works as expected. However now, I see that clicking on a name in the list now brings up the edit view and then the detail view.

2      

Noticed that I had both Button(action=... and onTapGesture for the button next to each name in the list. Anyway, replaced the Button with an Image with onTapGesture and everything now works as expected.

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.