リンクをコピーしました

iOSアプリ「LIFE OFFICE」を実際にリリースしたので、企画からApp Storeに並ぶまでの記録を残しておきます。


きっかけは自分の怠け癖

フリーランスになってから、朝も仕事中もびっくりするくらい怠惰になっている事を感じ始めました。。
会社員のころは通勤や始業時間という強制力があったけど、それがなくなった途端、ダラダラと1日が始まってダラダラと終わる。

「よし、やるぞ」というスイッチをどこかに用意しないとまずい、と思ったのが企画の出発点です。
元々楽したい怠け癖があるので、そこにAIが加わったことでやってる量は減っているけど、作業量はほぼAI使わずにやっていた頃と変わらない。
ただ、モード切り替えが出来ていないせいか、ダラダラと作業を長引かせてしまうということが多発。

そこで、昔のタイムカードの「カチッ」という感触をスマホで再現できたら、それが自分にとっての儀式になるんじゃないか。

あわせて、フリーランスは稼働時間があいまいになりがちなので、出勤・退勤を記録して可視化することも目標にしました。
元々個人で活動している人に向けてでしたが、Proプランでは勤怠を共有したい方々にもPDFやCSVなどで共有することも可能にしました。

プライバシーを守った完全ローカルで動くタイムカードアプリです。


企画からモック作成まで

思いつきをいきなりコードにはせず、まずはイメージ化する所から始めました。

最初にChatGPTでざっくりしたイメージ画像を作って、方向性を固めました。
そこからClaude DesignやGoogleスティッチを使って、画面ごとの構成やコンポーネントをもう少し詰めていきました。

出勤画面・打刻完了画面・記録一覧・設定・統計・ウィジェットの画面設計モックシート

こうやって主要画面をひと通り並べておくと、後で実装を頼むときの解像度がだいぶ上がる感覚があります。
「なんとなくこんな感じ」で終わらせず、ボタンの並びや情報の粒度まで画像に落としておくと、AIに投げる指示もあまりぶれません。


実装はClaude Codeに細かく指示しながら

今回が初めてのExpoでのiOSアプリ開発でした。
React Nativeはさわったことがあっても、打刻の演出、振り検知、ウィジェット拡張まで含めた個人開発は未経験です。

それでもClaude Codeに機能単位で細かく指示を出しながら進めることで、初心者でも思ったより苦労せずに形にできました。
「この画面はこういう状態を持つ」「このデータはこう保存する」くらいの粒度で会話しながら実装していく感じで、詰まったところはそのつどモックの画像を見せて認識を合わせています。

実際にできあがった打刻画面がこちらです。

下にスワイプして打刻する画面。LIFE OFFICEのパンチカード風カードと出勤前のひとことメモ欄

下にスワイプするとレシート風の打刻結果が出て、そのまま写真保存や共有ができます。
このスワイプとレシートの組み合わせが、自分の中では一番「儀式っぽさ」を感じるところです。


ウィジェットはExpoの外側を自作プラグインで作った

ホーム画面ウィジェットだけは、Expoの標準機能の範囲では実現できませんでした。
WidgetKitのExtensionはExpoのconfig pluginが用意してくれるものではないので、modules/life-office-widgetという自作のExpo Moduleを作って対応しています。

中身は大きく2つです。

ひとつは、expo prebuildのタイミングでXcodeプロジェクトに「LifeOfficeWidgetExtension」というターゲットを自分で追加するconfig plugin(withLifeOfficeWidget.js)です。
withXcodeProjectwithDangerousModwithEntitlementsPlistといったExpoのconfig-plugins APIを使って、ExtensionのInfo.plistやentitlements、ビルドフェーズ、App Groupの共有設定までスクリプトで組み立てています。

もうひとつは、JS側からWidgetKitを呼び出すための小さなネイティブモジュール(LifeOfficeWidgetModule.swift)です。
reloadWidgets()というAsyncFunctionを1つ公開しているだけですが、これがないと打刻してもウィジェットの表示が更新されません。

データの流れはこうです。

  1. アプリ側で打刻や設定が変わるたびに、今日の状態や月間サマリーをJSONにまとめる(widgetExport.ts
  2. それをApp Group(group.com.testkun08080.lifeoffice)の共有コンテナに書き出す
  3. 自作モジュールのreloadWidgets()を呼んでWidgetKitに再読み込みを依頼する
  4. Widget拡張側のSwiftコードが共有コンテナのJSONを読んで描画する

ここもClaude Codeに実装を頼んだ部分ですが、Expoの外側(生成されたXcodeプロジェクトそのものをどう書き換えるか)まで踏み込む必要があったので、普通の画面実装より一段難易度が高かったです。


CI/CDはXcode Cloud

ビルドとTestFlight配布にはXcode Cloudを使いました。
Apple Developer Programに含まれる無料枠があるので、個人開発だとありがたい選択肢です。

Expoのプロジェクトはそのままだとビルドできないので、ci_post_clone.shでNodeのセットアップとexpo prebuildを仕込む必要がありましたが、この構成は以前の記事でまとめた手順とほぼ同じです。
一度組んでしまえば、あとはmainにpushするだけでTestFlightにビルドが上がってくるようになりました。


審査用スクリーンショットはFigmaで

App Store提出に必要なスクリーンショットは、実機やシミュレータのキャプチャをそのまま使うのではなく、Figmaで整えて作りました。
ステータスバーの見た目を揃えたり、画面ごとに軽くキャッチコピーを添えたりする作業は、意外と地味に時間がかかります。


審査で一度リジェクトされた話

順調に進んだように書いていますが、実は一度審査に落ちています。
Guideline 4 / 2.1(b)で、iPadで見るとホーム画面の文字が重なっているという指摘でした。

iPhone側だけを見て確認していたので、iPadの表示崩れにまったく気づいていませんでした。
該当箇所を直して、修正版のビルドと一緒に再提出したところ、無事に通過しました。
1回落ちたこと自体は地味にへこみましたが、原因がはっきりしていたぶん対応は早く終わりました。

また、maestroを使ってシミュレーター上で問題になりそうなosと実機でインストールさせてローカルでスクショをさせて、それをAIに読み取らせて不自然な見た目がないかもチェックするようにしました。


企画からリリースまで、約1.5ヶ月

ざっくり振り返ると、企画からApp Storeでの公開まで、だいたい1.5ヶ月くらいでした。
初めてのExpo/iOSアプリでこの期間に収まったのは、正直AIをフルに使ったからだと思っています。
一番時間を要したのは審査のための素材作成とチェックかなと思います。


このアプリの紹介

無料でも一通りの打刻・記録は使えます。

  • スワイプ打刻 / 振り打刻(端末を振っての打刻。設定でON/OFF可能)
  • 後打刻(打ち忘れた記録をあとから追加)
  • 出勤前メモ・退勤コメント
  • 打刻レシートの写真保存・共有
  • 記録一覧・カレンダー・月次統計

勤務中はホーム画面がこんな見た目になります。

勤務中のホーム画面。出勤時刻・経過時間・退勤時刻の入力と退勤するボタン

記録は一覧でもカレンダーでも振り返れます。

記録一覧画面。日付ごとに出勤・退勤時刻と勤務時間が並ぶ

月次の統計では、平均出勤時刻・平均退勤時刻・平均勤務時間・合計勤務時間と、日別の勤務時間グラフが見られます。

月次統計画面。平均出勤時間・平均退勤時間・平均勤務時間・合計勤務時間と日別勤務時間のグラフ

買い切り600円のLIFE OFFICE Proにすると、次の機能が解放されます。

LIFE OFFICE Proのペイウォール画面。ホーム画面ウィジェット・統計の拡張・メモ検索・CSVエクスポート/インポートの案内
  • 広告の非表示
  • ホーム画面ウィジェット
  • ライト/ダーク切り替え
  • 週次統計・ヒートマップ
  • メモ全文検索
  • CSV/PDFエクスポート・CSVインポート
  • レシートのバーコード内容のカスタマイズ

ウィジェットを置いておくと、ホーム画面から今日の勤務状況がひと目でわかります。

ホーム画面ウィジェット。勤務中のステータス、出勤時刻、経過時間、今月の合計勤務時間のグラフ

データはすべて端末内保存で、アカウント登録は不要、オフラインでも使えます。


関連リンク

制作フローで使ったツールまわりの話は、こちらの記事もあわせてどうぞ。