Search code examples
swiftswiftui

Edit mode in a List


Updated with working code.

I've implemented a List in my app with Edit mode, so I can move the rows by dragging a row handle. That works fine, but doesn't look too good, since the move icon is placed under the content of the row (see screen dump). And that is because Edit mode makes room for a delete button.

Is there a way to hide elements in the row when you're in Edit mode?

Row in a list in Edit mode

The code for the View is:

import SwiftUI
import Combine
import DateHelper

struct EggList: View {

    @EnvironmentObject var egg : Egg
    @State private var eggs = Egg.all()

    @State private var editMode: EditMode = .inactive

    var body: some View {

        NavigationView {

            List {

                Image("Pantanal")
                    .resizable()
                    .frame(height: 250)

                ForEach(eggs) { eggItem in
                    NavigationLink(destination: EggDayList(eggItem: eggItem)) {
                        CellRow(eggItem: eggItem)
                        .environment(\.editMode, self.$editMode)
                    }
                 }
                .onDelete(perform: delete)
                .onMove(perform: move)
            }

            .navigationBarTitle(Text("Eggs"), displayMode: .inline)
            .navigationBarItems(leading: EditButton(), trailing: NavigationLink(destination: Settings()){
                Text("Add Egg")})
            .environment(\.editMode, self.$editMode)
        }
    }
    func delete(at offsets: IndexSet) {
        eggs.remove(atOffsets: offsets)
    }
    func move(from source: IndexSet, to destination: Int) {
        eggs.move(fromOffsets: source, toOffset: destination)
    } 

}

struct CellRow: View {

    let eggItem: Egg
    @Environment(\.editMode) private var editMode

    var body: some View {

        HStack(spacing: 8) {

            Image(eggItem.species)
                .resizable()
                .frame(width: 48, height: 48)
                .clipShape(Circle())

            VStack(alignment: .leading, spacing: 0) {
                Text("\(eggItem.species)")
                    .font(.footnote)
                    .lineLimit(1)
                    .padding(.top, -4)

                Text("id-"+String(eggItem.eggNumber))
                    .font(.footnote)
                    .lineLimit(1)
                    .padding(0)

                Text("\(eggItem.layDate.string(with: "dd-MM-yy"))")
                    .font(.footnote)
                    .lineLimit(1)
                    .padding(.bottom, -7)
            }.frame(width: 90, alignment: .leading)

            VStack(spacing: 2) {
                Text("days")
                    .font(.footnote)
                    .padding(.top, 12)
                Image(systemName: "\(eggItem.diffToday)"+".circle")
                    .resizable()
                    .frame(width: 40, height: 30)
                    .padding(.bottom, 12)
                    .foregroundColor(.red)
            }.frame(width: 50, alignment: .leading)

            VStack(spacing: 0) {
                Text("prediction")
                    .font(.footnote)
                    .padding(.top, 14)
                Text(formatVar1(getal: eggItem.calcWeights[eggItem.daysToPip-1].prediction)+"%")
                    .font(.title)
                    .padding(.bottom, 12)
            }.frame(width: 80, alignment: .leading)

            if !(self.editMode?.wrappedValue.isEditing ?? false) {
                VStack(alignment: .leading, spacing: 0) {
                    Text("INC")
                        .font(.footnote)
                        .lineLimit(1)
                        .padding(.top, -4)
                    Text("37.3")
                        .font(.footnote)
                        .lineLimit(1)
                        .padding(0)
                    Text("30%")
                        .font(.footnote)
                        .lineLimit(1)
                        .padding(.bottom, -7)
                }
                .frame(width: 30, alignment: .leading)
            }

            Spacer()

            VStack(alignment: .trailing, spacing: 0) {

                Image(systemName: "info.circle")
                    .resizable()
                    .frame(width: 22, height: 22)
                    .foregroundColor(.accentColor)
                    .onTapGesture(count: 1) {
                        print("action")
                }
            }

        }
        .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
        .frame(height: 46, alignment: .leading)
        .padding(0)
    }
}

Solution

  • Your problem is, that the editMode variable does not change when you press the EditButton. No matter in what state, in your CellRow the editMode variable always returns .inactive. I don't know why, it could be a bug. I had the same problem, but I found this question here: SwiftUI - How do I make edit rows in a list?, which uses a workaround by passing the editMode environment value into a private @State variable, which seems to work perfect.

    So here is what you need to do:

    • Add @State private var editMode: EditMode = .inactive to your EggList view.

      This creates a state variable that from now on holds the editing mode.

    • Add .environment(\.editMode, self.$editMode) somewhere after the .navigationBarItems(...).

      This sets the environment variable editMode in EggList to a binding of the state variable above.

    • Add .environment(\.editMode, self.$editMode) directly after the CellRow(...) initializer.

      This inserts the state variable editMode into the environment of CellRow, where it can be accessed via @Environment(\.editMode).

    Now you can just wrap one of your elements in an if-statement to hide it when in editing mode:

    if !self.editMode?.wrappedValue.isEditing ?? true {
        VStack(alignment: .leading, spacing: 0) {
            Text("INC")
                .font(.footnote)
                .lineLimit(1)
                .padding(.top, -4)
            Text("37.3")
                .font(.footnote)
                .lineLimit(1)
                .padding(0)
            Text("30%")
                .font(.footnote)
                .lineLimit(1)
                .padding(.bottom, -7)
        }
        .frame(width: 30, alignment: .leading)
    }
    

    or, if you prefer, continue using the isHidden extension from LuLuGaGa: .isHidden(self.editMode?.wrappedValue.isEditing ?? false)