2

I'm trying to implement a list which can be navigated with arrow keys - up/down. I've created layout, but now I don't totally understand how(and where) to make up/down keys intercepted so I could add my custom logic. I already tried onMoveCommand with focusable but that did not work(wasn't firing at all)

Code I have - below

public var body: some View {
        VStack(spacing: 0.0) {
            VStack {
                HStack(alignment: .center, spacing: 0) {
                    Image(systemName: "command")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 20, height: 20)
                        .padding(.leading, 20)
                        .offset(x: 0, y: 1)
                    TextField("Search Commands", text: $state.commandQuery)
                        .font(.system(size: 20, weight: .light, design: .default))
                        .textFieldStyle(.plain)
                        .onReceive(
                            state.$commandQuery
                                .debounce(for: .seconds(0.1), scheduler: DispatchQueue.main)
                        ) { val in
                            state.fetchMatchingCommands(val: val)
                        }
                    .padding(16)
                    .foregroundColor(Color(.systemGray).opacity(0.85))
                    .background(EffectView(.sidebar, blendingMode: .behindWindow))
                }
            }
            Divider()
            VStack(spacing: 0) {
                List(state.filteredCommands.isEmpty && state.commandQuery.isEmpty ?
                     commandManager.commands : state.filteredCommands, selection: $selectedItem) { command in
                        VStack(alignment: .leading, spacing: 0) {
                            Text(command.title).foregroundColor(Color.white)
                                .padding(EdgeInsets.init(top: 0, leading: 10, bottom: 0, trailing: 0))
                                .frame(height: 10)
                        }.frame(maxWidth: .infinity, maxHeight: 15, alignment: .leading)
                            .listRowBackground(self.selectedItem == command ?
                                               RoundedRectangle(cornerRadius: 5, style: .continuous)
                                .fill(Color(.systemBlue)) :
                                                RoundedRectangle(cornerRadius: 5, style: .continuous)
                                .fill(Color.clear) )

                        .onTapGesture {
                            self.selectedItem = command
                            callHandler(command: command)
                        }.onHover(perform: { _ in self.selectedItem = command })
                    }.listStyle(SidebarListStyle())
            }
        }
        .background(EffectView(.sidebar, blendingMode: .behindWindow))
        .foregroundColor(.gray)
        .edgesIgnoringSafeArea(.vertical)
        .frame(minWidth: 600,
           minHeight: self.state.isShowingCommandsList ? 400 : 28,
           maxHeight: self.state.isShowingCommandsList ? .infinity : 28)
    }

This is how it looks - and I want to make focus move between found list items

enter image description here

Avdept
  • 2,261
  • 2
  • 26
  • 48
  • What is code for EffectView or state or so many other things? can you give a code that already build? – ios coder Aug 05 '22 at 17:16
  • Focusable should fire or we should understand why it is not firing, can you manually select one of the list items and then use arrow keys and see if that works. If so then focusable is the problem to solve – user1046037 Aug 05 '22 at 17:22
  • Refer https://developer.apple.com/wwdc21/10023 – user1046037 Aug 05 '22 at 17:23

1 Answers1

4

If I understand your question correctly, you want to use the arrow keys to "move" from the search TextField, to the list of items, and then navigate the list with the up/down arrow keys.

Try something simple like this example code, to monitor the up/down arrow keys, and take the appropriate action.

Adjust/tweak the logic to suit your needs.

import Foundation
import SwiftUI
import AppKit


struct ContentView: View {
    let fruits = ["apples", "pears", "bananas", "apricot", "oranges"]
    @State var selection: Int?
    @State var search = ""
    
    var body: some View {
        VStack {
            VStack {
                HStack(alignment: .center, spacing: 0) {
                    Image(systemName: "command")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 20, height: 20)
                        .padding(.leading, 20)
                        .offset(x: 0, y: 1)
                    TextField("Search", text: $search)
                        .font(.system(size: 20, weight: .light, design: .default))
                        .textFieldStyle(.plain)
                }
            }
            Divider()
            List(selection: $selection) {
                ForEach(fruits.indices, id: \.self) { index in
                    Text(fruits[index]).tag(index)
                }
            }
            .listStyle(.bordered(alternatesRowBackgrounds: true))
        }
        .onAppear {
            NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { nsevent in
                if selection != nil {
                    if nsevent.keyCode == 125 { // arrow down
                        selection = selection! < fruits.count ? selection! + 1 : 0
                    } else {
                        if nsevent.keyCode == 126 { // arrow up
                            selection = selection! > 1 ? selection! - 1 : 0
                        }
                    }
                } else {
                    selection = 0
                }
                return nsevent
            }
        }
    }
}