skip to Main Content

I’m working on a macOs 13 app and I’m using the new NavigationSplitView. The problem is that it doesn’t let us use the .onDeleteCommand(perform:) (or maybe i’m using it wrong). Here is what I did :

In order to use the .onDeleteCommand(perform:), the view needs to be focused. I did a simple app showing 3 rectangles that I can select with the TAB key, and when I hit DELETE key or in the menu bar Edit > Delete (both trigger the .onDeleteCommand), it switches to white or to its original colour.

VStack {
    Rectangle()
        .fill((isColorDeleted.contains(.blue) ? Color.white : Color.blue))
        .padding()
        .focusable()
        .focused($focusedColor, equals: .blue)

    Rectangle()
        .fill((isColorDeleted.contains(.red) ? Color.white : Color.red))
        .padding()
        .focusable()
        .focused($focusedColor, equals: .red)

    Rectangle()
        .fill((isColorDeleted.contains(.yellow) ? Color.white : Color.yellow))
        .padding()
        .focusable()
        .focused($focusedColor, equals: .yellow)
}
.onDeleteCommand {
    if let focusedColor {
        if !isColorDeleted.contains(focusedColor) {
            isColorDeleted.append(focusedColor)
        } else {
            let idx = isColorDeleted.firstIndex(of: focusedColor)!
            isColorDeleted.remove(at: idx)
        }
    }
}

^^^ This works as it should ^^^

But if you put it in a NavigationSplitView like this :

NavigationSplitView(columnVisibility: $visibility) {
    List {
        Text("Main page")
    }
} detail: {
    VStack {
        Rectangle()
            .fill((isColorDeleted.contains(.blue) ? Color.white : Color.blue))
            .padding()
            .focusable()
            .focused($focusedColor, equals: .blue)

        Rectangle()
            .fill((isColorDeleted.contains(.red) ? Color.white : Color.red))
            .padding()
            .focusable()
            .focused($focusedColor, equals: .red)

        Rectangle()
            .fill((isColorDeleted.contains(.yellow) ? Color.white : Color.yellow))
            .padding()
            .focusable()
            .focused($focusedColor, equals: .yellow)
    }
    .onDeleteCommand {
        if let focusedColor {
            if !isColorDeleted.contains(focusedColor) {
                isColorDeleted.append(focusedColor)
            } else {
                let idx = isColorDeleted.firstIndex(of: focusedColor)!
                isColorDeleted.remove(at: idx)
            }
        }
    }
}

If you press DELETE or Edit > Delete when a rectangle is focused as I explained, it doesn’t anything. In fact, the Edit > Delete isn’t clickable at all.

2

Answers


  1. You need to use onDeleteCommand modifier directly on NavigationSplitView like this:

    NavigationSplitView(columnVisibility: $visibility) {
        List {
            Text("Main page")
        }
    } detail: {
        VStack {
            Rectangle()
                .fill((isColorDeleted.contains(.blue) ? Color.white : Color.blue))
                .padding()
                .focusable()
                .focused($focusedColor, equals: .blue)
    
            Rectangle()
                .fill((isColorDeleted.contains(.red) ? Color.white : Color.red))
                .padding()
                .focusable()
                .focused($focusedColor, equals: .red)
    
            Rectangle()
                .fill((isColorDeleted.contains(.yellow) ? Color.white : Color.yellow))
                .padding()
                .focusable()
                .focused($focusedColor, equals: .yellow)
        }
    }
    .onDeleteCommand {
        ...
    }
    
    Login or Signup to reply.
  2. With some input from https://swiftwithmajid.com/2021/03/03/focusedvalue-and-focusedbinding-property-wrappers-in-swiftui, I could solve this problem for me as follows:

    • Create the FocusedValues extension.
    • Define the @FocusedValue in the view where the NavigationSplitView resides.
    • Add the .onDeleteCommand to the NavigationSplitView
    • Replace the .onDeleteCommand methods in the content/detail views with the .focusedValue methods

    This also works nicely for multiple views. Simply use the .focusedValue method instead of .onDeleteCommand. No further references or parameters required.

        import SwiftUI
    
        extension FocusedValues {
    
            struct DeleteValueKey: FocusedValueKey {
                typealias Value = () -> Void
            }
    
            var delete: DeleteValueKey.Value? {
                get { self[DeleteValueKey.self] }
                set { self[DeleteValueKey.self] = newValue }
            }
        }
    
        struct Ocean: Identifiable, Hashable {
            let name: String
            let id = UUID()
        }
    
        struct ContentView: View {
            
            private var oceans = [
                Ocean(name: "Pacific"),
                Ocean(name: "Atlantic"),
                Ocean(name: "Indian"),
                Ocean(name: "Southern"),
                Ocean(name: "Arctic")
            ]
            
            @FocusedValue (.delete) var delete
            
            var body: some View {
    
                VStack {
                    NavigationSplitView() {
                        NonWorkingView(oceans: oceans)
                    } detail: {
                        WorkingView(oceans: oceans)
                    }
                    .onDeleteCommand(perform: delete)
                }
            }
        }
    
        struct NonWorkingView: View {
            
            @State var multiSelection = Set<UUID>()
            @State var oceans: [Ocean]
            
            func delete() {
                for id in multiSelection { print("(id) deleted")}
            }
            
            var body: some View {
    
                List(oceans, selection: $multiSelection) {
                    Text($0.name)
                }
                .onDeleteCommand(perform: delete)
                //  ^^^^^^^ DOES NOT work
            }
        }
    
        struct WorkingView: View {
            
            @State var oceans: [Ocean]
            @State var multiSelection = Set<UUID>()
            
            func delete() {
                for id in multiSelection { print("(id) deleted")}
            }
            
            var body: some View {
    
                List(oceans, selection: $multiSelection) {
                    Text($0.name)
                }
                .focusedValue(.delete, delete)
                //  ^^^^^^^ DOES work
            }
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search