macOS Venturaからの新しい“Settings”表記と、旧“Preferences”表記からの移行
概要
macOS Ventura 13ではSystem Preferences(システム環境設定)のデザインが刷新され、ビジュアルから名称までiOSやiPadOSと同様の “System Settings”(システム設定)に変更されました。これに合わせて、各アプリケーションのメニューバーに収まっている “Preferences…”(環境設定…)も “Settings…”(設定…)に変更されました。[1]
今回はこのあたりの情報を少し調査したので、資料にまとめておきます。
開発対応方針
基本的には次のとおりです:
- macOS Ventura 13以降のOSでは、
Preference(s)
の文言はSetting(s)
に書き換える - アプリケーションメニューの
Preferences…
はmacOS SDK 13.0以降でビルドすると自動でSettings…
に書き換えられる - アプリケーションメニュー以外の箇所の表記は手動で対応
- ローカライズは手動で対応
アプリケーションメニューの “Preferences…” の書き換え挙動
Xcode 14.1 (macOS SDK 13.0)の環境でmacOS appをビルドすると、アプリケーションメニューの “Preferences…” タイトルが自動的に “Settings…” に書き換えられます。アプリケーションのランタイム内で動的に書き換え処理が走ります。Xcode 14.0.1以前でビルドしたアプリケーションではこの処理は実行されません。
今回テストプロジェクトを作成して、あたりをつけた箇所にブレークポイントを仕掛けてこれを検証してみました。
NSMenuItemのサブクラスからスタックトレースを探る 1
NSMenuItemのサブクラスを作り、title
プロパティのdidSetにprint()
ログとブレークポイントを仕掛けてみました。するとアプリケーション起動直後にtitleの文言が“Settings…”に書き換えられていることを発見しました。アプリケーション自身が起動時に設定メニューの名前をダイナミックに書き換えているということです。
oldValueが“Preferences…”で、newValueが“Settings…”だとわかります。
これでなんとなく仕組みに察しがついたので、次は「どういうルールで名前の書き換えが起こるのか」を探ってみました。
MainMenuの並びを変えたり、同じアイテムを複製してみる
今回のテストプロジェクトはStoryboardベースのUI設計にしていたので、Interface Builder上のMainMenuをいじり、“Preferences…”メニューアイテムを複数並べてビルドしてみました。すると次のような結果になりました。
これで大体察しがつきました。メニュー内に “Preferences…” が複数あっても、最初に見つかった1つ目のみがタイトルの書き換え対象になります。 スクリーンショットは用意しませんでしたが、例えばFileメニューに1つ目の “Preferences…” を置いても書き換え処理が走ります。
メニューアイテムにチェックマークがついていようが、Disableステートになっていようが、書き換え対象かどうかはタイトルのみで判断しているようです。
NSMenuItemのサブクラスからスタックトレースを探る 2
さっきブレークポイントを仕掛けたところから遡ってみて、どのようなメソッドが呼ばれているのかを眺めてみました。
// ブレークポイントを張ったところ
0 Menultem.title.didset
// NSMenuItem.titleのセッターが呼ばれている
2 @objc Menultem.title. setter
// NSApplication内部で何やらMainMenuの書き換え処理が行われているっぽい箇所
3 - [NSApplication(NSMenuUpdating) _updateSettingsMenultemIfNeeded]
4 - [NSApplication(NSMenuUpdating)_customizeMainMenu]
// アプリケーションの起動が完了
5 - INSApplication finish Launching]
// アプリケーションのメインループ開始
7 NSApplicationMain
途中省きましたが、アプリケーション起動直後にNSApplication内で_customizeMainMenu
なるメソッドが呼ばれ、次に_updateSettingsMenultemIfNeeded
なるメソッドも呼ばれています。その直後にブレークポイントを張ったNSMenuItem(のサブクラス)のtitle
プロパティにメッセージが到達しています。名前も明らかにそれっぽい _updateSettingsMenultemIfNeeded
が、設定メニュー名のタイトルの書き換えを行なっている正体だと思われます。
NSMenuShouldUpdateSettingsTitle でタイトルの自動書き換え処理を止める
続けて、右側のアセンブリコードの中身を少し眺めてみます。
再掲
何やらよくわからなコードだらけなのですが、一部読みやすい部分があります。一番気になるのが @"NSMenuShouldUpdateSettingsTitle"
と書かれた部分です。@""
はObjective-C文字列なので内部でもこの文字列を使って何かの評価が行われているっぽいです。そして名前も直球に「NSMenuがSettingsタイトルを更新するか」みたいな意味が取れるので、きっとこいつが何かのフラグになっているのだと思います。
これを見たとき、なんとなく「NSMenuShouldUpdateSettingsTitleはdefaultsコマンドでBoolean値を指定できそうな雰囲気があるな」と思ったのですが、まさにこの見解が正解でした。ググったらちょうど次のWeb資料を見つけたのですが、ここにはdefaultsコマンドが実行できたと書かれています。
defaults write -g NSMenuShouldUpdateSettingsTitle -bool NO
-g
はGlobal Domainに対する指定なので、ここをBundle Identifierとかにすると特定アプリケーションのみで書き換え防止を起動変数に設定できそうです。[2]
defaults コマンドが実行できるということは、XcodeのSchemeからLaunch Argumentsを指定することでもきっと同様のことが行えるだろうと思い試したところ、これもうまくいきました。
“Arguments Passed On Launch” のところにフィールドを追加して、中身を-NSMenuShouldUpdateSettingsTitle NO
とします。これでアプリケーションを実行するとXcode 14.1以降のコンパイルでも設定メニュー名の書き換え処理を止めることができます。
だから何って感じではあるのですが、ドキュメントに載っていないOSの裏側の仕組みが明らかになったので興味深い気持ちは抱けたかなあと思います。実用性はあまりないでしょうが、デバッグのための知識として持っておいても損はないかと。
ローカライズの方針
ローカライズされた文言は自動で書き換えられるわけではないため、これまで通りの方法で対応します。
もしMainMenuのタイトルをキーに取る形でNSLocalizedString()を実行している場合、Localizable.strings
に "Preferences…" = "環境設定…";
的な記述があるならそれはそのまま残しつつ、新たに "Settings…" = "設定…";
を加えるだけで済むと思います。Main.storyboardを直接.stringsでローカライズしている場合は少し工夫が必要かもしれません。
注意したいのは、アプリケーションがmacOS Monterey 12.x以前とmacOS Ventura 13.0以降どちらのOSにも対応するケースです。この場合、ランタイムの環境が12.xの時は “Preferences…”(環境設定…)のまま、13.0以降の時は “Settings…”(設定…)に書き換えることをしなければなりません。「"Preferences…" = "環境設定…";
を残して"Settings…" = "設定…";
を追加する」と言ったのはこれが理由です。
デバッグの際はOSごとの表記の違いを意識し、どちらにも対応するようにしましょう。
まとめ
- macOS Monterey以前までのデザインは、“Preferences…”(環境設定…)
- macOS Ventura以降のデザインは、“Settings…”(設定…)
- Ventura対応としては、Xcode 14.1 (macOS SDK 13.0)以降でビルドすること
- アプリケーション起動時にMainMenu内の対象アイテムのタイトルが自動で“Settings…”に書き換えられる
- NSMenuShouldUpdateSettingsTitle = NOで書き換え処理を無効化可能
- ローカライズやその他の箇所は手動で対応
-
WWDC22で言及があります:10074 - What's new in AppKit ↩︎
-
そんなことをしても実用性がありませんが。 ↩︎
Discussion