実際に使ってみる
この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます
たべメモを個人開発して App Store に出すまでにつまずいた5つのこと。審査、APIキーの持ち方、CloudKit の制約、ウィジェットの残高ズレ、そして作り直し続けたUI。
実際に使ってみる
この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます
カロリー記録アプリが続かない理由は、だいたい入力の重さにあります。食品DBを検索して、分量を選んで、単位を合わせて……1食3分。これを毎日3回やれるなら、そもそも太っていません。
ほしかったのは栄養管理ではなく、「今日、あと何kcal食べられるか」という残高の数字ひとつでした。だから「アルフォート3枚」と雑に書いたら、AIが推定して残高が減る——それだけのアプリにしました。
ところが作ってみると、アプリの中身より「公開できる形にする」ところで詰まりました。以下、実際につまずいた5つです。
v1.0 の審査で Guideline 5.1.1(i) / 5.1.2(i) を指摘されました。入力テキストを第三者のAIサービスへ送るなら、送る前に、アプリ内で明示的な同意を取れ、という趣旨です。
build 5 でやったのは次の3つでした。
効いたのは2つ目です。このアプリには Siri(App Intent)からの記録経路があり、そこは同意画面を通りません。画面側だけで制御していたら、その入口が空いたままになっていました。「同意を取る」を画面の仕事ではなく通信層の不変条件にしたことで、入口が増えても塞がったままになります。
審査への返信では、送るのはユーザーが入力した食事テキストだけ(音声入力も端末側で文字起こしした結果だけで、音声そのものは送らない)、アカウントも位置情報も記録履歴も送らない、と箇条書きで説明しました。「送っていないもの」まで書いたほうが話が早かったです。
最初は、ユーザー自身の Anthropic APIキーを設定画面に入れてもらう作りでした。個人開発ではよくある形で、自分で使うぶんにはこれで十分動きます。
ただ一般公開を考えると、アプリを入れた人の最初の一歩が「APIのアカウントを作って、従量課金のキーを発行する」になります。ここで大半の人は消えます。
そこで推定を自前のバックエンドプロキシ(Vercel)経由に変え、APIキーはサーバ側で持つことにしました。ただしキーを開発者が持つということは、誰かが叩けば実費が飛ぶということでもあります。対策として App Attest を入れ、正規のアプリからのリクエストであることを端末レベルで検証しています。
そして「実費が出ていく」ことが、そのまま広告と買い切り(¥300で広告を消す)を入れた理由になりました。収益化を先に決めたのではなく、キーをサーバに置いた時点で、収益化しないと続かない構造になったという順番です。
端末間同期のために SwiftData を CloudKit バックドにしたのですが、ここで制約が2つ効いてきました。
#Unique など)が使えない後者が地味に厄介でした。設定は「常に1件」という前提でコードを書いていたのに、DB側でそれを保証できません。実際、複数端末で同時に初期化されると設定レコードが2件できます。
結局、「取得時に重複を見つけたら、フィールド単位で決定的にマージして1件に畳む」という処理をアプリ側に持たせました。あわせて、読むだけの画面やウィジェットが .first で1件目を拾う書き方も禁止にしています(順序が保証されないので、端末によって違う目標値が出ます)。DBが守ってくれない不変条件は、アプリ側に置き場所を作らないと守れません。
このアプリの肝は「どこから記録しても残高が一致する」ことです。アプリ本体・ウィジェット・Siri の3経路が、App Group 上の同じ SwiftData ストアを共有しています。
それでもズレました。原因は単純で、書き込みのあとに WidgetCenter.shared.reloadAllTimelines() を呼び忘れるだけでズレます。保存・削除・Siri経由の記録……書き込み経路が増えるたびに、呼び忘れが1か所生まれました。
残高の計算式自体も、最初はアプリとウィジェットに別々に書いていました。今は計算をひとつの関数に集約し、UIを持たない framework に置いて3ターゲットから呼んでいます。「同じ数字を2か所で計算しない」は、地味ですがこのアプリでいちばん効いた判断でした。
技術の話より恥ずかしい話です。編集画面で「朝/昼/夕/間食」を選ぶUIだけで、コミットが10本近くあります。色付きピル → セグメント → カプセル調に統一 → それを撤回 → 枠が二重に見えるので自前セグメント → 幅を他のフィールドに揃える……という具合です。
機能としてはどれも同じです。ここで溶けた時間は、機能を1つ足せた時間でした。デザインの微調整は手応えがある割に終わりがないので、いまは「機能が固まってから、まとめて1パスだけ」と決めています。
PFC分析、食事の採点、アドバイス、リマインド通知、食品DB、バーコード。全部入れていません。
どれも「あったほうが良さそう」に見えますが、足すと結局「入力を頑張る」アプリに戻り、続かない理由そのものが復活します。全画面広告にも同じ考えで歯止めを入れていて、4回の記録につき1回・1日3回まで・前回から3分以上、という条件を全部満たしたときだけ出します。「雑に書く→残高が減る」の手触りを毎回潰さないためです。
App Store で公開中で、8言語まで来ました。とはいえ直近で直したのは「推定結果の品目名が、入力と違う言語で返ってくる」というプロンプト側のバグです。多言語対応はUIを訳して終わりではない、というのが最近の学びでした。