Skip to content

Commit 8a2cc91

Browse files
Rename .on() to .events() and add SwiftUI view modifier for listening to events (#79)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent ee60fcf commit 8a2cc91

File tree

3 files changed

+108
-14
lines changed

3 files changed

+108
-14
lines changed

Example/KeyboardShortcutsExample/MainScreen.swift

+6-13
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,17 @@ private struct DoubleShortcut: View {
100100
.frame(maxWidth: 300)
101101
.padding()
102102
.padding()
103+
.onKeyboardShortcut(.testShortcut1) {
104+
isPressed1 = $0 == .keyDown
105+
}
106+
.onKeyboardShortcut(.testShortcut2, type: .keyDown) {
107+
isPressed2 = true
108+
}
103109
.task {
104-
KeyboardShortcuts.onKeyDown(for: .testShortcut1) {
105-
isPressed1 = true
106-
}
107-
108-
KeyboardShortcuts.onKeyDown(for: .testShortcut2) {
109-
isPressed2 = true
110-
}
111-
112110
KeyboardShortcuts.onKeyUp(for: .testShortcut2) {
113111
isPressed2 = false
114112
}
115113
}
116-
.task {
117-
for await _ in KeyboardShortcuts.on(.keyUp, for: .testShortcut1) {
118-
isPressed1 = false
119-
}
120-
}
121114
}
122115
}
123116

Sources/KeyboardShortcuts/KeyboardShortcuts.swift

+64-1
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ extension KeyboardShortcuts {
396396
var body: some View {
397397
Text(isUnicornMode ? "🦄" : "🐴")
398398
.task {
399-
for await _ in KeyboardShortcuts.on(.keyUp, for: .toggleUnicornMode) {
399+
for await event in KeyboardShortcuts.events(for: .toggleUnicornMode) where event == .keyUp {
400400
isUnicornMode.toggle()
401401
}
402402
}
@@ -407,6 +407,69 @@ extension KeyboardShortcuts {
407407
- Note: This method is not affected by `.removeAllHandlers()`.
408408
*/
409409
@available(macOS 10.15, *)
410+
public static func events(for name: Name) -> AsyncStream<KeyboardShortcuts.EventType> {
411+
AsyncStream { continuation in
412+
let id = UUID()
413+
414+
DispatchQueue.main.async {
415+
streamKeyDownHandlers[name, default: [:]][id] = {
416+
continuation.yield(.keyDown)
417+
}
418+
419+
streamKeyUpHandlers[name, default: [:]][id] = {
420+
continuation.yield(.keyUp)
421+
}
422+
423+
registerShortcutIfNeeded(for: name)
424+
}
425+
426+
continuation.onTermination = { _ in
427+
DispatchQueue.main.async {
428+
streamKeyDownHandlers[name]?[id] = nil
429+
streamKeyUpHandlers[name]?[id] = nil
430+
431+
unregisterShortcutIfNeeded(for: name)
432+
}
433+
}
434+
}
435+
}
436+
437+
/**
438+
Listen to keyboard shortcut events with the given name and type.
439+
440+
You can register multiple listeners.
441+
442+
You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.
443+
444+
Ending the async sequence will stop the listener. For example, in the below example, the listener will stop when the view disappears.
445+
446+
```swift
447+
import SwiftUI
448+
import KeyboardShortcuts
449+
450+
struct ContentView: View {
451+
@State private var isUnicornMode = false
452+
453+
var body: some View {
454+
Text(isUnicornMode ? "🦄" : "🐴")
455+
.task {
456+
for await event in KeyboardShortcuts.events(for: .toggleUnicornMode) where event == .keyUp {
457+
isUnicornMode.toggle()
458+
}
459+
}
460+
}
461+
}
462+
```
463+
464+
- Note: This method is not affected by `.removeAllHandlers()`.
465+
*/
466+
@available(macOS 10.15, *)
467+
public static func events(_ type: EventType, for name: Name) -> AsyncFilterSequence<AsyncStream<EventType>> {
468+
events(for: name).filter { $0 == type }
469+
}
470+
471+
@available(macOS 10.15, *)
472+
@available(*, deprecated, renamed: "events(_:for:)")
410473
public static func on(_ type: EventType, for name: Name) -> AsyncStream<Void> {
411474
AsyncStream { continuation in
412475
let id = UUID()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import SwiftUI
2+
3+
@available(macOS 12, *)
4+
extension View {
5+
/**
6+
Register a listener for keyboard shortcut events with the given name.
7+
8+
You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.
9+
10+
The listener will stop automatically when the view disappears.
11+
12+
- Note: This method is not affected by `.removeAllHandlers()`.
13+
*/
14+
public func onKeyboardShortcut(_ shortcut: KeyboardShortcuts.Name, perform: @escaping (KeyboardShortcuts.EventType) -> Void) -> some View {
15+
task {
16+
for await eventType in KeyboardShortcuts.events(for: shortcut) {
17+
perform(eventType)
18+
}
19+
}
20+
}
21+
22+
/**
23+
Register a listener for keyboard shortcut events with the given name and type.
24+
25+
You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.
26+
27+
The listener will stop automatically when the view disappears.
28+
29+
- Note: This method is not affected by `.removeAllHandlers()`.
30+
*/
31+
public func onKeyboardShortcut(_ shortcut: KeyboardShortcuts.Name, type: KeyboardShortcuts.EventType, perform: @escaping () -> Void) -> some View {
32+
task {
33+
for await _ in KeyboardShortcuts.events(type, for: shortcut) {
34+
perform()
35+
}
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)