1

I have an application that needs to respond to key down/up and modifier changed events. This is the code for the view that receives the keyboard events:

struct KeyAwareView: NSViewRepresentable {
    let onEvent: (NSEvent) -> Void

    func makeNSView(context: Context) -> NSView {
        let view = KeyView()
        view.onEvent = onEvent
        DispatchQueue.main.async {
            view.window?.makeFirstResponder(view)
        }
        return view
    }

    func updateNSView(_ nsView: NSView, context: Context) {}
}

private class KeyView: NSView {
    var onEvent: (NSEvent) -> Void = { _ in }
    
    override var acceptsFirstResponder: Bool { true }
    override func keyDown(with event: NSEvent) {
        onEvent(event)
    }
    override func keyUp(with event: NSEvent) {
        onEvent(event)
    }
    override func flagsChanged(with event: NSEvent) {
        onEvent(event)
    }
}

I then have this view layered in a ZStack:

KeyAwareView(onEvent: { event in
    switch event.type {
    case .keyDown:
        KeyboardEventResponder.shared.handleKeyDown(keyCode: Int(event.keyCode))
        
    case .keyUp:
        KeyboardEventResponder.shared.handleKeyUp(keyCode: Int(event.keyCode))
        
    case .flagsChanged:
        KeyboardEventResponder.shared.updateModifiers(newModifiers: event.modifierFlags)
        
    default:
        break
    }
})

The main window is a master-detail view which looks like this (some detail removed):

ZStack() {
    NavigationView {
        LayoutList(layoutsList: $document.layouts, selectedLayout: $selectedLayout)
        
        if selectedLayout != nil {
            let index = document.layouts.firstIndex(of: selectedLayout!)!
            if let layout = document.loadLayout(atIndex: index) {
                LayoutEditor(layoutData: $document.layouts[index])
                    .environmentObject(keyboardStatus)
            }
        }
    }
    KeyAwareView { event in
        switch event.type {
        case .keyDown:
            KeyboardEventResponder.shared.handleKeyDown(keyCode: Int(event.keyCode))
            
        case .keyUp:
            KeyboardEventResponder.shared.handleKeyUp(keyCode: Int(event.keyCode))
            
        case .flagsChanged:
            KeyboardEventResponder.shared.updateModifiers(newModifiers: event.modifierFlags)
            
        default:
            break
        }
    }
}

This all works properly when the first layout is selected. But then when you click on another layout, the KeyAwareView doesn't receive any events until I click somewhere in the window apart from selecting another layout in the list.

What can I do to make the view receive events? I tried adding

DispatchQueue.main.async {
    nsView.window?.makeFirstResponder(nsView)
}

to the updateNSView method, but that made no difference.

Mussau
  • 33
  • 6
  • Does this answer your question? [How to detect keyboard events in SwiftUI on macOS?](https://stackoverflow.com/questions/61153562/how-to-detect-keyboard-events-in-swiftui-on-macos) – vadian Oct 20 '21 at 17:02
  • Or this one https://stackoverflow.com/a/61833055/12299030? – Asperi Oct 20 '21 at 17:04
  • @vadian, that doesn't work for my use case. I need to have the key code and modifiers changed, but using ```commands``` and its equivalent gets things after interpretation through the keyboard layout to the actual characters, so that you can't know the key code. Plus modifiers on their own don't trigger these. – Mussau Oct 21 '21 at 05:53
  • @Asperi, that's getting closer to what I want, so I'll do some experimenting today. I had been thinking that I would get an NSView to put as the content view for the document window, and then having the actual SwiftUI content inside the NSView, hence both ```NSViewRepresentable``` and ```NSHostingView```. But if it can be done using ```EnvironmentObject```, that would be simpler. – Mussau Oct 21 '21 at 05:55
  • OK, I played around with various options, and what I had originally seems to be the simplest and works. Instead of putting it in a ```.background```, I layer it in a ```ZStack```, and it works. The problem is still that it doesn't keep working properly. The main view is master-detail, and when I choose another item in the master pane, the key capture doesn't happen until I click in the new detail pane. Is there something that can be done to fix that? – Mussau Oct 21 '21 at 08:59

0 Answers0