はじめに
iOSアプリがmacOSで動くようになる *1
今年、Appleから発表されたMac Catalyst *2。Mac Catalystとは、iOSアプリのソースコードを流用してMacアプリを開発できるというツールセットです。TwitterがMac Catalystを使ってMac版デスクトップアプリを復活させる*3 など、既に商用アプリでも使われはじめています。今回はこのMac Catalystを使ってiOSアプリとMacアプリを開発し、その使い方を確認しました。
アプリ概要
今回作ったものは、とても簡単な手書きノートアプリです。App Storeにリリースしているので、以下リンクからインストールできます。なお、Mac Catalyst対応アプリはmacOS 10.15以上のPCでインストールできます。
Blue Sketch - iOSアプリ
Blue Sketch - Macアプリ
1stバージョンをリリースするまでの開発期間は3日+App Store申請に1日です。
iPad版とMac版のスクリーンショットを以下に載せます。
キャンバス画面: この青いキャンバス上に白線を書くことができます。
UIAlertController: Mac版は上からニョキッと出てくる感じです。
UIActivityViewController: 画像をエクスポートするために使っていますが、Mac版でも普通に動きます。
ご覧の通り、UIKitがそのままmacOSで使える感じです。
使用ライブラリ
Name | Version | 内容 |
---|---|---|
Default | 3.0.0 | UserDefaultsのラッパー |
MKAToggleButton | 1.2.3 | 自作のマルチステートButtonライブラリ |
R.swift | 5.1.0 | リソースファイルをいい感じに扱うライブラリ |
XCGLogger | 7.0.0 | ロガー |
すべてCocoaPodsでインストールしています。
最初はモバイルDBにRealmを使おうかと思いましたが、Mac Catalystでうまくビルドができずやめました。今回は、UserDefaultsとファイル保存でユーザデータを管理しています。
その他、Firebaseや広告SDKも試してみたいですが、今回は未検証です。
開発
ここからはアプリ開発において得た知見を書いていきます。
開発環境
Version | |
---|---|
macOS | 10.15.1 |
Xcode | 11.3 |
Swift | 5 |
Mac Catalyst対応
iOSアプリをMac Catalystに対応する手順は以下になります。
-
Mac Catalyst対応するTarget > General > Deployment Info > Macにチェック
-
My Macを選択して▶Runをクリック
ビルドを開始します。無事ビルドが通れば成功です。
Macアプリ向けにカスタマイズ
ビルドが通ればMacアプリとして使うことはできますが、このままではデスクトップアプリケーションらしさが足りないため、いくつかのカスタマイズをします。
AppIcon
Macアプリ用にアイコンを指定しなければ、iOSアプリのアイコンが使われます。しかし、Macアプリのアイコンは、iOSアプリのように角丸正方形である必要はありません。折角なので、Macアプリ用にデザインを少し変えてみたいと思います。
手順
- Assets.xcassets > AppIconを開く
- 右パネルのMac Allにチェック
- Macアプリ用のアイコンホルダーができるので各画像ファイルをDrag & Drop
LaunchScreen
Mac CatalystはLaunchScreenに対応していないようです。何か設定方法があるのかもしれませんが、今回はiOSアプリのみ対応しています。
ダークモード対応
特に手を加えることはなかったです。UIKitのダークモード対応がそのまま使えました。
Mac Catalystかどうかの判定
Mac Catalystの時だけ処理したい場合は以下のように書けます。
#if targetEnvironment(macCatalyst)
print("Mac Catalystだよ")
#else
print("Mac Catalystじゃないよ")
#endif
ちなみに、#available(iOS 13.0, *)
を使った判定はMac Catalystでも通ります。APIとしてはiOSだからということでしょうか。
if #available(iOS 13.0, *) {
print("Mac Catalystも通るよ")
}
タイトルバーとステータスバー
override func viewDidLoad() {
super.viewDidLoad()
#if targetEnvironment(macCatalyst)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.window?.windowScene?.titlebar?.titleVisibility = .hidden
}
#endif
}
titlebar
はMac CatalystにしかないAPIのため#if targetEnvironment(macCatalyst) ~ #endif
で囲みます。
なお、Macアプリにはステータスバーが無いため、statusBarFrame
は(0, 0, 0, 0)を返します。
var statusBarFrame: CGRect {
if #available(iOS 13.0, *) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let statusBarManager = appDelegate.window?.windowScene?.statusBarManager else {
return .zero
}
return statusBarManager.statusBarFrame // => (0, 0, 0, 0) on Mac Catalyst
}
else {
return UIApplication.shared.statusBarFrame
}
}
メニューバー
Macアプリにはメニューバーがあります。🍎マークが並んでいるバーですね。デフォルトでは以下のようなメニューが並んでいます。
アプリで使わないメニューがあるので以下のようにカスタマイズします。
UIResponderクラスにメニューを構築するbuildMenu(with:)
メソッドがあるので、これをオーバーライドします。
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
#if targetEnvironment(macCatalyst)
override func buildMenu(with builder: UIMenuBuilder) {
guard builder.system == UIMenuSystem.main else { return }
// 初期メニューで不要なものを削除
builder.remove(menu: .file)
builder.remove(menu: .format)
builder.remove(menu: .help)
builder.remove(menu: .services)
}
#endif
}
また、ショートカットキーをメニューに割り当てることもできます。今回作ったアプリもUndo/RedoをそれぞれCommand+z/Command+Shift+zに割り当てています。
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
#if targetEnvironment(macCatalyst)
override func buildMenu(with builder: UIMenuBuilder) {
...
// Undo/Redoメニューの追加
let undoAction = UIKeyCommand(title: "Undo",
action: #selector(handleUndoMenu(_:)),
input: "z",
modifierFlags: [.command])
let redoAction = UIKeyCommand(title: "Redo",
action: #selector(handleRedoMenu(_:)),
input: "z",
modifierFlags: [.command, .shift])
let editMenu = UIMenu(title: "Edit",
image: nil,
identifier: "com.example.MyApp.editMenu",
options: .destructive,
children: [undoAction, redoAction])
builder.insertSibling(editMenu, afterMenu: .application)
}
@objc func handleUndoMenu(_ command: UIKeyCommand) {
// TODO: Undo処理
}
@objc func handleRedoMenu(_ command: UIKeyCommand) {
// TODO: Redo処理
}
#endif
}
詳細は長くなるため、また別の機会に書きたいと思います。
ボタンのマウスオーバー(Hover)対応
Macアプリでは、マウスカーソルをボタンに重ねた時のスタイルを設定できます。
下記のコードは、マウスオーバー時にボタンのタイトルと枠線をハイライトする例です。
private let normalColor = UIColor.blue
private let hoverColor = UIColor.cyan
private lazy var closeButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 160.0, height: 44.0))
button.setTitle("Close", for: .normal)
button.setTitleColor(normalColor, for: .normal)
button.layer.cornerRadius = 5.0
button.layer.borderWidth = 1.0
button.layer.borderColor = normalColor.cgColor
button.addTarget(self, action: #selector(closeButtonClicked(_:)), for: .touchUpInside)
if #available(iOS 13.0, *) {
let hover = UIHoverGestureRecognizer(target: self, action: #selector(hovering(_:)))
button.addGestureRecognizer(hover)
}
return button
}()
@available(iOS 13.0, *)
@objc func hovering(_ recognizer: UIHoverGestureRecognizer) {
guard let button = recognizer.view as? UIButton else { return }
switch recognizer.state {
case .began, .changed:
button.setTitleColor(hoverColor, for: .normal)
button.layer.borderColor = hoverColor.cgColor
case .ended:
button.setTitleColor(normalColor, for: .normal)
button.layer.borderColor = normalColor.cgColor
default:
break
}
}
@objc func closeButtonClicked(_ sender: UIButton) {
dismiss(animated: true)
}
なお、UIHoverGestureRecognizerは今のところiOSアプリには対応していないようです(指を重ねても何も起きません)。actionに指定したhovering(_:)
メソッドが呼ばれていませんでした。
ウィンドウサイズの変更に追従する
Macアプリは、ウィンドウ枠をマウスドラッグしてウィンドウサイズが可変なため、ドラッグ中のレンダリング処理を見直す必要がありました。ドラッグ中はUIViewControllerのviewWillLayoutSubviews()
およびviewDidLayoutSubviews()
が繰り返し呼ばれるため、キャンバスの更新処理を追記しました。
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// TODO: キャンバスの再計算
}
UIScreen.main.bounds/nativeBoundsの値が謎
Macアプリの場合、UIScreen.main.bounds
およびUIScreen.main.nativeBounds
の値が、アプリのウィンドウサイズでもなく、Macのディスプレイサイズとも異なる値を返してくるため、このプロパティに依存したコードを書いていると影響が出るかもしれません。UIScreen.main.bounds
を使わないように修正する必要がありました。
App Store申請
今回、iOSアプリをApp Storeへ、MacアプリをMac App Storeへそれぞれ申請しました。申請手順については以下の記事で紹介しています。
iOSアプリとMac CatalystアプリのApp Store申請手順
まとめ
今回はView(見た目)の調整を中心にアプリを作りました。CameraやBluetooth、Audioなどハードウェアの利用、AWSやFirebaseといった外部サービス連携などは試せていませんが、少ない労力でiOS向けとmacOS向けのアプリを作ることができました。
特に、iOS開発に慣れた人がmacOS向けのアプリを開発する場合、Mac Catalystは短期間で高品質のアプリを作る有効な手段になるかもしれません。
参考
-
「この秋からiOSアプリがmacOSで動くようになる」, TechCrunch
https://jp.techcrunch.com/2019/06/04/2019-06-03-ios-apps-will-run-on-macos-with-project-catalyst/ -
「iPad用AppをMacへ」, Apple
https://developer.apple.com/jp/mac-catalyst/ -
「Twitter公式アプリのMac版がMac Catalystアプリとして復活」, TechCrunch
https://jp.techcrunch.com/2019/10/12/2019-10-11-twitter-comes-back-to-the-mac/