Engineering

Vigilare の iOS 版を作っている

  • iOS
  • SwiftUI
  • Vigilare
  • Apple Reminders

macOS で動かしている Vigilare は、Apple Reminders を常時最前面のフローティングウィンドウで出しっぱなしにする、というのが核です。iOS にはこの「常時最前面」の前提が成立しません。同じ Apple Reminders 連携を扱うのに、iOS 版は体験の軸ごと別物として組み直す判断をしました。今もその開発を進めている途中です。

vigilare.labee.devVigilare - Floating Reminders for macOS | Always VisibleNever miss a task with Vigilare's floating window for Apple Reminders. Always visible on your Mac, even in fullscreen apps. $9.99 one-time purchase.

この記事では、iOS 版で落としたくなかったもの、macOS 版の前提のうち iOS では成立しないもの、検討して却下した代替案、別物として組むと決めた3つの判断軸、いまどこまで動いているかを順に扱います。共通のドメイン層 (VigilareKit) の話は別記事で扱います。

iOS 版で落としたくなかったもの

Vigilare は「Apple Reminders に書いたタスクが、別のアプリに切り替えなくても視界に入っている」状態を作るためのアプリです。macOS 版では常時最前面のフローティングウィンドウで、その状態を作っていました。

iOS 版を始めるときに、落としたくなかったのは次の2点です。

  • Apple Reminders を一次データソースに使う (アプリ内に独自のタスクストレージを作らない)
  • 「いま着手すべきタスクが視野にある」という体験軸

逆に、iOS 版で諦めることになったのは macOS 版の表現方式そのものです。常時最前面のフローティング、menu bar アプリの常駐、ウィンドウを畳むと小さくなる縮小アイコン、全 Spaces への配置といった macOS 表現は iOS の OS 前提と直接ぶつかります。

落としたくない2点と、iOS で成立しない macOS 表現とのあいだに、別物として組み直す余地がありました。

macOS 版の前提を iOS に移せない理由を整理する

整理すると、macOS 版が前提にしているものは iOS では次のように扱えません。

macOS 版の前提iOS 版での扱い
常時最前面のフローティングウィンドウフォアグラウンドのアプリが画面を占有するため、アプリ間の重ね合わせの概念がない
menu bar アプリとしての常駐menu bar 相当の常駐領域がない
全 Spaces 配置Spaces 相当の機能がない
ウィンドウを畳むと現れる縮小アイコンウィンドウを縮小して画面上に置いておく場所がない

ここを「同じ表現で揃える」と決めた時点で、iOS 版は OS の前提と戦い続けるアプリになります。逆に、表現は別物として、体験軸 (Apple Reminders 連携と「視界に入る」) だけ揃える、と決めれば iOS の標準的な動線に乗せ替えられます。

iOS の標準でどう代替するか3案並べた

「いま着手すべきタスクが視野にある」を iOS で組み直すとき、画面構成の選択肢として3案を順に試しました。

案A 左メニュー + 1リスト表示

macOS 版の構造をそのまま縮小して、画面左にリスト切替メニュー、右にタスクリストを置く案です。最初に組んで触ってみました。

iPhone の幅では、リスト切替メニューを開く動線が片手で扱いづらく、「タスクが視野に入る」までの操作が深くなりました。macOS では同時に表示できていたリスト切替とタスクリストが、iPhone ではメニューを開く操作で隠れる関係になります。「視界に入る」を狙うはずが、操作の度に視界から消える形になりました。

案B Apple リマインダーに近い NavigationStack 階層

リスト一覧からリスト内タスク、そしてタスク詳細、という Apple のリマインダー app に近い階層構成です。iOS のユーザーには馴染みのある動線です。

ただ、Apple のリマインダー app と同じ動線で良いなら Vigilare を選ぶ理由が薄れます。「今日着手するタスクをすぐに見たい」という目的に対しては、起動からリスト選択を経て表示まで段階が増え、視野に入るまでに 2 タップ以上要します。

案C 3 tab + FAB

主要な視点 (Today / All / Browse) を tab で並列に並べ、追加操作だけ右下の FAB に固定する案です。起動直後の Today タブに今日着手するタスクが直接出ます。

「アプリを開いた瞬間に視野に入る」という体験軸に対して、案 C が最も短い動線になりました。Today タブの「今日が期限のタスク」が、起動直後の追加操作なしで読める状態になります。

3案を並べた結果、視界に入るまでの動線の深さで案 C を採用しました。

「アプリを開けば今日のタスクが見える」に振った3つの判断軸

iOS 版を別物として組むと決めたとき、ぶれないように立てた判断軸が3つあります。

1つ目は、データソースは Apple Reminders に固定するという軸です。macOS 版でも iOS 版でも、ユーザーが書き込む先は OS の Apple Reminders です。Vigilare 内に別のストレージを作ると、Apple リマインダー app と Vigilare の表示が乖離しうるし、複数端末同期も別経路を持つことになります。Apple Reminders を介していれば、macOS と iOS で同じデータを別の体験で読む、という構造に自然に乗ります。

2つ目は、体験はプラットフォームごとに作る、という軸です。macOS の「常時最前面」と iOS の「アプリを開けば見える」は、再現を目指す表現ではなく、別の手段で同じ目的に向かう設計にしました。プラットフォームの前提を曲げて表現を揃えると、片方の OS と戦い続ける状態になります。前提を活かして体験を組み直すほうが、どちらの OS でも自然なアプリになります。

3つ目は、共有ラインは Views の手前で止める、という軸です。Models や Repositories や Services といったドメイン層は macOS と iOS で共通化する一方、Views はプラットフォーム別に持ちます。共通化を Views まで広げて「macOS と iOS でなるべく同じ画面」を目指すと、両方の OS で妥協のある画面ができあがります。共有を境界の手前で止めて、Views だけ別物として持つほうが、それぞれの OS で素直な体験になります。

この3軸に従って、iPhone は 3 tab + FAB、iPad は NavigationSplitView の二分割、と表現を別物にしながら、ドメイン層を共通化する形に落ち着きました。

いまどこまで動いているか

iOS 版はまだ App Store には出していません。基本動線は動いており、Apple Reminders との同期、tab + FAB の追加動線、リスト一覧と編集、リマインダーアクセス権の取得まで一通り通っています。

残っているのは、smart filter を適用したときの初期の並び順、タスク完了演出の頻度調整、iPad の split view から編集に入るときの遷移、あたりです。完了演出のタイマーキャンセルとドロップ確認ダイアログを直近で入れたところです。

機能ではなく目的でクロスプラットフォーム化する

「macOS 版と iOS 版を同じプロダクト名で出す」と言ったとき、ユーザーは「同じ機能のクロスプラットフォーム版」を想像しがちです。Vigilare の場合、macOS 版と iOS 版が共有しているのは Apple Reminders 連携という機能ではなく、「いま着手すべきタスクが視野にある」という目的のほうです。

macOS のアプリは「常に開いておく」が成立する OS の主役側、iOS のアプリは「使うときに開く」のが前提の OS です。同じ目的を、それぞれの OS の前提に合わせて別の表現で組み直すと、ユーザーから見たプロダクトとしての一貫性は、機能ではなく目的のレベルで取れるようになります。

機能のレベルで揃えようとすると、片方の OS と戦うアプリになります。プラットフォーム間で何を変えないかを1つだけ先に決めると、残りは別物として持ってよくなり、機能の同一性を揃える義務から外れます。Vigilare ではその「変えないもの」を Apple Reminders 連携と「視界に入る」体験軸に置いていて、それ以外は OS ごとの素直な形に任せています。