Enabling keyboard shortcuts for buttons with buttonStyle applied in SwiftUI

Enabling keyboard shortcuts for buttons with buttonStyle applied in SwiftUI
Page content

When you try to create a cool UI with SwiftUI, you will often face a problem. In this article, I'll show you how to use keyboardShortcut to assign keyboard shortcuts to buttons that have been customized with buttonStyle.

Environment

  • mac OSX 11.0
  • XCode 12.0

Goal in this article

  • Using SwiftUI
  • In a macOS app
  • You can use the Enter key to submit a Button.
  • Button design can be customized with buttonStyle modifier.

This is a useful feature that is often used in forms.

A pattern of keyboard shortcuts being disabled

As of macOS 11.0, KeyboardShortcut is available. See the code sample below. You can use the .keyboardShortcut modifier to set any keyboard input as a shortcut. The .defaultAction specified is the default value, the Return key. This code works fine for shortcuts.

1Button(action: {
2  debugPrint("Sign In")
3}) {
4  Text("Sign In")
5}
6.keyboardShortcut(.defaultAction)

However, the following code does not work for shortcuts. Note that XXXXButtonStyle specified in .buttonStyle is assumed to be an arbitrary ButtonStyle Struct.

1Button(action: {
2  debugPrint("Sign In")
3}) {
4  Text("Sign In")
5}
6.keyboardShortcut(.defaultAction)
7.buttonStyle(XXXXButtonStyle())

There was also someone on StackOverflow asking for help.

Solution: Hide the keyboard shortcut button with ZStack

As a result of my workaround, it was found that only keyboard shortcuts will not work if keyboardShortcut and buttonStyle are used together for a single Button. Therefore, until the behavior of SwiftUI is improved, need to create your own workaround. The easiest way I found to do this was to use ZStack to hide the button for handling keyboard shortcut behind the button it was customized with buttonStyle.

 1ZStack {
 2  // Button for handling keyboard shortcut
 3  Button(action: {
 4    debugPrint("Sign In")
 5  }) {}
 6    .padding(0)
 7    .opacity(0)
 8    .frame(width: 0, height: 0)
 9    .keyboardShortcut(.defaultAction)
10  // Button for handling mouse event
11  Button(action: {
12    debugPrint("Sign In")
13  }) {
14    Text("Sign In")
15  }
16  .buttonStyle(XXXXButtonStyle())
17}

ZStack will display the lower defined View in the front. Use this behavior to hide the Button for handling shortcut keys behind the Button with .buttonStyle applied. This way, keyboard shortcuts will be enabled.

Point 1: Make shortcut-only buttons as small as possible and make them transparent.

The important point is to make the size as small as possible with .frame(width: 0, height: 0) and .padding(0), and then make it fully transparent with .opacity(0). The setting .opacity(0) is useful to avoid the problem of the keybord shortcut button showing through when the front button is transparent.

Point 2: Can't use hidden() modifier.

Some people may think that specifying a hidden() modifier will hide the back side of the button nicely. However, if you specify hidden(), the keyboard shortcuts will not work.

Conclusion

To assign a keyboard shortcut to a custom-designed button in SwiftUI, use the

  • Create two buttons, apply .buttonStyle to one, and .keyboardShortcut to the other
  • Describe the same process in the action part of Button
  • Use ZStack to hide one button on the back of the other.

If you need to implement more shortcuts, you may want to create a modifier yourself.

This is bad know-how, but I hope it helps someone.

References