Monitor for Keypress in Menu/Picker in SwiftUI on MacOS

213 Views Asked by At

I'm learning SwiftUI programming by trying to duplicate basic features in the MacOS Finder.

The groups button in the Finder window (screenshot below) has me stumped. Clicking the menu shows the group options, while option-clicking shows the sort options. I can't figure out how that's done.

enter image description here

My basic code is as follows:

Menu {
  if NSEvent.modifierFlags.contains(.option) {
    Picker(selection: viewSorts, label: EmptyView()) {
      ForEach(viewSorts) { sort in
        Text(sort.name).tag(sort)
      }
    }
    .labelsHidden()
    .pickerStyle(InlinePickerStyle())
  } else {
    Picker(selection: viewGroups, label: EmptyView()) {
      ForEach(viewGroups) { group in
        Text(group.name).tag(group)
    }
    .labelsHidden()
    .pickerStyle(InlinePickerStyle())
  }
} Label: {
  Image(systemName: "square.grid.3x1.below.line.grid.1x2")
}

It works, however NSEvent.modifierFlags.contains(.option) never evaluates to true.

This post has two examples that I used to try to fix the problem:

Using onTapGesture with EventModifiers:

@State private var showSort = false

Menu {
  if showSort {
//    ... show sort picker ...
  } else {
//    ... show group picker ...
  }
} Label: {
  Image(systemName: "square.grid.3x1.below.line.grid.1x2")
}
  .gesture(TapGesture().modifiers(.option).onEnded {
    showSort = true
  })
  .onTapGesture {
    showSort = false
  }

And another using a CGKeyCode extension:

import CoreGraphics

extension CGKeyCode
{
    static let kVK_Option     : CGKeyCode = 0x3A
    static let kVK_RightOption: CGKeyCode = 0x3D
    
    var isPressed: Bool {
        CGEventSource.keyState(.combinedSessionState, key: self)
    }
    
    static var optionKeyPressed: Bool {
        return Self.kVK_Option.isPressed || Self.kVK_RightOption.isPressed
    }
}

Menu {
  if CGKeyCode.optionIsPressed {
//    ... show sort picker ...
  } else {
//    ... show group picker ...
  }
} Label: {
  Image(systemName: "square.grid.3x1.below.line.grid.1x2")
}

And from these two posts (1, 2), addLocalMonitorForEvents:

@State private var showSort = false

Menu {
  if showSort {
//    ... show sort picker ...
  } else {
//    ... show group picker ...
  }
} Label: {
  Image(systemName: "square.grid.3x1.below.line.grid.1x2")
}
.onAppear() {
  NSEvent.addLocalMonitorForEvents(matching: .keyDown) { (keyEvent) -> NSEvent? in
    if keyEvent.modifierFlags == .option {
        showSort = true
    } else {
        showSort = false
    }
    return keyEvent
  }
}

The answer is probably staring at me in the face, but I just can't see it! Thank you for any help!

UPDATE: onContinuousHover does work, but it only updates when the mouse is moving over the menu.

  .onContinuousHover { _ in
    showSort = NSEvent.modifierFlags.contains(.option) ? true : false
  }

But onTapGesture doesn't work

  .onTapGesture {
    showSort = NSEvent.modifierFlags.contains(.option) ? true : false
  }
0

There are 0 best solutions below