🧝‍♂️

NavigationStackの中でEditButtonを使うと、editModeが正しく取得できない

2023/04/28に公開2

Listの並び替えや削除などに使用するEditButton()ですが、環境変数のeditModeを使用して現在の編集状態を取得しようとしたところ、NavigationStack内でEditButtonを使用するケースだとeditModeが正しく反映されていないことが分かった。

環境

  • Xcode 14.2
  • iOS 16.2

原因発生例

コード

import SwiftUI

struct ContentView: View {
    
    @Environment(\.editMode) var editMode
    
    private var isActiveEditing: Bool {
        return editMode?.wrappedValue == .active
    }
    
    var body: some View {
        
        VStack {
            NavigationStack {
                List {
                    ForEach(0..<5) { number in
                        Text(String(number))
                    }
                    .onMove(perform: moveItem)
                }
                .navigationTitle("List")
                .toolbar {
                    EditButton()
                }
            }
            
            // editModeの状態によって変化する
            Text(isActiveEditing ? "アクティブ" : "インアクティブ")
                .foregroundColor(isActiveEditing ? .accentColor : .gray)
        }
    }
    
    private func moveItem(from source: IndexSet, to destination: Int) {
            print("moveItem")
        }
}

結果

EditButton()でリストは編集中のUIに切り替わるが、editModeで値で切り替わる下部のテキストはずっとインアクティブのままです。

demo1

コード

var body: some View {
    
    VStack {
        NavigationStack {
            List {
                ForEach(0..<5) { number in
                    Text(String(number))
                }
                .onMove(perform: moveItem)
            }
            .navigationTitle("List")
        }
        // NavigationStack外に出す
        EditButton()
        
        // editModeの状態によって変化する
        Text(isActiveEditing ? "アクティブ" : "インアクティブ")
            .foregroundColor(isActiveEditing ? .accentColor : .gray)
    }
}

結果

NavigationStack外にEditButton()を出して試してみると、editModeの値の変化は取得出来るが今度はリストが編集状態にならない。

demo2

解決策

あまりやりたくないですが、EditButton()と同様の振る舞いをするEditButtonを作成することで解決します。

EditButton

// 自家製のEditButton
struct EditButton: View {
    
    @Binding var editMode: EditMode
    
    var body: some View {
        
        Button(editMode.isEditing ? "Done" : "Edit") {
            switch editMode {
            case .active:
                editMode = .inactive
            case .inactive:
                editMode = .active
            default:
                break
            }
        }
    }
}

コード

クラス変数として記述していた環境変数を@State変数に変更して、Button操作によってその値を変更しています。
また、リストの編集状態を変更する為に、environmentモディファイアを使い、環境変数側のeditModeも切り替えています。

struct ContentView: View {
        
    @State private var editMode: EditMode = .inactive
    
    private var isActiveEditing: Bool {
        return editMode == .active
    }
    
    var body: some View {
        
        VStack {
            NavigationView {
                    List {
                        ForEach(0..<5) { number in
                            Text(String(number))
                        }
                        .onMove(perform: moveItem)
                    }
                    .navigationTitle("List")
                    .toolbar {
                        EditButton(editMode: $editMode)
                    }
                    .environment(\.editMode, $editMode)
            }
            .navigationViewStyle(.stack)
            
            // editModeの状態によって変化する
            Text(isActiveEditing ? "アクティブ" : "インアクティブ")
                .foregroundColor(isActiveEditing ? .accentColor : .gray)
        }
    }
    
    private func moveItem(from source: IndexSet, to destination: Int) {
            print("moveItem")
        }
}

結果

これで意図した動き出来るようになりました。

demo3

おわりに

SwiftUIのバグなのか?まだ確証まで至っていませんが、NavigationStackEditButton()を使用するとeditModeの値が正しく取得できなくて困ってしまいました。
今回は自家製のEdtiButtonを作成することで解決しましたが、力技なのであまりやりたくなく、もし他に簡単な対策をご存知の方いらっしゃいましたら、優しく教えていただけると嬉しいです。

littleossa

参考

https://stackoverflow.com/questions/64417416/swiftui-editbutton-bug
https://www.reddit.com/r/SwiftUI/comments/121esru/navigration_editbutton_bug/

Discussion