SwiftUI如何在编辑模式更改时执行操作?

13

当EditMode更改时,我想执行某个操作。

具体来说,在编辑模式下,用户可以选择要删除的一些项目。他通常在之后按垃圾桶按钮。但他也可能按完成。当他再次按编辑时,之前选择的项目仍然被选择。我希望所有项目都被清除。

struct ContentView: View {
    @State var isEditMode: EditMode = .inactive
    @State var selection = Set<UUID>()
    var items = [Item(), Item(), Item(), Item(), Item()]

    var body: some View {
        NavigationView {
            List(selection: $selection) {
                ForEach(items) { item in
                    Text(item.title)
                }
            }
            .navigationBarTitle(Text("Demo"))
            .navigationBarItems(
                leading: EditButton(),
                trailing: addDelButton
            )
            .environment(\.editMode, self.$isEditMode)
        }
    }

    private var addDelButton: some View {
        if isEditMode == .inactive {
            return Button(action: reset) {
                Image(systemName: "plus")
            }
        } else {
            return Button(action: reset) {
                Image(systemName: "trash")
            }
        }
    }

    private func reset() {
        selection = Set<UUID>()
    }
}

项目的定义:

struct Item: Identifiable {
    let id = UUID()
    let title: String

    static var i = 0
    init() {
        self.title = "\(Item.i)"
        Item.i += 1
    }
}

已经在这里回答了:https://dev59.com/L1MH5IYBdhLWcg3w_Wd_#57381570 - Casper Zandbergen
可能是SwiftUI Button as EditButton的重复问题。 - Casper Zandbergen
很酷,但现在选择元素的气泡不再出现了...所以真正的EditButton必须是一些不同的东西,而不仅仅是将editMode设置为活动状态。 - caram
此外,我还需要将 editMode 设为 @State 变量,以便在 editMode 未激活时,垃圾桶按钮会变回加号。 - caram
3个回答

20

iOS 15更新。

此解决方案一举两得:

  1. 当editMode切换时,整个视图将重新绘制
  2. 可以在激活/停用editMode时执行特定操作

希望这对其他人有所帮助。

struct ContentView: View {
    @State var editMode: EditMode = .inactive
    @State var selection = Set<UUID>()
    @State var items = [Item(), Item(), Item()]

    var body: some View {
        NavigationView {
            List(selection: $selection) {
                ForEach(items) { item in
                    Text(item.title)
                }
            }
            .navigationTitle(Text("Demo"))
            .environment(\.editMode, self.$editMode)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    editButton
                }
                ToolbarItem(placement: .navigationBarTrailing) {
                    addDelButton
                }
            }
        }
    }

    private var editButton: some View {
        Button(action: {
            self.editMode.toggle()
            self.selection = Set<UUID>()
        }) {
            Text(self.editMode.title)
        }
    }

    private var addDelButton: some View {
        if editMode == .inactive {
            return Button(action: addItem) {
                Image(systemName: "plus")
            }
        } else {
            return Button(action: deleteItems) {
                Image(systemName: "trash")
            }
        }
    }
    
    private func addItem() {
        items.append(Item())
    }
    
    private func deleteItems() {
        for id in selection {
            if let index = items.lastIndex(where: { $0.id == id }) {
                items.remove(at: index)
            }
        }
        selection = Set<UUID>()
    }
}
extension EditMode {
    var title: String {
        self == .active ? "Done" : "Edit"
    }
    
    mutating func toggle() {
        self = self == .active ? .inactive : .active
    }
}

1
FYI - 一个人应该能够将内置的 EditButton() 声明为尾随/前导 ToolbarItem;这样我们不仅避免了 custom editButton 代码,而且在切换模式时还获得了完整的动画支持。(ToolbarItem 因为 navigationBarItems 在 iOS 13 中已被弃用。) - Vinod Madigeri
我已经更新了代码,使用.toolbar代替.navigationBarItems - caram

9
我一直在努力尝试,在用户退出“编辑模式”时清除“列表”选择。对我来说,我发现对于对“编辑模式”的更改做出反应,最干净的方法是确保引用“@Environment”变量。
@Environment(\.editMode) var editMode

在视图中添加一个计算属性以监控状态。
private var isEditing: Bool {
   editMode?.wrappedValue.isEditing == true
}

然后使用.onChange(of:perform:)方法:
.onChange(of: self.isEditing) { value in
  if value {
    // do something
  } else {
    // something else
  }
}

一起来:
struct ContentView: View {
  
  @Environment(\.editMode) var editMode
  @State private var selections: [String] = []
  @State private var colors: ["Red", "Yellow", "Blue"]

  private var isEditing: Bool {
    if editMode?.wrappedValue.isEditing == true {
       return true
     }
     return false
  }
  
  var body: some View {

    List(selection: $selections) {
      ForEach(colors, id: \.self) { color in
        Text("Color")
      }
    }
    .toolbar {
      ToolbarItem(placement: .navigationBarTrailing) {
        EditButton()
      }
    }
    .onChange(of: isEditing) { value in
      if value == false {
        selection.removeAll()
      }
    }
  }

}

1
这个解决方案应该得到更多的赞同。 - Filip Krawczyk

0
如果有人想使用SwiftUI的EditButton()而不是自定义一个Button,并且仍然希望在isEditing状态更改时执行操作,则可以使用View扩展
extension View {
    func onChangeEditMode(editMode: EditMode?, perform: @escaping (EditMode?)->()) -> some View {
        ZStack {
            Text(String(describing: editMode))
                .opacity(0)
                .onChange(of: editMode, perform: perform)
            self
        }
    }
}

然后你可以像这样使用它

struct TestEditModeView: View {
    @Environment(\.editMode) var editMode
    @State private var editModeDescription: String = "nil"
    var body: some View {
        VStack {
            Text(editModeDescription)
            EditButton()
        }
        .onChangeEditMode(editMode: editMode?.wrappedValue) {
            editModeDescription = String(describing: $0)
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接