🗒️

iCloudで同期する付箋アプリを作ってみた。要件定義, Apple Developer登録, アイコン作成, リリースまでのワンマン開発記

2023/12/26に公開

macOSに最初から入ってるスティッキーズという便利なアプリ知ってますか?画面上のどこにでもピン留め(常に最前面に表示)できて、めちゃめちゃ小さく折りたためる。ツールバーやウィンドウコントロールのUIがギリギリクリックできるくらい小さく、「付箋」であることに特化しているアプリです。

メモアプリも標準で入ってますが、スティッキーズみたいに極限まで最小化したり、半透明にしたりはできません。


🔼極限まで最小化した状態のスティッキーズ。比較用に背景に別のアプリのControls(閉じるボタンなどがある部位のこと)を置いてみました。

スティッキーズのiCloud対応を私は切望しているのですが、iCloudが登場してから10年以上経っても対応されないので、おそらくされないのだと思っています。

Appleがやらないなら俺が作る。

このような思いから、今年の1月頃にmacOS, iOS両対応の付箋アプリ制作を着工しました。

対象読者

次の人に向けた知見を書いたつもりです。

  1. macOS, iOSのアプリの要件定義からリリースまで、何をする必要があるかをざっくり知りたい人
  2. Apple Developer Programの登録がうまくいかない人
  3. macOSアプリ開発を初めてやる人(特にCloud Kitを触ってみたい人)

できたもの

クラウドで同期する付箋なので、そのままCloud Stickiesと名付けました。無料で、iCloudを使っていればお手元のmacで使用可能です。ダウンロードしたい方はこちらより。
Cloud Stickiesのアプリ詳細・ダウンロード

macOS


🔼ピン留めすると他のアプリよりも常に前面にくる


🔼最小化表示!


🔼付箋一覧ウィンドウ

iOS(仮・未リリース)

付箋一覧 付箋 色変更UI

要件定義

このような感じで勢いで始めた開発でしたが、コードを書き始める前にまず欲しいアプリの具体像を書き出しました。
既存のスティッキーズと同じ要件、自分のアプリに加えたい追加要件、既存のアプリとの違いをつらつらと書いてみます。

ワンマン開発なので、しっかりとした要件定義はしなかったですが、大体作るものは既存の付箋アプリに足りないものや、自分の普段の作業で必要としていることのリストアップで決まっていきました。

作る意義がしっかりないと手を動かす気にならない性格なのですが、世の中にありふれている付箋アプリなどのなかにこれらの要件を満たすものは無かったのと、完成すればこれを使って仕事の効率を上げられそうだと思ったのと、あとは1人で他人と合わせることなく夢中にゴリゴリ開発をする経験を久々にしたい、という点で制作する意義は持てました。

技術選定や実装上の詰まりポイント

Swiftでネイティブアプリ

Swiftでネイティブアプリとして制作することにしました。技術選定というほどたいそうなことはしておらず、ある意味思い切りですが、ざっと理由はこんな感じです。

まず自分がMacとiPhoneしか使ってないので、ネイティブアプリで良いなというのがありました。技術面では、macOSのウィンドウマネジメントやiCloudのAPIを駆使することになりそうだったのも理由の一つです。また個人の知識としては、元々私はiOSアプリエンジニアなので比較的多くの知識がそのままmacOSアプリ開発にも使えるだろう、という理由もありました。

オールSwiftUI構成の断念・AppKitの採用

今時iOSアプリだと簡単なものは全てSwiftUIで作れてしまう場合が多いので、付箋アプリくらいだったら全部SwiftUIで作れるっしょ!っと思ってたのですが、そうはいきませんでした。

スティッキーズはmacOSのアプリに標準装備されているtitleBar, Controlsと呼ばれるパーツが無く、これを再現するにはAppKitの採用が必要でした。これについては、こちらの記事に知見をまとめているので、興味があればぜひご覧ください。

titleBarのないmacOSアプリを作ろうとしてハマったあれこれ

今回は、Window自体はAppKitのコードで操作し、中身のテキストエディタの部分や付箋一覧画面の部分はSwiftUIで作りました。iOSアプリの方は全てSwiftUIで制作しています。

SwiftPackageによる構成

今回macOS, iOSの2つのアプリを、見た目以外のデータ取扱等の部分はほぼ同様になるため、共通化しています。この構成によるメリット・デメリットは多くのテックブログで触れられているので、そちらを参照してください。今回はロジックの共通化をする以外に特に選んだ理由はないです。

  • Model
  • ViewModel
  • CloudService
    • iCloudとの通信を担う
  • MarkdownUtil
    • 当初Markdown対応しようとしていたため、その関連
  • stickies (xcodeproj)
    • macOS版のUI関連
  • stickies-mobile (xcodeproj)
    • iOS版のUI関連

今回はMVVMでの構成とし、model, viewModelの一部をmacOS, iOS間で共通としました。

SwiftGenでi18n

このアプリは日英の2言語に対応させたかったため、i18nを最初から入れてあります。選定理由を忘れてしまったのですが、確か他の手段と比べて比較的楽に生成できるのがSwiftGenだった気がします。

詳しい使い方は公式Docを参照いただければと思いますが、簡単度合いで言うと、configファイルであるswiftgen.ymlに何を生成するか定義したのち、swiftgen config runを実行すれば生成され、コード内でL10n.Localizable.HogeScreen.quit のようにStringを呼び出すことができます。

strings:
    inputs: stickies/stickies/en.lproj
    outputs:
        templateName: structured-swift5
        output: stickies/stickies/Strings.swift
strings:
    inputs: stickies_mobile/stickies-mobile/en.lproj
    outputs:
        templateName: structured-swift5
        output: stickies_mobile/stickies-mobile/Strings.swift

Cloud Kitを使ったiCloud同期機能の実装

CloudKit Databaseでスキーマやクエリの定義をする

CloudKit DatabaseでiCloudで管理するデータのスキーマ定義をしていきます。次にインデックスを作成しクエリで取得できるようにします。CloudKit Databaseで定義してないデータスキーマやインデックスのクエリは実行しようとするとエラーが出て実行できないため注意です。
CloudKit - iCloud - Apple Developer

データベース:CloudKitに直接保管されているデータにアクセスするには、CloudKit Databaseアプリを使用します。

更新されたことを検知して何かする

別の端末からiCloudに更新がプッシュされたとき、また自分の端末からiCloudに更新をかけそれが反映されたときのデリゲートメソッドをAppDelegateで実装できます。

ちなみにiOSは実機じゃないとデータ更新の通知が送信されないので注意です。つまりiOSのSimulator+macOSで、いくらmac側で待機してても以下のデリゲートメソッドは呼ばれないということです。

    func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) {
        if let ckNotification = CKNotification(fromRemoteNotificationDictionary: userInfo) {
            // やりたい処理を追加する
            return
        }
    }

メニューの実装

メニュー

この部分です。Storyboardで実装する方法とコードで全部書く方法がありますが、前者の方が楽(体感)でした。

メニューバーをクリックした時に出るメニュー

巷のアプリでmenubarをクリックすると出てくるものは以下の2つです。

  • Menu
    • システムの右クリックメニューのようなUIのメニュー項目が出てきて、何をするか選択できる
  • View
    • Adobe CC, JetBrains Toolbox, Figmaのように、Viewが出てきてそれは好きなUIにできる

今回はMenuの方を選択しました。本当はカスタムViewをあてて付箋一覧を表示したかったのですが、Cloud Stickiesを選択後に他のアプリのMenuBarItemにactiveが戻らない等の不具合が発生したため、一旦見送ってます。

メニューとメニューバーのメニューで同一ショートカットキーの割り当てができなかった

本アプリではcmd+Nで付箋の新規作成ができるようになっていますが、このショートカットキーはメニューのStoryboardで設定しています。menuBarItemから出しているメニューにも、新規作成の項目を作ったので、こちらにもショートカットキーを割り当てたかったのですが、同一ショートカットキーを割り当てることが禁止されているようで、これも今回見送りました。

配布・アプデ提供方法

macOSアプリ

XcodeでDeveloper IDでのappファイル出力→zipファイルに詰めて自分のWebサイトに置く、Sparkleをアプリに載せてアップデートを随時確認、という方式に落ち着きました。

Sparkle: open source software update framework for macOS

まず審査を通すのが大変なのでAppStoreでの配布は選択肢になく、brew 等で配布するのも今は必要もないと思い、自分のWebサイトに置くことにしました。

アップデートは、自分のWebサイトやストレージに、バージョン情報やバンドルのハッシュ等が記載されるxmlファイル(Sparkleによってアップデート作成時に生成)を置き、アプリは適宜それを確認し、更新があればユーザーにアップデートを促す、という流れになります。

この際Sparkleでは、zipでappを詰めたファイルを配布することが推奨されています。dmgはdmg, appの両方にコード署名をする必要があり、またディスクユーティリティを使った手順が煩雑ですが、ユーザーがdmgを開いたときにApplicationフォルダにappを移動してもらえるような体験を作ることができます。
私はこの要件はそこまで必要ないと感じたのと、今はできてないですが将来的にCI/CD化する際に、全てCLIでかつ簡潔にリリースできた方が嬉しいため、zipで配布することにしました。dmg配布も頑張ればCLIで全てのフローを網羅できるとは思います。

iOSアプリ

iOSアプリは実質AppStore一択なので考える余地はあまり無かったです。審査を通すために満たさなければいけない項目をまだ満たせてないので、リリースしてません。

Apple Developer Programへの登録

今まで会社のApple Developer Programで会社のアプリを開発したことはあったのですが、自分のものは持っておらず、この際に取得しました。

法人登録はAppleの審査があり1週間以上時間を要するのですが、個人はそれはなく、すぐ使用可能な状態になりました。

MacやiPhoneでApple IDは複数アカウントサインインでき、2FAコードも受け取れる

非常に意外だったのは、MacやiPhoneでApple IDは、システム環境設定のインターネットアカウントという項目から複数アカウントサインインでき、2FAコードが受け取れることです。Macの場合は同一ユーザーで複数アカウントサインインできます。

ブラウザよりDeveloper.appを使ったほうが良いかも

Chromeは当然のごとく、Safari版も挙動が怪しいのでAppStoreからAppleのDeveloper.appをダウンロードして使うのがよいです。(2023年1月時点)

Developer.appでアカウント情報を確認したときに、Apple Developer Programの「今すぐ登録」ボタンが非活性状態でWebからの登録を勧められる場合、そのアカウントで操作を進めたとしてもその後のフローで弾かれる(審査状態がPendingのまま進まなくなる)ことがあるので、新しいアカウントを作成して作るのが良さそうです。(なお活性条件は不明)

新しいApple IDを作成して作る

普段使いのアカウントと開発用のものは分離したほうが良いらしい。
ref: https://unitynightowlgames.hatenablog.com/entry/2016/09/23/110949

  • アカウント作成時に他社サービスのメールアドレスを使用していた場合、Program登録に失敗することがあるらしい
  • アカウントを作成する際、iOSデバイスでアカウントを作成すると作成時のメールアドレスが不要(Gmailなど他社サービスのもの)です
  • Developer.appで新規作成したアカウント(macOSでログインしてるものとは別でも良い)でログインしDeveloper Programへの登録を進めると、最後に課金するところで別のアカウントで支払うことも可能です。支払いはApple IDの"サブスクリプション"で管理されます

住所などは全て英語で入力する

ブラウザ版だとバリデーションが効いてて英数しか入らないが、Developer.appだと日本語で入れられてしまうので注意です。

証明書の管理

キーチェンでcsrファイルを作成しDeveloper管理画面でcerファイルを発行した場合、csrを作成したのと同じmac(物理マシン)・同じApple IDである必要があるので、それに該当しない状況(他のマシンやCI/CDランナー上)で同じ証明書を使いたい場合はp12形式にエクスポートして共有します。

今回はDeveloper IDの証明書がappへのコード署名で必要でした。

アイコン制作

アイコンをどう作るか、どのようなアイコンにするかをなかなか決められず(判断基準を持てていなかった)時間を食いましたが、最終的にアイコンはFigmaで作りました。AI生成でも試したのですが、あまりしっくりくるものが出せず。頭の中に浮かんだものが、こんな感じでめっちゃ簡素なものだったので、Figmaでさっと作りました。

明るめのトーンで、抽象的な付箋っぽい雰囲気を出せたんじゃないかなと思います。

macOS用アイコンのテンプレートは探せば転がってるので、それに自分のアイコン用素材を1箇所にはめれば、バンドルに必要な全サイズのものを書き出せる状態にしてくれます。

テンプレートにはアイコンがバランスよく見えるようにするための黄金比?のガイド線が引いてあり、色のついた四角形をそれに合わせて並べました。

https://www.figma.com/community/file/1040708197994685442/official-macos-app-icon-template

感想

このアプリを作り始めたのは、ちょうど1年前くらいでした。そこから2〜3ヶ月でおおよそ現在のリリースしている形にはなっていたのですが、アプリアイコンの作成や配布準備、自動アップデート周りの実装ができておらず、リリースが今月まで遅れました。

今後の展望

まず、iOS版をリリースしたいと思ってます。次に上述したMenu Bar周りの積み残し回収をしたいと考えています。

あとは、今回はオフライン時にはテキストを編集できないようにしています。オフライン時に編集できるようにすると、万一競合が発生した際の解消や、一旦ローカルに保持するなどの実装が増えます。前者についてはCloud Kitが提供しているAPIを使うことで、比較的簡単になんとかなる読みをしているのですが、蓋を開けてみないとわからないです。デスクトップアプリとしてちゃんとするのであれば、オフラインで使えるのはMUSTだと思っているので、こちらもやっていきたいと思っています。

また今回の開発では、要件定義とコードを書く以外の箇所が、そう何度もやったことのある作業ではなかったので、手間をとりました。次回の開発では今回の経験を活かして、スムーズに進められるようにしたいと思います。

Discussion