SwiftUI,shortcutKey没有按钮/查看实例——这可能吗



在swiftUI中存在.keyboardShortcut()修饰符:

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

但要使用此修饰符,您需要有按钮的实例。

问题是——是否有可能在不创建任何无用视图的情况下应用一些修饰符?

如果我需要为一些键盘键分配20个操作,该怎么办?

我是否需要创建20个按钮并使其不可见,即使这对性能非常不利?

请看另一个答案。它比这个好多了。这个保存在这里只是为了历史:(


此解决方案的利润:

  • 它的工作速度比.keyboardShortcut()方式快
  • 它不需要创建额外的视图/按钮
  • 它适用于任何键盘布局(.keyboardShortcut()仅适用于一个键盘布局(

缺点:

  • 菜单中未显示热键
  • 需要为不同的视图状态创建自定义热键逻辑(在某些视图中需要使用一个热键,在某些子视图中使用另一个热键(

解决方案示例:

struct WindowView: View {
@ObservedObject var model: WindowModel
var body: some View {
Text("some text")
.padding(60)
// THIS IS FINAL SOLUTION
.addCustomHotkeys(model.hotkeys)
// THIS IS FINAL SOLUTION
}
}

ViewModel:内的热键初始化

// THIS IS FINAL SOLUTION
let hotkeys: [HotkeyCombination] = [
HotkeyCombination(keyBase: [], key: .kVK_ANSI_Q) { print("Q") },
HotkeyCombination(keyBase: [.option], key: .kVK_ANSI_Q) { print("Option+Q") }
]

使用我的解决方案的文件:

HotKeys.swift:

import Foundation
import SwiftUI
import Combine
@available(OSX 11.0, *)
extension View {
func addCustomHotkeys( _ hotkeys: [HotkeyCombination] ) -> some View {
self.modifier(HotKeysMod(hotkeys))
}
}
@available(OSX 11.0, *)
public struct HotKeysMod: ViewModifier {
@State var subs = Set<AnyCancellable>() // Cancel onDisappear
var hotkeys: [HotkeyCombination]

init(_ hotkeys: [HotkeyCombination] ) {
self.hotkeys = hotkeys
}

public func body(content: Content) -> some View {
ZStack {
DisableSoundsView(hotkeys:hotkeys)
content
}
}
}
struct DisableSoundsView: NSViewRepresentable {
var hotkeys: [HotkeyCombination]

func makeNSView(context: Context) -> NSView {
let view = DisableSoundsNSView()

view.hotkeys = hotkeys

return view
}

func updateNSView(_ nsView: NSView, context: Context) { }
}
class DisableSoundsNSView: NSView {
var hotkeys: [HotkeyCombination] = []

override func performKeyEquivalent(with event: NSEvent) -> Bool {
return hotkeysSubscription(combinations: hotkeys)
}
}
fileprivate func hotkeysSubscription(combinations: [HotkeyCombination]) -> Bool {
for comb in combinations {
let basePressedCorrectly = comb.keyBasePressed

if basePressedCorrectly && comb.key.isPressed {
comb.action()
return true
}
}

return false
}
///////////////////////
///HELPERS
///////////////////////
struct HotkeyCombination {
let keyBase: [KeyBase]
let key: CGKeyCode
let action: () -> ()
}
extension HotkeyCombination {
var keyBasePressed: Bool {
let mustBePressed    = KeyBase.allCases.filter{ keyBase.contains($0) }
let mustBeNotPressed = KeyBase.allCases.filter{ !keyBase.contains($0) }

for base in mustBePressed {
if !base.isPressed {
return false
}
}

for base in mustBeNotPressed {
if base.isPressed {
return false
}
}

return true
}
}
enum KeyBase: CaseIterable {
case option
case command
case shift
case control

var isPressed: Bool {
switch self {
case .option:
return CGKeyCode.kVK_Option.isPressed  || CGKeyCode.kVK_RightOption.isPressed
case .command:
return CGKeyCode.kVK_Command.isPressed || CGKeyCode.kVK_RightCommand.isPressed
case .shift:
return CGKeyCode.kVK_Shift.isPressed   || CGKeyCode.kVK_RightShift.isPressed
case .control:
return CGKeyCode.kVK_Control.isPressed || CGKeyCode.kVK_RightControl.isPressed
}
}
}

CGKeyCode扩展取自:

https://gist.github.com/chipjarred/cbb324c797aec865918a8045c4b51d14

CGKeyCode.swift:

import Foundation
///https://gist.github.com/chipjarred/cbb324c797aec865918a8045c4b51d14
extension CGKeyCode {
static let kVK_ANSI_A                    : CGKeyCode = 0x00
static let kVK_ANSI_S                    : CGKeyCode = 0x01
static let kVK_ANSI_D                    : CGKeyCode = 0x02
static let kVK_ANSI_F                    : CGKeyCode = 0x03
static let kVK_ANSI_H                    : CGKeyCode = 0x04
static let kVK_ANSI_G                    : CGKeyCode = 0x05
static let kVK_ANSI_Z                    : CGKeyCode = 0x06
static let kVK_ANSI_X                    : CGKeyCode = 0x07
static let kVK_ANSI_C                    : CGKeyCode = 0x08
static let kVK_ANSI_V                    : CGKeyCode = 0x09
static let kVK_ANSI_B                    : CGKeyCode = 0x0B
static let kVK_ANSI_Q                    : CGKeyCode = 0x0C
static let kVK_ANSI_W                    : CGKeyCode = 0x0D
static let kVK_ANSI_E                    : CGKeyCode = 0x0E
static let kVK_ANSI_R                    : CGKeyCode = 0x0F
static let kVK_ANSI_Y                    : CGKeyCode = 0x10
static let kVK_ANSI_T                    : CGKeyCode = 0x11
static let kVK_ANSI_1                    : CGKeyCode = 0x12
static let kVK_ANSI_2                    : CGKeyCode = 0x13
static let kVK_ANSI_3                    : CGKeyCode = 0x14
static let kVK_ANSI_4                    : CGKeyCode = 0x15
static let kVK_ANSI_6                    : CGKeyCode = 0x16
static let kVK_ANSI_5                    : CGKeyCode = 0x17
static let kVK_ANSI_Equal                : CGKeyCode = 0x18
static let kVK_ANSI_9                    : CGKeyCode = 0x19
static let kVK_ANSI_7                    : CGKeyCode = 0x1A
static let kVK_ANSI_Minus                : CGKeyCode = 0x1B
static let kVK_ANSI_8                    : CGKeyCode = 0x1C
static let kVK_ANSI_0                    : CGKeyCode = 0x1D
static let kVK_ANSI_RightBracket         : CGKeyCode = 0x1E
static let kVK_ANSI_O                    : CGKeyCode = 0x1F
static let kVK_ANSI_U                    : CGKeyCode = 0x20
static let kVK_ANSI_LeftBracket          : CGKeyCode = 0x21
static let kVK_ANSI_I                    : CGKeyCode = 0x22
static let kVK_ANSI_P                    : CGKeyCode = 0x23
static let kVK_ANSI_L                    : CGKeyCode = 0x25
static let kVK_ANSI_J                    : CGKeyCode = 0x26
static let kVK_ANSI_Quote                : CGKeyCode = 0x27
static let kVK_ANSI_K                    : CGKeyCode = 0x28
static let kVK_ANSI_Semicolon            : CGKeyCode = 0x29
static let kVK_ANSI_Backslash            : CGKeyCode = 0x2A
static let kVK_ANSI_Comma                : CGKeyCode = 0x2B
static let kVK_ANSI_Slash                : CGKeyCode = 0x2C
static let kVK_ANSI_N                    : CGKeyCode = 0x2D
static let kVK_ANSI_M                    : CGKeyCode = 0x2E
static let kVK_ANSI_Period               : CGKeyCode = 0x2F
static let kVK_ANSI_Grave                : CGKeyCode = 0x32
static let kVK_ANSI_KeypadDecimal        : CGKeyCode = 0x41
static let kVK_ANSI_KeypadMultiply       : CGKeyCode = 0x43
static let kVK_ANSI_KeypadPlus           : CGKeyCode = 0x45
static let kVK_ANSI_KeypadClear          : CGKeyCode = 0x47
static let kVK_ANSI_KeypadDivide         : CGKeyCode = 0x4B
static let kVK_ANSI_KeypadEnter          : CGKeyCode = 0x4C
static let kVK_ANSI_KeypadMinus          : CGKeyCode = 0x4E
static let kVK_ANSI_KeypadEquals         : CGKeyCode = 0x51
static let kVK_ANSI_Keypad0              : CGKeyCode = 0x52
static let kVK_ANSI_Keypad1              : CGKeyCode = 0x53
static let kVK_ANSI_Keypad2              : CGKeyCode = 0x54
static let kVK_ANSI_Keypad3              : CGKeyCode = 0x55
static let kVK_ANSI_Keypad4              : CGKeyCode = 0x56
static let kVK_ANSI_Keypad5              : CGKeyCode = 0x57
static let kVK_ANSI_Keypad6              : CGKeyCode = 0x58
static let kVK_ANSI_Keypad7              : CGKeyCode = 0x59
static let kVK_ANSI_Keypad8              : CGKeyCode = 0x5B
static let kVK_ANSI_Keypad9              : CGKeyCode = 0x5C
// keycodes for keys that are independent of keyboard layout
static let kVK_Return                    : CGKeyCode = 0x24
static let kVK_Tab                       : CGKeyCode = 0x30
static let kVK_Space                     : CGKeyCode = 0x31
static let kVK_Delete                    : CGKeyCode = 0x33
static let kVK_Escape                    : CGKeyCode = 0x35
static let kVK_Command                   : CGKeyCode = 0x37
static let kVK_Shift                     : CGKeyCode = 0x38
static let kVK_CapsLock                  : CGKeyCode = 0x39
static let kVK_Option                    : CGKeyCode = 0x3A
static let kVK_Control                   : CGKeyCode = 0x3B
static let kVK_RightCommand              : CGKeyCode = 0x36 // Out of order
static let kVK_RightShift                : CGKeyCode = 0x3C
static let kVK_RightOption               : CGKeyCode = 0x3D
static let kVK_RightControl              : CGKeyCode = 0x3E
static let kVK_Function                  : CGKeyCode = 0x3F
static let kVK_F17                       : CGKeyCode = 0x40
static let kVK_VolumeUp                  : CGKeyCode = 0x48
static let kVK_VolumeDown                : CGKeyCode = 0x49
static let kVK_Mute                      : CGKeyCode = 0x4A
static let kVK_F18                       : CGKeyCode = 0x4F
static let kVK_F19                       : CGKeyCode = 0x50
static let kVK_F20                       : CGKeyCode = 0x5A
static let kVK_F5                        : CGKeyCode = 0x60
static let kVK_F6                        : CGKeyCode = 0x61
static let kVK_F7                        : CGKeyCode = 0x62
static let kVK_F3                        : CGKeyCode = 0x63
static let kVK_F8                        : CGKeyCode = 0x64
static let kVK_F9                        : CGKeyCode = 0x65
static let kVK_F11                       : CGKeyCode = 0x67
static let kVK_F13                       : CGKeyCode = 0x69
static let kVK_F16                       : CGKeyCode = 0x6A
static let kVK_F14                       : CGKeyCode = 0x6B
static let kVK_F10                       : CGKeyCode = 0x6D
static let kVK_F12                       : CGKeyCode = 0x6F
static let kVK_F15                       : CGKeyCode = 0x71
static let kVK_Help                      : CGKeyCode = 0x72
static let kVK_Home                      : CGKeyCode = 0x73
static let kVK_PageUp                    : CGKeyCode = 0x74
static let kVK_ForwardDelete             : CGKeyCode = 0x75
static let kVK_F4                        : CGKeyCode = 0x76
static let kVK_End                       : CGKeyCode = 0x77
static let kVK_F2                        : CGKeyCode = 0x78
static let kVK_PageDown                  : CGKeyCode = 0x79
static let kVK_F1                        : CGKeyCode = 0x7A
static let kVK_LeftArrow                 : CGKeyCode = 0x7B
static let kVK_RightArrow                : CGKeyCode = 0x7C
static let kVK_DownArrow                 : CGKeyCode = 0x7D
static let kVK_UpArrow                   : CGKeyCode = 0x7E
// ISO keyboards only
static let kVK_ISO_Section               : CGKeyCode = 0x0A
// JIS keyboards only
static let kVK_JIS_Yen                   : CGKeyCode = 0x5D
static let kVK_JIS_Underscore            : CGKeyCode = 0x5E
static let kVK_JIS_KeypadComma           : CGKeyCode = 0x5F
static let kVK_JIS_Eisu                  : CGKeyCode = 0x66
static let kVK_JIS_Kana                  : CGKeyCode = 0x68
var isModifier: Bool {
return (.kVK_RightCommand...(.kVK_Function)).contains(self)
}
var baseModifier: CGKeyCode?
{
if (.kVK_Command...(.kVK_Control)).contains(self)
|| self == .kVK_Function
{
return self
}
switch self
{
case .kVK_RightShift: return .kVK_Shift
case .kVK_RightCommand: return .kVK_Command
case .kVK_RightOption: return .kVK_Option
case .kVK_RightControl: return .kVK_Control
default: return nil
}
}

var isPressed: Bool {
CGEventSource.keyState(.combinedSessionState, key: self)
}
}

比我的另一个答案好得多

此解决方案的利润:

  • 它的工作速度比.keyboardShortcut()方式快
  • 它不需要创建额外的视图/按钮
  • 它适用于任何键盘布局(.keyboardShortcut()仅适用于一个键盘布局(

缺点:

  • 菜单中未显示热键
  • 需要为不同的视图状态创建自定义热键逻辑(在某些视图中需要使用一个热键,在某些子视图中使用另一个热键(

最佳使用方式:

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
}

最新更新