SwiftUIでbuttonStyleを適用したButtonに対してキーボードショートカットを有効にする
SwiftUIで凝ったUIを作ろうとすると節々詰まることがあります。今回はデザインをカスタマイズしたButtonに対してキーボードショートカットを割り当てる方法を紹介します。
環境情報
今回コードを書いた環境はこちらです。
- mac OSX 11.0
- XCode 12.0
やりたいこと
やりたいことを箇条書きにすると以下です。
- SwiftUIを使った
- macOSアプリで
- EnterキーでButtonをSubmitできるようにし
- Buttonは buttonStyle modifierでデザインをカスタマイズする
入力フォームでよく使われる便利系の機能ですね。
Enter キーでSubmitができないパターン
実は macOS 11.0から KeyboardShortcut が提供されています。
以下のコードサンプルをご覧ください。 .keyboardShortcut
modifier を使用し、任意のキーボード入力をショートカットとして設定できます。
指定されている .defaultAction
はデフォルト値で、Returnキーです。このコードはショートカットが問題なく動作します。
1Button(action: {
2 debugPrint("Sign In")
3}) {
4 Text("Sign In")
5}
6.keyboardShortcut(.defaultAction)
しかし、以下のコードはショートカットが機能しません。なお、 buttonStyle
に指定されている XXXXButtonStyle
は任意のButtonStyle Structとします。
1Button(action: {
2 debugPrint("Sign In")
3}) {
4 Text("Sign In")
5}
6.keyboardShortcut(.defaultAction)
7.buttonStyle(XXXXButtonStyle())
StackOverflow 上にも似た症状で困っている方がいるのを発見しました。
解決策:ZStackでキーボードショートカット専用ボタンを隠す
ワークアラウンドの結果、1つのButtonに対してkeyboardShortcutとbuttonStyleの併用すると、キーボードショートカットのみ効かなくなる ということが判明しました。 そのため、SwiftUIの挙動が改善されるまでは回避策を自作する必要があります。 一番手軽だった方法を紹介すると「キーボードショートカット専用のボタンを作成し、ZStackで隠す」です。
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は後に定義したViewが前面に表示されるため、.buttonStyle
を適用したButtonの裏側にショートカットキーをハンドリングする用のButtonを隠してしまいます。
これでショートカットが効きます。
ポイント1: ショートカット専用ボタンは限りなく小さく、かつ透過に
重要なポイントとしては、 .frame(width: 0, height: 0)
や .padding(0)
で可能な限りサイズを小さくした後、 .opacity(0)
で完全透過にすることです。
.opacity(0)
は前面のButtonに透過を付与した時に裏側のボタンが透けて見えてしまう問題を回避するのに役立ちます。
ポイント2: hidden() modifier は使えない
一見 hidden()
modifier を指定すれば裏側のボタンを綺麗に隠せると考える人もいるかもしれません。
しかし、 hidden()
を指定するとキーボードショートカットが機能しなくなってしまいます。
まとめ
SwiftUI で デザインをカスタムしたボタンに対してキーボードショートカットを割り当てる場合には
- ZStack で Button を2つ作成する
- 片方には
.buttonStyle
を適用し、もう一方には.keyboardShortcut
を指定する - Buttonのaction部には同じ処理を記述する
で実装できます。
ショートカットの箇所が増えるようならmodifierを自作するのもありです。
バッドノウハウ感全開ですが誰かのお役に立てば。