個人開発したiOSアプリ「LoopMate」について書きます。技術的な解説というよりは、何を考えて、何を捨てて、どこで詰まったのかの記録です。実装そのものより、企画を決めるまでの過程と、途中で作り直す羽目になった話のほうが、自分にとっては学びが大きかったからです。
LoopMate は、友達とルームを作り、日々の習慣の達成状況を共有し合うiOSアプリです。できることはシンプルで、次のようなものです。
実は、最初に作ろうとしていたのはまったく別のアプリでした。家計簿SNSです。
出発点は自分の課題でした。家計簿は、続ければ続けるほど価値が出ます。記録が溜まって初めて「何にいくら使いすぎているか」が見えてくるからです。ところが自分は、いつも途中で飽きて辞めてしまっていました。価値が出るまでの距離が長く、その間モチベーションが保たないのです。
そこで考えたのが、友人と一緒に記録する仕組みでした。一人だと辞めても誰も困らないけれど、誰かが見ていれば辞めにくい。加えて、友達のお金の使い方が参考になるし、自分だけ使いすぎていることに気づけば危機感も生まれる。継続できない原因を「自分の意志が弱いから」ではなく「一人でやっているという構造」に求めた、という発想です。
ただ、詰めていくうちに致命的な問題に気づきました。お金は、多くの人が他人に見せたくない情報だということです。自分は平気でも、誘った相手が乗ってくれなければ共有型のアプリは成立しません。参加者が集まらない前提のSNSを作っても意味がない。ここでこの案は捨てました。
このとき判断の軸を、「自分が使いたいか」から「共有することに抵抗がないか」に切り替えました。整理すると、こういう線引きになります。
習慣の達成状況は後者です。何をどれだけ頑張ったかという中身まで晒す必要がなく、○か×かだけで成立する。継続を助けるという目的に対して必要十分でありながら、共有の心理的コストが小さい。この点が決め手になりました。
さらに調べてみると、習慣化アプリ自体は数多くあるものの、友人と達成状況を共有するタイプは意外と少なかった。ここなら作る意味があると判断して、LoopMate の開発を始めました。
結果的に、残ったアイデアより、捨てたアイデアのほうが多くを教えてくれました。家計簿SNSを検討しなければ、「共有できる情報とできない情報の境界」という観点自体を持てなかったはずです。
技術構成は SwiftUI / Firebase Authentication(匿名認証)/ Cloud Firestore / Swift Concurrency です。設計として意識したのは次の点でした。
一番きつかったのは、新しい機能を作ることではなく、途中でデータ構造を変えたことでした。
当初、ユーザーの識別は username(表示名)で行っていました。作り始めた時点ではそれで動いていたのですが、後から usernameKey という識別専用のキーを導入しました。表示名そのものを識別子に使うのは安全面で危ういと考えたためです。
あわせて、ブロック機能も追加しました。これは App Store の審査基準を満たすためです。ユーザー同士がつながるアプリでは、不適切な相手を遮断する手段が必要になる。自分が作りたかった機能ではなく、無いと世に出せない機能でした。
問題は、この2つがどちらも「ユーザーをどう識別するか」に関わっていたことです。username で人を特定している処理は、アプリの至るところにありました。フレンドの検索、ルームのメンバー一覧、誰が何を達成したかの集計、カレンダーの表示。「ユーザーを指す」処理は、ほとんどすべてが影響範囲になります。ブロック機能も同じで、誰かの一覧を出すあらゆる場所に「この人はブロックされているか」という判定が挟まることになりました。
しんどさは2種類ありました。ひとつは、どこを直せば全部直るのか見通せないこと。関係する箇所が一箇所にまとまっておらず、探しながら直す形になりました。もうひとつは、直したつもりが別の画面で落ちること。ある画面が動くようになっても、別の画面が古い前提のまま残っていて壊れる。これが繰り返されると、今どこまで直せているのかが自分でも分からなくなってきます。
振り返って思うのは、これは「後から直すのが面倒だった」という話ではないということです。識別子のような土台は、機能ではなく前提であり、前提を変えるとその上に乗っているもの全部を見直すことになる。頭では分かっていたつもりのことを、手を動かして初めて理解しました。
正直に言うと、この経験から「じゃあ次はどう設計すれば正解なのか」という答えはまだ出せていません。最初からすべてを見通した完璧な設計ができるとも思えないので、変更に強い作り方をどう身につけるかが次の課題だと考えています。
もうひとつは画面遷移です。やりたかったこと自体は単純で、ルーム作成完了のモーダルを閉じたら、そのまま作ったルームの画面に入るという挙動でした。ユーザーからすれば「作ったのだから、そのまま入りたい」のは自然な流れです。
ところが、モーダルを閉じる操作と、次の画面へ進む操作は別物です。閉じる処理と遷移の処理が噛み合わず、モーダルが閉じきる前に遷移しようとして何も起こらない、といったことが起きました。
ここで理解したのは、ユーザーにとって自然に感じられる流れほど、裏側では状態を丁寧に管理する必要があるということです。「気持ちよく動く」体験は、実装として素直であることを意味しません。
技術面では、SwiftUI と Firebase、そして Apple Developer Program まわりの理解が深まりました。とくに後者は、コードを書くだけでは絶対に触れない領域です。作ったものを実際に世に出そうとして初めて、審査基準やアカウント周りの制約といった「開発の外側」が存在することを知りました。
それ以上に身についたのは、機能の優先順位を付けるという考え方です。作りたい機能は無限に出てくる一方、時間は有限です。しかもブロック機能のように、自分の作りたいものリストには無いのに、リリースするためには必須という機能もある。何を作るかを決めることは、同時に何を作らないかを決めることでもあるのだと実感しました。
作り終えて一番印象に残っているのは、実装が動いた瞬間ではなく、家計簿SNSを捨てた瞬間でした。あそこで判断の軸が「自分が欲しいもの」から「他人が使ってくれるもの」に切り替わり、それ以降の意思決定がすべてやりやすくなったからです。
一方で、やり残しも多く残っています。ViewModel 層を入れられておらず、ロジックが View に残ったままです。テストも書けていません。動くものを作ることと、良い設計で作ることはまったく別の話だというのが、今回一番痛感したことでした。MVVM への移行とサービス層の整理は、次に手を付けたい課題です。
今回のアプリはiOSですが、授業でWebを扱って感じたのは、「一人では続かない」という課題自体はプラットフォームに依存しないということです。共有のハードルという意味では、インストールが要らないWebのほうが適している可能性すらある。今後はWeb側の実装も含めて、この課題設定をもう一度考え直してみたいと思っています。
最後まで読んでいただきありがとうございました。ソースコードは GitHub で公開しています。