Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI, shortcutKey without button/View instance -- is it possible?

in swiftUI exist .keyboardShortcut() modifier:

// run "doSomeAction()" on press of "b" key on the keyboard
Button("SomeBtn") { doSomeAction() }
    .keyboardShortcut("b", modifiers: [])

but for usage of this modifier you need to have an instance of button.

Question is -- is there are possible to apply some modifier without creation of any useless Views?

What if I need to have 20 actions assigned to some keyboard keys?

Do I need to create 20 buttons and make it invisible even if this is really bad for performance?

like image 873
Andrew Avatar asked Jul 29 '26 12:07

Andrew


1 Answers

Much better way than my another answer

Profits of this solution:

  • Its works faster than .keyboardShortcut() way
  • It does not need to create extra views/buttons;
  • it's works on ANY keyboard layout ( .keyboardShortcut() works ONLY IN ONE keyboard layout);

Disadvantages:

  • no hotkeys displayed in menus
  • need to create custom hotkeys logic for different states of view (in some views need to use one hotkeys, in some subviews - another hotkeys)

Best way of usage:

someView()
    .keyboardReaction { myMainViewHotkeys($0) }

///////

func myMainViewHotkeys(_ event: NSEvent) -> NSEvent {
    switch event.keyCode {
    case KeyCode.escape:
        print("esc pressed!")
        return nil // disable beep sound
    case KeyCode.a:
        print("A pressed!")
        return nil // disable beep sound
    default:
        return event // beep sound will be here
    }
}

keyboardReactionModifier.swift

#if os(macOS)
import Foundation
import SwiftUI

@available(macOS 11.0, *)
public extension View {
    
    /// * Reaction on keyboard's .keyDown will be executed only in case of view located at window where window.isKeyWindow == true
    /// * But reaction will be even if this window displays .sheet
    /// * KeyCode struct located in Essentials
    /// * Active Keyboard Layout does not matter
    /// ```
    ///.keyboardReaction { event in
    ///    switch event.keyCode {
    ///    case KeyCode.escape:
    ///        print("esc pressed!")
    ///        return nil // disable beep sound
    ///    case KeyCode.a:
    ///        print("A pressed!")
    ///        return nil // disable beep sound
    ///    default:
    ///        return event // beep sound will be here
    ///    }
    ///}
    /// ```
    func keyboardReaction(action: @escaping (NSEvent) -> (NSEvent?) ) -> some View {
        self.modifier(KeyboardReactiomModifier(action: action))
    }
}

@available(macOS 11.0, *)
private struct KeyboardReactiomModifier: ViewModifier {
    let action: (NSEvent) -> (NSEvent?)
    
    @State var window: NSWindow? = nil
    
    func body(content: Content) -> some View {
        content
            .wndAccessor { self.window = $0 }
            .onAppear {
                NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
                    guard let window = window,
                          window.isKeyWindow
                    else { return event }
                    
                    return action(event)
                }
            }
    }
}
#endif

wndAccessorModifier.swift

#if os(macOS)
import SwiftUI
public extension View {
    /// With this modifier you're able to access to window from View.
    ///
    /// Usage:
    /// ```
    /// someView
    ///     .wndAccessor { wnd
    ///         self.window = wnd
    ///     }
    /// ```
    /// another sample:
    /// ```
    /// someView
    ///     .wndAccessor { wnd
    ///         wnd.title = "THIS... IS... WINDOOOOOW!"
    ///     }
    /// ```
    func wndAccessor(_ act: @escaping (NSWindow?) -> () )
        -> some View {
            self.modifier(WndTitleConfigurer(act: act))
    }
}

private struct WndTitleConfigurer: ViewModifier {
    let act: (NSWindow?) -> ()
    
    @State var window: NSWindow? = nil
    
    func body(content: Content) -> some View {
        content
            .getWindow($window)
            .onChange(of: window, perform: act )
    }
}

//////////////////////////////
///HELPERS
/////////////////////////////

private extension View {
    func getWindow(_ wnd: Binding<NSWindow?>) -> some View {
        self.background(WindowAccessor(window: wnd))
    }
}

private struct WindowAccessor: NSViewRepresentable {
    @Binding var window: NSWindow?
    
    public func makeNSView(context: Context) -> NSView {
        let view = NSView()
        DispatchQueue.main.async {
            self.window = view.window
        }
        return view
    }
    
    public func updateNSView(_ nsView: NSView, context: Context) {}
}

#endif

KeyCode.swift

import SwiftUI

public struct KeyCode {
    public static let returnKey                 : UInt16 = 0x24
    public static let enter                     : UInt16 = 36 //0x4C
    public static let tab                       : UInt16 = 0x30
    public static let space                     : UInt16 = 0x31
    public static let delete                    : UInt16 = 0x33
    public static let escape                    : UInt16 = 0x35
    public static let command                   : UInt16 = 0x37
    public static let shift                     : UInt16 = 0x38
    public static let capsLock                  : UInt16 = 0x39
    public static let option                    : UInt16 = 0x3A
    public static let control                   : UInt16 = 0x3B
    public static let rightShift                : UInt16 = 0x3C
    public static let rightOption               : UInt16 = 0x3D
    public static let rightControl              : UInt16 = 0x3E
    public static let leftArrow                 : UInt16 = 0x7B
    public static let rightArrow                : UInt16 = 0x7C
    public static let downArrow                 : UInt16 = 0x7D
    public static let upArrow                   : UInt16 = 0x7E
    public static let volumeUp                  : UInt16 = 0x48
    public static let volumeDown                : UInt16 = 0x49
    public static let mute                      : UInt16 = 0x4A
    public static let help                      : UInt16 = 0x72
    public static let home                      : UInt16 = 0x73
    public static let pageUp                    : UInt16 = 0x74
    public static let forwardDelete             : UInt16 = 0x75
    public static let end                       : UInt16 = 0x77
    public static let pageDown                  : UInt16 = 0x79
    public static let function                  : UInt16 = 0x3F
    public static let f1                        : UInt16 = 0x7A
    public static let f2                        : UInt16 = 0x78
    public static let f4                        : UInt16 = 0x76
    public static let f5                        : UInt16 = 0x60
    public static let f6                        : UInt16 = 0x61
    public static let f7                        : UInt16 = 0x62
    public static let f3                        : UInt16 = 0x63
    public static let f8                        : UInt16 = 0x64
    public static let f9                        : UInt16 = 0x65
    public static let f10                       : UInt16 = 0x6D
    public static let f11                       : UInt16 = 0x67
    public static let f12                       : UInt16 = 0x6F
    public static let f13                       : UInt16 = 0x69
    public static let f14                       : UInt16 = 0x6B
    public static let f15                       : UInt16 = 0x71
    public static let f16                       : UInt16 = 0x6A
    public static let f17                       : UInt16 = 0x40
    public static let f18                       : UInt16 = 0x4F
    public static let f19                       : UInt16 = 0x50
    public static let f20                       : UInt16 = 0x5A
    
    // US-ANSI Keyboard Positions
    // eg. These key codes are for the physical key (in any keyboard layout)
    // at the location of the named key in the US-ANSI layout.
    public static let a                         : UInt16 = 0x00
    public static let b                         : UInt16 = 0x0B
    public static let c                         : UInt16 = 0x08
    public static let d                         : UInt16 = 0x02
    public static let e                         : UInt16 = 0x0E
    public static let f                         : UInt16 = 0x03
    public static let g                         : UInt16 = 0x05
    public static let h                         : UInt16 = 0x04
    public static let i                         : UInt16 = 0x22
    public static let j                         : UInt16 = 0x26
    public static let k                         : UInt16 = 0x28
    public static let l                         : UInt16 = 0x25
    public static let m                         : UInt16 = 0x2E
    public static let n                         : UInt16 = 0x2D
    public static let o                         : UInt16 = 0x1F
    public static let p                         : UInt16 = 0x23
    public static let q                         : UInt16 = 0x0C
    public static let r                         : UInt16 = 0x0F
    public static let s                         : UInt16 = 0x01
    public static let t                         : UInt16 = 0x11
    public static let u                         : UInt16 = 0x20
    public static let v                         : UInt16 = 0x09
    public static let w                         : UInt16 = 0x0D
    public static let x                         : UInt16 = 0x07
    public static let y                         : UInt16 = 0x10
    public static let z                         : UInt16 = 0x06
    
    public static let zero                      : UInt16 = 0x1D
    public static let one                       : UInt16 = 0x12
    public static let two                       : UInt16 = 0x13
    public static let three                     : UInt16 = 0x14
    public static let four                      : UInt16 = 0x15
    public static let five                      : UInt16 = 0x17
    public static let six                       : UInt16 = 0x16
    public static let seven                     : UInt16 = 0x1A
    public static let eight                     : UInt16 = 0x1C
    public static let nine                      : UInt16 = 0x19
    
    public static let equals                    : UInt16 = 0x18
    public static let minus                     : UInt16 = 0x1B
    public static let semicolon                 : UInt16 = 0x29
    public static let apostrophe                : UInt16 = 0x27
    public static let comma                     : UInt16 = 0x2B
    public static let period                    : UInt16 = 0x2F
    public static let forwardSlash              : UInt16 = 0x2C
    public static let backslash                 : UInt16 = 0x2A
    public static let grave                     : UInt16 = 0x32
    public static let leftBracket               : UInt16 = 0x21
    public static let rightBracket              : UInt16 = 0x1E
    
    public static let keypadDecimal             : UInt16 = 0x41
    public static let keypadMultiply            : UInt16 = 0x43
    public static let keypadPlus                : UInt16 = 0x45
    public static let keypadClear               : UInt16 = 0x47
    public static let keypadDivide              : UInt16 = 0x4B
    public static let keypadEnter               : UInt16 = 0x4C
    public static let keypadMinus               : UInt16 = 0x4E
    public static let keypadEquals              : UInt16 = 0x51
    public static let keypad0                   : UInt16 = 0x52
    public static let keypad1                   : UInt16 = 0x53
    public static let keypad2                   : UInt16 = 0x54
    public static let keypad3                   : UInt16 = 0x55
    public static let keypad4                   : UInt16 = 0x56
    public static let keypad5                   : UInt16 = 0x57
    public static let keypad6                   : UInt16 = 0x58
    public static let keypad7                   : UInt16 = 0x59
    public static let keypad8                   : UInt16 = 0x5B
    public static let keypad9                   : UInt16 = 0x5C
}
like image 135
Andrew Avatar answered Aug 02 '26 16:08

Andrew



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!