ショートカットキー&カスタムバインディング

カスタマイズ可能なショートカットキー

1. なぜ初期化(init)が二つあるのか?

理由:

  • 1つ目:コード内で普通に使うための初期化(例:KeyboardShortcut(key: "s", modifiers: .command)
  • 2つ目:JSONからデコードする時に自動的に呼ばれる初期化

Codableプロトコルに準拠すると、init(from:)JSONDecoderが自動的に呼び出します。EventModifiersはそのままではJSON化できないので、rawValue(Int値)に変換して保存・復元する必要があります。

2. loadShortcuts()の流れ

ステップバイステップ:

  1. UserDefaults.standard.data(forKey: "customShortcuts")
  2. ,(カンマ)で続けている
  3. JSONDecoder().decode([String: KeyboardShortcut].self, from: data)
  4. 両方成功すれば

保存時の流れ(参考):

  • encode()の時はencode(to encoder:)メソッドが自動的に呼ばれる
  • EventModifiers → rawValue(Int)に変換してJSON化

要するに、CodableプロトコルとJSONEncoder/Decoderが連携して、自動的にシリアライズ・デシリアライズしてくれる仕組みです!

NSViewRepresentableの初期化と各メソッドの役割

NSViewRepresentableのライフサイクル

この構造体の初期化:

  • SwiftUIから呼ばれる時、自動的に初期化される
  • 例:ShortcutRecorderNSView(isRecording: $isRecording) { key, flags in ... }

各メソッドが呼ばれるタイミングと効果

1. makeCoordinator() – 最初に1回だけ

効果:

  • SwiftUIとAppKit(NSView)の橋渡し役を作成
  • parentで親のプロパティ(@Bindingなど)にアクセスできる
  • 状態を保持し続ける(ビューが再描画されても同じインスタンスが使われる)

2. makeNSView(context:) – 最初に1回だけ

効果:

  • 実際のNSViewを作成してSwiftUIに渡す
  • startMonitoring()でキーイベントの監視を開始
  • このビューがSwiftUIの階層に追加される

3. updateNSView(_:context:) – 親の状態が変わる度に

効果:

  • 今回は空だが、通常は@Bindingvarが変わった時にNSViewを更新する
  • 例:isRecordingが変わった時に何かしたい場合はここに書く

4. dismantleNSView(_:coordinator:) – ビューが破棄される時に1回

なぜCoordinatorが必要?

理由:

  • NSEvent.addLocalMonitorForEventsのクロージャ内で@Bindingを更新したい
  • SwiftUIの構造体は値型なので、直接参照できない
  • Coordinatorがクラス(参照型)なので、状態を保持できる

これでself.parent.isRecordingのように親の状態を変更できます!

カスタムバインディング

通常のBindingとの違い

通常のBinding(直接プロパティをバインド)

カスタムBinding(今回のケース)

get と set の動作

get – 値を読み取る時に呼ばれる

いつ呼ばれる?

  • ShortcutRecorderViewshortcutの値を読む時
  • 画面に表示する時(例:shortcut.displayStringを表示)

何をしている?

  • shortcutManagerから指定したactionのショートカットを取得
  • 無ければデフォルト値(⌘A)を返す

set – 値を書き込む時に呼ばれる

いつ呼ばれる?

  • ShortcutRecorderView内でshortcutに新しい値を代入する時
  • 例:shortcut = KeyboardShortcut(key: "n", modifiers: .command)

何をしている?

  • $0は新しく設定された値(新しいKeyboardShortcut
  • それをshortcutManagerに保存

実際の動作フロー

なぜこの方法が必要だったのか?

問題:直接バインドできない

解決策:カスタムBindingで橋渡し

他の使用例

例1:計算プロパティのバインディング

例2:フォーマット変換

まとめ

  • get: 子ビューが値を読む時の処理
  • set: 子ビューが値を書く時の処理
  • 用途: 直接バインドできない値や、変換が必要な場合に使う

今回のケースでは、辞書から特定のキーの値を取り出して、それを@Bindingとして渡すために使っています!

コメント

タイトルとURLをコピーしました