Skip to content

Commit

Permalink
Rename .on() to .events() and add SwiftUI view modifier for liste…
Browse files Browse the repository at this point in the history
…ning to events (#79)

Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
EmilioPelaez and sindresorhus authored May 3, 2022
1 parent ee60fcf commit 8a2cc91
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 14 deletions.
19 changes: 6 additions & 13 deletions Example/KeyboardShortcutsExample/MainScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,17 @@ private struct DoubleShortcut: View {
.frame(maxWidth: 300)
.padding()
.padding()
.onKeyboardShortcut(.testShortcut1) {
isPressed1 = $0 == .keyDown
}
.onKeyboardShortcut(.testShortcut2, type: .keyDown) {
isPressed2 = true
}
.task {
KeyboardShortcuts.onKeyDown(for: .testShortcut1) {
isPressed1 = true
}

KeyboardShortcuts.onKeyDown(for: .testShortcut2) {
isPressed2 = true
}

KeyboardShortcuts.onKeyUp(for: .testShortcut2) {
isPressed2 = false
}
}
.task {
for await _ in KeyboardShortcuts.on(.keyUp, for: .testShortcut1) {
isPressed1 = false
}
}
}
}

Expand Down
65 changes: 64 additions & 1 deletion Sources/KeyboardShortcuts/KeyboardShortcuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ extension KeyboardShortcuts {
var body: some View {
Text(isUnicornMode ? "🦄" : "🐴")
.task {
for await _ in KeyboardShortcuts.on(.keyUp, for: .toggleUnicornMode) {
for await event in KeyboardShortcuts.events(for: .toggleUnicornMode) where event == .keyUp {
isUnicornMode.toggle()
}
}
Expand All @@ -407,6 +407,69 @@ extension KeyboardShortcuts {
- Note: This method is not affected by `.removeAllHandlers()`.
*/
@available(macOS 10.15, *)
public static func events(for name: Name) -> AsyncStream<KeyboardShortcuts.EventType> {
AsyncStream { continuation in
let id = UUID()

DispatchQueue.main.async {
streamKeyDownHandlers[name, default: [:]][id] = {
continuation.yield(.keyDown)
}

streamKeyUpHandlers[name, default: [:]][id] = {
continuation.yield(.keyUp)
}

registerShortcutIfNeeded(for: name)
}

continuation.onTermination = { _ in
DispatchQueue.main.async {
streamKeyDownHandlers[name]?[id] = nil
streamKeyUpHandlers[name]?[id] = nil

unregisterShortcutIfNeeded(for: name)
}
}
}
}

/**
Listen to keyboard shortcut events with the given name and type.

You can register multiple listeners.

You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.

Ending the async sequence will stop the listener. For example, in the below example, the listener will stop when the view disappears.

```swift
import SwiftUI
import KeyboardShortcuts

struct ContentView: View {
@State private var isUnicornMode = false

var body: some View {
Text(isUnicornMode ? "🦄" : "🐴")
.task {
for await event in KeyboardShortcuts.events(for: .toggleUnicornMode) where event == .keyUp {
isUnicornMode.toggle()
}
}
}
}
```

- Note: This method is not affected by `.removeAllHandlers()`.
*/
@available(macOS 10.15, *)
public static func events(_ type: EventType, for name: Name) -> AsyncFilterSequence<AsyncStream<EventType>> {
events(for: name).filter { $0 == type }
}

@available(macOS 10.15, *)
@available(*, deprecated, renamed: "events(_:for:)")
public static func on(_ type: EventType, for name: Name) -> AsyncStream<Void> {
AsyncStream { continuation in
let id = UUID()
Expand Down
38 changes: 38 additions & 0 deletions Sources/KeyboardShortcuts/ViewModifiers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import SwiftUI

@available(macOS 12, *)
extension View {
/**
Register a listener for keyboard shortcut events with the given name.

You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.

The listener will stop automatically when the view disappears.

- Note: This method is not affected by `.removeAllHandlers()`.
*/
public func onKeyboardShortcut(_ shortcut: KeyboardShortcuts.Name, perform: @escaping (KeyboardShortcuts.EventType) -> Void) -> some View {
task {
for await eventType in KeyboardShortcuts.events(for: shortcut) {
perform(eventType)
}
}
}

/**
Register a listener for keyboard shortcut events with the given name and type.

You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.

The listener will stop automatically when the view disappears.

- Note: This method is not affected by `.removeAllHandlers()`.
*/
public func onKeyboardShortcut(_ shortcut: KeyboardShortcuts.Name, type: KeyboardShortcuts.EventType, perform: @escaping () -> Void) -> some View {
task {
for await _ in KeyboardShortcuts.events(type, for: shortcut) {
perform()
}
}
}
}

0 comments on commit 8a2cc91

Please sign in to comment.