Aleph
Aleph

Reputation: 593

Focus on a TextField using a keyboard shortcut

I have a macOS Monterrey app that has a TextField on the toolbar. I use this to search for text on my app. Now, I'm trying to add a keyboard shortcut to focus on the TextField. I've tried the code below, adding button with a shortcut as a way to test whether this is doable, but I can't get it to work. The .focused() doesn't do anything.

Beyond that, I have added a new menu item Find and set the keyboard shortcut to cmd-L but I don't know either how to send the focus to the TextField.

What am I missing?

struct AllData: View {
    @FocusState private var searchFieldIsFocused: Bool
    @State var searchText: String = ""

    var body: some View {
        NavigationView {
            List(data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText) }) { 
               //...
            }
        }
        .navigationTitle("A Title")
        .toolbar {
            ToolbarItem(placement: .automatic) {
                TextField("Search...", text: $searchText)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .frame(minWidth: 200)
                    .focused($searchFieldIsFocused)
            }
            
            //test for the focus
            ToolbarItem(placement: .automatic) {
                Button(action: {
                    print("Plus pressed")
                    searchFieldIsFocused = true
                }) {
                    Image(systemName: "plus")
                }
                .keyboardShortcut("e", modifiers: [.command])
            }
        }
    }
}

Edit after Yrb comments

It seems .focusable will not work with a TextField on a toolbar, so he suggested using .searchable.

Trying with .searchable

struct AllData: View {
    @FocusState private var searchFieldIsFocused: Bool
    @State var searchText: String = ""

    var body: some View {
        NavigationView {
            List(data.notes.filter { searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText) }) { 
               //...
            }
            .searchable(
               text: $searchText,
               placement: .toolbar,
               prompt: "Search..."
            )
        }
        .navigationTitle("A Title")
        .toolbar {
            //test for the focus
            ToolbarItem(placement: .automatic) {
                Button(action: {
                    print("Plus pressed")
                    searchFieldIsFocused = true
                }) {
                    Image(systemName: "plus")
                }
                .keyboardShortcut("e", modifiers: [.command])
            }
        }
    }
}

Observations:

  1. I have no control where the search field will appear, it seems to be the last item on the toolbar, which is not what I want, but OK.
  2. There is no way that I can find to add a .focusable so I can jump to the search with a keyboard shortcut, which is the reason for this question
  3. When you search, the list get's filtered, but when you try to navigate the list with the arrows (keyboard), upon selecting the next item, the focus returns to the search field, it doesn't remain on the list, making navigation very slow and painful.

I'm sure I'm missing something, and probably my code is wrong. Any clues?

Edit #2

It seems this is not possible with a .searchable:

.searchable modifier with a keyboard shortcut

Upvotes: 6

Views: 1111

Answers (1)

camdenfullmer
camdenfullmer

Reputation: 56

I ended up getting the following to work on macOS using the .searchable() modifier (only tested on macOS 12.3):

import SwiftUI

@main
struct MyApp: App {
    
    @State var searchText = ""
    var items = ["Item 1", "Item 2", "Item 3"]
    var searchResults: [String] {
        if searchText.isEmpty {
            return items
        } else {
            return items.filter { $0.contains(searchText) }
        }
    }

    var body: some Scene {
        WindowGroup {
            List(self.searchResults, id: \.self) { item in
                Text(item)
            }.searchable(text: self.$searchText)
        }.commands {
            CommandMenu("Find") {
                Button("Find") {
                    if let toolbar = NSApp.keyWindow?.toolbar,
                        let search = toolbar.items.first(where: { $0.itemIdentifier.rawValue == "com.apple.SwiftUI.search" }) as? NSSearchToolbarItem {
                        search.beginSearchInteraction()
                    }
                }.keyboardShortcut("f", modifiers: .command)
            }
        }
    }
}

Upvotes: 4

Related Questions