3.8K Views
September 11, 22
スライド概要
Mac Catalystは,iOS/iPad OSと共通のプロジェクト,コードでmacOS Nativeアプリケーションも提供できるフレームワークです.さらに,Mac Catalystには,iOS/iPad OSでしか扱えないHomeKit.frameworkなどのフレームワークを使ったアプリを開発できるなどの開発効率以上の価値をもたらす側面も持ちます.しかしながら,それを使った開発には,AppKitのAPIを利用できない,起動時に勝手にウィンドウが表示されてしまうなどの課題もあります.本セッションでは,Mac Catalystでアプリを開発する上で必須のテクを筆者が公開しているオープンソースのアプリをベースに紹介します.
To be written.
#iOSDC2022 Dive into Mac Catalyst Yuichi Yoshida Senior researcher, DENSO IT Laboratory, Inc. © 2022 Yuichi Yoshida. All rights reserved. Redistribution or public display not permitted without written permission from Yuichi Yoshida.
自己紹介 • sonson,こと吉田悠一 • 昔,2tchという2ちゃんビューアを作ってた. • iOSの本も書いてたけど,今は昔・・・・ • 本業は,画像認識とか,機械学習とか,そういうの
ことの発端 • なんでmacからHomeKitデバイスの操作がこんなにやりにくいのか iOSはまだマシ.2stepでオン・オフできる アプリをわざわざ起動して・・ウィンドウもでかい
こんな風にメニューバー常駐状態から操作したい
macOSは,HomeKit.frameworkをサポートしない Mac Catalystしかない
おさらい Mac Catalyst Mac Catalystを使って構築したMac向けAppでは、iPad向けのAppとコードを共有すること ができるほか、Mac専用の機能を追加することもできます。iPadOS 16のデスクトップクラ スの最新機能は、macOS 13でも同様に利用できます。Mac向けAppのツールバーは自動的 に最適化され、ドキュメントベースのAppには、主要な機能(メニューアイテム、ツール バー上で名前を変更する機能など)が追加されました。また、新しいMac Catalyst APIを使 用して、マルチウインドウの動作を強化したり、ツールバーにカスタムビューを追加した りできるようになりました。 https://developer.apple.com/jp/mac-catalyst/
おさらい ク ー ワ ム ー Mac Catalyst レ フ い な こ る え 使 も る あ とが え 使 しか で S O d a MacSCatalystを使って構築したMac向けAppでは、iPad向けのAppとコードを共有すること P i / O +i ができるほか、Mac専用の機能を追加することもできます。iPadOS 16のデスクトップクラ スの最新機能は、macOS 13でも同様に利用できます。Mac向けAppのツールバーは自動的 に最適化され、ドキュメントベースのAppには、主要な機能(メニューアイテム、ツール バー上で名前を変更する機能など)が追加されました。また、新しいMac Catalyst APIを使 用して、マルチウインドウの動作を強化したり、ツールバーにカスタムビューを追加した りできるようになりました。 https://developer.apple.com/jp/mac-catalyst/
Mac Catalystの課題 • System menuが使えない(AppKitが呼べない) • メニューバー常駐アプリは作れるのか? • 起動時にウィンドウが必ず表示されてしまう問題 • 署名にまつわる問題 Mac Catalystでこれらを解決していき 最終的にHomeKitを使うmacOSアプリを開発可能にする
課題1: Cocoa/AppKitを呼べない • macOSのAPIは,基本的にMac Catalyst環境では呼べない • 一部例外あり • 呼びたい・・・・ • NSStatusBar, NSWindow, NSViewController…
Workaround • • Dynamicを使う • Objective-CのPrivate APIにアクセスするSwiftライブラリ • 危険な香り・・・審査で難癖を付けられると詰む macOSバイナリをバンドルする • AppKit向けにビルドしたバイナリをPluginとしてバンドル • macOS環境下で起動したときのみLoadする • 大丈夫・・・?そう・・・?
誤解を恐れないイメージ 実行ファイル (target = Mac Catalyst) UIKit/Mac Catalyst/HomeKit.framework AppKit
誤解を恐れないイメージ UIKitとMac Catalystしか話せない 実行ファイル (target = Mac Catalyst) UIKit/Mac Catalyst/HomeKit.framework AppKit 裏側ではAppKitを叩いてる・・・?
誤解を恐れないイメージ 実行ファイル (target = Mac Catalyst) Bundle (target = macOS) UIKit/Mac Catalyst/HomeKit.framework AppKit
誤解を恐れないイメージ 実行時に動的に読み込み 実行ファイル (target = Mac Catalyst) Bundle (target = macOS) UIKit/Mac Catalyst/HomeKit.framework AppKit
誤解を恐れないイメージ やりとりはお互いの環境に共通する型 Obj-Cで使える型 Anyなどで通信 実行ファイル (target = Mac Catalyst) Bundle (target = macOS) UIKit/Mac Catalyst/HomeKit.framework AppKit macOS向けビルドなのでAppKitが呼べる
Bundleの動作イメージ UIKit HomeKit APIを叩くなど Mac Catalyst iOSController こいつは実行時にロード mac2ios Protocol 参照 Bundle MacOSController iOS2Mac Protocol NSMenuを作るなど AppKit
Bundleの動作イメージ UIKit これらのプロトコルに出てくる変数は, Mac CatalystとmacOSで使用可能であり, さらに,Objective-CのBundleで Mac Catalyst iOSController mac2ios Protocol Bundle MacOSController iOS2Mac Protocol ロードできる型でなければならない. HomeKitのクラスは使えない SwiftUIのクラスもBundleでは使えない AppKit
Bundleの動作イメージ UIKit AppKit側から呼び出したいHomeKitの関数 func readCharacteristic(of uniqueIdenti er: UUID) Mac Catalyst iOSController mac2ios Protocol Bundle MacOSController iOS2Mac Protocol アプリ本体から呼び出したいAppKitの関数 func reloadAllMenuItems() fi AppKit
Bundleの使い方 具体的な設定手順
iOSの新規プロジェクト
Mac Catalystをオン
macOS BundleをTargetに追加
適当に名前をつける
Principle classをセットする
macOS BundleをCatalystでインポートする
BundleとCatalystの境界 やりとりするクラスは NSObjectProtocoloに準拠しないとダメ BundleとCatalystの境界を結ぶプロトコルなので,元のCatalyst, Bundleの両方のターゲットに追加する. ここには,StringやUUIDなどのAppKitでも,UIKitでも共通して含まれる型しか宣言できない.
いよいよBundle本体を作る 名前は,MacOSControllerとかにした Obj-Cの仕組みを使うので,bridging headerを追加しとく
ここではCocoaが使える ターゲットは,Bundleだけに追加. ここでは,自由にAppKit, Cocoaが使える.Catalyst側から呼び出したい処理を関数で実装しておく.
Catalyst側からロードする Bundleの中に複数クラスがあったり,Principal Classが間違っていたりすると ここで失敗する.ちゃんと確認すること.
Catalyst側から呼び出せる
Bundleを介したやりとりに必要な工夫 • • • 型に起因する問題 • String, Int, Anyみたいなものしか渡せない • AnyにしてBundle側に持たせるとかはできる • HomeKitは,すべてにUUIDが振られていたので,割と楽. 例外など • 例外は飛ばせる • asyncのメソッドは,リンカがコケる.なぜ・・・・・? #if !os(macOS)で処理の切り分けは可能
BundleのLoadがうまくいかないとき • • コンパイル • プロトコルのUIKitとAppKitの変数がごっちゃになってる • 使えないクラス,型を使っている 実行時 • Bundleからクラスをロードするときに型を間違ってる • デバッガでちゃんと型通りのものがロードされているか確認
課題2: メニューバー常駐アプリにできる? • Dockに表示されないアプリにするには? • Mac Catalystでビルドしても,実はInfo.plistは機能する • UIElementをオンにすると常駐アプリになる • ノーヒントなので,探すの大変
課題3: ウィンドウ制御の問題 • 2番目に苦労した • 普通にやると・・・・ • • Mac Catalystを全面にすると新規ウィンドウが開く • 起動時に新規ウィンドウが開く • UIScene ≒ ウィンドウという感じ メニュー常駐型アプリには邪魔でしかない
コード func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let activity = connectionOptions.userActivities. rst switch (activity?.activityType, activity?.title) { case ....: default: #if targetEnvironment(macCatalyst) // This is a workaround to hide the view controller which is forced to appear when launching. windowScene.sizeRestrictions?.minimumSize = CGSize(width: 1, height: 1) windowScene.sizeRestrictions?.maximumSize = CGSize(width: 1, height: 1) #endif } fi }
対策 • 事実 • ウィンドウの最大サイズを(1,1)にすると実はウィンドウがでない • • • インスタンスも生成されない模様・・・なぜ? activityTypeでシーンは識別できる もっと自由にしたい場合は・・・・ • bundleの仕組みを使って,NSWindowでウィンドウを作るとよい
課題4: 署名に関する話題〜HomeKitを使った場合
課題4: 署名に関する話題〜HomeKitを使った場合 署名方法 ビルド Developer ID provisioning pro leがHomeKitに対 応していないのでビルドできない 署名なし 実行 署名がないのでHomeKitを実行 できない Mac App Store fi ※この件はAppleにBug Reportで改善を要求済み
余談:Mac Catalystのアイコンがヘンになる デフォルトのアイコンアセット macOSターゲットのアイコンセット
こうやってできたのが,HomeConMenu Mac App Storeで公開中 ソースコードも公開中 https://github.com/sonsongithub/HomeConMenu アイコンは @1024jp さんのデザイン.かっこいい.
参考文献 • • Bundleのロード方法 • https://betterprogramming.pub/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5 • https://medium.com/geekculture/use-of-macos-specific-api-in-maccatalyst-apps-and-vice-versa-e7082a007b7c Bundleのサンプルコード • • • https://github.com/sonsongithub/MacCatalystWithAppKit HomeConMenu,私のアプリとそのソースコード • App Store https://apps.apple.com/us/app/homeconmenu/id1615397537 • github https://github.com/sonsongithub/HomeConMenu その他のより複雑なHomeKitやSceneKitを使ったMac Catalystソースコードの例 • https://github.com/nehayward/Scenecuts