Home > Mobile >  How to add buttons next to a Swiftui navigation link
How to add buttons next to a Swiftui navigation link

Time:05-02

I am making a simple checklist app that allows you to see more detailed information about items. I would like to be able to tap the checkbox (the circle button) to remove the item or click on the rest of the navigation link to enter that separate page. Here is my code for the list that's embedded in a navigation view:

List {
  ForEach(fruitIds, id: \.self) { fruitId in
    HStack {
      Button {
        fruitIds.removeLast()
        fruits.remove(at: fruitId)
      } label: {
        Image(systemName: "circle")
          .imageScale(.large)
          .foregroundColor(.accentColor)
      }
      NavigationLink(destination: Text(fruits[fruitId])) {
        Text(fruits[fruitId])
      }
    }
  }
} 

Currently, pressing either the button or the navigation link sends you to the other screen, deletes the item, which then sends you back to the list.

I have these 2 arrays declared btw

@State var fruits: [String] = ["apple", "orange", "banana", "peach"]
@State var fruitIds: [Int] = [0, 1, 2, 3]

Also, I'm very new to Swift, and there's definitely a better way to do this list.

CodePudding user response:

I am taking the answer to your button question from this answer, so please give credit there. It is also possible to use a Button with a programmatically triggered NavigationLink in a .background() as the NavigationLink. That can be tricky to make it look stock, and this solution does what you want. This However, you also asked about the list, and that question is very important to understand.

First, if at all possible, avoid using id: \.self in a List, ForEach, etc. The reason is it is very fragile, and will come back to bite you when you try to delete or move items, or if you have two of the "same" items in the list. You really should be using an identifiable struct for this. For this answer, I am using:

struct Fruit: Identifiable {
    let id = UUID()
    var type: String
    // var color: Color, etc.
}

The view then becomes:

struct FruitListView: View {
    
    //  This array can have duplicate fruits and the ForEach is unaffected as the id's are different.
    @State var fruits: [Fruit] = [Fruit(type: "apple"),
                                  Fruit(type: "orange"),
                                  Fruit(type: "orange"),
                                  Fruit(type: "banana"),
                                  Fruit(type: "banana"),
                                  Fruit(type: "peach")]
    
    var body: some View {
        List {
            // Since Fruit conforms to Identifiable, you do not need id:
            ForEach(fruits) { fruit in
                HStack {
                    Button {
                        fruits.removeAll(where: { $0.id == fruit.id })
                    } label: {
                        Image(systemName: "circle")
                            .imageScale(.large)
                            .foregroundColor(.accentColor)
                    }
                    .buttonStyle(PlainButtonStyle()) // This is necessary to capture the click
                    .frame(width: 40)
                    // .contentShape makes a larger tap area to take up the height of the row.
                    .contentShape(Rectangle())
                    // You can use fruit directly as it is a Fruit and an element of fruits
                    NavigationLink(destination: Text(fruit.type)) {
                        Text(fruit.type)
                    }
                }
            }
            // This is a standard delete. Try implementing it in your code. You will likely get a crash.
            .onDelete(perform: delete)
        }
    }
    
    func delete(at offsets: IndexSet) {
                fruits.remove(atOffsets: offsets)
    }
}
  • Related