3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PR: 株式会社フューチャークリエーションファクトリー
F.C.Fでは、一緒に成長していくエンジニアを募集しています 🌱

【個人開発】Claude CodeとChatGPTでiOS個人開発してみた:終電を逃さないアプリ「帰れ、私」を作るまで

3
Posted at

1. はじめに

スクリーンショット 2026-05-31 21.41.06.png

「最近あんまり個人開発してないなあ」「AI駆動開発やってみようかな」「業務で触っているiOSも個人開発で試してみたい」と思い、AIを使ったiOS個人開発に挑戦してみました。

作ったのは、終電を逃さないために帰る時間を通知してくれるiOSアプリです。
アプリ名は 「帰れ、私」

名前の通り、飲み会や外出先で「まだいける」「あと少しだけ」と粘ってしまう自分に対して、帰る時間をしつこく通知してくれるアプリです。

※ 私はアラームをつけても一瞬でスヌーズを止めてしまうタイプなので、「それなら通知が何回も来たらいいのでは?」という安直な発想から作りはじめました。

この記事では、AIを使ったiOS個人開発の記録として、以下の内容を整理します。

  • ✅ Claude Code と ChatGPT を使った開発の進め方
  • ✅ 終電を逃さないアプリ「帰れ、私」で作った機能
  • ✅ 実機iPhoneで動かすまでに詰まったこと
  • ✅ 通知が来ない問題の原因と対応
  • ✅ AIに任せてよかったこと・人間が確認すべきこと

AIを使った個人開発の進め方が、少しでも参考になれば幸いです!

※ これまでの個人開発実績はこちら

2. 作ったもの

今回作ったアプリは、帰宅リミット時刻を設定すると、その時間に向けてローカル通知を送ってくれるiOSアプリです。

主な機能

スクリーンショット 2026-05-31 22.05.08.png

  • 帰宅リミット時刻の設定
  • 設定時刻の保存
  • ローカル通知の予約
  • 通知予定一覧の表示
  • 「出発した!」ボタンによる通知キャンセル
  • しつこさモードの選択
    • やさしめ
    • 標準
    • 鬼しつこい
  • 鬼しつこいモード専用の通知文言
  • 鬼しつこいモード専用のカスタム通知音
  • カスタムアプリアイコン

実機iPhoneでの動作確認も実施しました。
UIは、最終的に夜っぽいネイビー基調のデザインにしました。

3. 開発環境・技術スタック

今回の開発環境・技術スタックは以下の通りです。

  • macOS
  • Xcode
  • SwiftUI
  • UserNotifications
  • UserDefaults
  • Git / GitHub
  • GitHub CLI
  • Claude Code
  • ChatGPT

AIの活用方法としては、以下の通りです。

  • Claude Code:実装、ファイル修正、コード生成
  • ChatGPT:仕様整理、差分レビュー、実装方針の相談、詰まりポイントの切り分け
  • Canva:アプリアイコン作成
  • AI生成音声:鬼しつこいモード用の通知音作成

4. 最初にやったこと

まずGitHub上にリポジトリを作成し、ローカルにcloneしました。

gh repo clone [リポジトリURL]
cd [プロジェクトフォルダ]

その後、Xcodeで新規iOSアプリプロジェクトを作成し、GitHubへpushしました。

git add .
git commit -m "Initial iOS project"
git push origin main

この時点では、ほぼXcodeテンプレートそのままの状態です。

5. Claude Codeを導入する

今回の実装では、Claude Codeを使いました。(Homebrewでインストール)

brew install --cask claude-code

インストール後、バージョンを確認します。

claude --version

その後、プロジェクトディレクトリでClaude Codeを起動しました。

claude

6. MVP要件と実装方針

まず、Claude Codeに現在のプロジェクト構成を見てもらい、MVPとして必要なファイルと実装順序を整理してもらいました。

最初のMVPでは、以下の3ファイルを中心に進める方針にしました。

LastTrainGuardian/
├── ContentView.swift
├── SettingsStore.swift
└── NotificationManager.swift

役割は以下です。

ファイル 役割
ContentView.swift メイン画面
SettingsStore.swift UserDefaultsへの保存・読み込み
NotificationManager.swift 通知許可、通知予約、通知キャンセル

この段階で、MVP要件として以下を整理しました。

  • 帰宅リミット時刻を入力できる
  • 入力した時刻を保存できる
  • 通知をセットできる
  • 複数タイミングで通知を受け取れる
  • 「出発した!」ボタンで通知をキャンセルできる

SettingsStore.swift:設定値を保存する

まず、帰宅リミット時刻を保存するSettingsStore.swiftを作成しました。
UserDefaultsを使い、アプリを閉じても前回設定した時刻を復元できるようにしました。

import Foundation
import Combine

class SettingsStore: ObservableObject {
    private let returnLimitTimeKey = "returnLimitTime"

    @Published var returnLimitTime: Date {
        didSet {
            UserDefaults.standard.set(returnLimitTime.timeIntervalSince1970, forKey: returnLimitTimeKey)
        }
    }

    init() {
        let saved = UserDefaults.standard.double(forKey: returnLimitTimeKey)
        if saved > 0 {
            returnLimitTime = Date(timeIntervalSince1970: saved)
        } else {
            returnLimitTime = Date().addingTimeInterval(3600)
        }
    }
}

@Publishedを使うことで、時刻が変わったときにSwiftUIの画面側へ変更が伝わるようになります。

NotificationManager.swift:ローカル通知を実装する

次に、ローカル通知を管理するNotificationManager.swiftを作成しました。
最初は以下のような通知タイミングにしました。

  • 60分前
  • 30分前
  • 15分前
  • 5分前
  • リミット時刻
content.title = "帰れ、私"
content.body = entry.body
content.sound = .default

ローカル通知にはUNUserNotificationCenterを使いました。
通知予約にはUNCalendarNotificationTriggerを使っています。

ContentView.swift:画面を作る

画面では以下を配置しました。

  • 帰宅リミット時刻のDatePicker
  • 通知をセットボタン
  • 出発した!ボタン
  • 操作結果メッセージ
  • 通知予定一覧

最初はシンプルな白背景の画面でした。
その後、通知予定一覧を追加し、どの時刻にどんな通知が予約されたか確認できるようにしました。

if !scheduledNotifications.isEmpty {
    VStack(alignment: .leading, spacing: 6) {
        Text("通知予定")
        ForEach(scheduledNotifications, id: \.fireDate) { item in
            HStack {
                Text(item.fireDate, format: .dateTime.hour().minute())
                Text(item.body)
            }
        }
    }
}

これにより、通知をセットした直後に「何件予約されたか」「何時に通知が来るか」が分かるようになりました。

7. 実機iPhoneで動かすまでに詰まったこと・工夫したこと

シミュレーターでは比較的すぐ動きましたが、実機iPhoneで動かすまでにはいくつか詰まりました。

実機起動で詰まったこと

Deployment Targetが実機のiOSと合わない

実機iPhoneのiOSは18.6.2でしたが、Xcode側のDeployment Targetが26.4になっていました。そのため、以下のようなエラーが出ました。

iOS 18.6.2 doesn't match app's iOS 26.4 deployment target.

これは、XcodeのMinimum DeploymentsをiOS 18.0に下げることで解決しました。

Developer Modeが無効

次に、Developer Modeが無効というエラーが出ました。

Developer Mode disabled

そのため、iPhone側で以下を設定しました。

設定
↓
プライバシーとセキュリティ
↓
デベロッパモード
↓
ON

Developer App Certificateが信頼されていない

さらに、開発者証明書が信頼されていないというエラーも出ました。

The application could not be launched because the Developer App Certificate is not trusted.

そのため、iPhone側で以下を設定しました。

設定
↓
一般
↓
VPNとデバイス管理
↓
Developer App
↓
信頼

これで実機iPhoneにアプリをインストールできるようになりました。

通知まわりで詰まったこと

通知が来ない

実機で通知が来ない場面もありました。調査すると、主に以下の原因がありました。

  • アプリをフォアグラウンド表示したままだと通知が見えない

iOSでは、アプリが前面にある状態では通知バナーが表示されないことがあります。
今回のアプリは、実際の使い方としては

通知をセット
↓
アプリを閉じる / 端末をロック
↓
通知を受け取る

という想定です。
そのため、テスト時もアプリをバックグラウンドに移動したところ、通知が届くことを確認できました。

DatePickerのDateに古い日付が残る

DatePickerは時・分だけを表示していても、内部的には年月日を含むDateを持っています。

そのため、前日に保存した時刻をそのまま使うと、通知時刻が過去扱いになり、通知が全件スキップされる可能性がありました。

そこで、通知予約時には保存済みDateの「時・分」だけを使い、日付は今日に補正するようにしました。

private func resolvedLimitTime(from returnLimitTime: Date) -> Date {
    let calendar = Calendar.current
    let now = Date()
    let timeParts = calendar.dateComponents([.hour, .minute], from: returnLimitTime)

    var todayComponents = calendar.dateComponents([.year, .month, .day], from: now)
    todayComponents.hour = timeParts.hour
    todayComponents.minute = timeParts.minute
    todayComponents.second = 0

    guard let todayLimit = calendar.date(from: todayComponents) else {
        return returnLimitTime
    }

    if todayLimit <= now {
        return calendar.date(byAdding: .day, value: 1, to: todayLimit) ?? todayLimit
    }
    return todayLimit
}

これにより、保存されたDateの日付が古くても、通知セット時には「今日または明日のその時刻」として扱えるようになりました。

通知許可が非同期だった

最初は以下のように、通知許可リクエスト直後に通知予約していました。

NotificationManager.shared.requestAuthorization()
NotificationManager.shared.scheduleNotifications(...)

しかし、通知許可は非同期です。
そこで、許可結果を受け取ってから通知予約する形に変更しました。

NotificationManager.shared.requestAuthorization { granted in
    DispatchQueue.main.async {
        if granted {
            NotificationManager.shared.scheduleNotifications(...)
            statusMessage = "通知をセットしました"
        } else {
            statusMessage = "通知が許可されていません。設定アプリから通知を許可してください。"
        }
    }
}

この修正で、初回起動時の通知予約が安定しました。

アプリらしさを出すために工夫したこと

鬼しつこいモードの通知文言

鬼しつこいモードでは、通知回数を増やし、文言も強めにしました。
例として、以下のような通知が届きます。

  • 伊能忠敬界隈になるの?歩いて帰る気?
  • タクシー代かかるよ。ほんとにいいの?
  • 終電逃します。今すぐ出てください

スクリーンショット 2026-05-31 21.40.14.png

単なるリマインダーではなく、自分に刺さる言葉で帰宅を促すことで、アプリの個性が出ました。

カスタム通知音を追加する

鬼しつこいモードだけ、専用の通知音を鳴らすようにしました。
まず、短い警告音を作成し、kaere_alert.wavとしてXcodeプロジェクトに追加しました。

Xcodeに追加するときは、以下を確認しました。

Copy items if needed
Add to targets: LastTrainGuardian

通知音の指定は以下です。

content.sound = mode == .intense
    ? UNNotificationSound(named: UNNotificationSoundName("kaere_alert.wav"))
    : .default

これで、やさしめ・標準モードはデフォルト通知音、鬼しつこいモードだけカスタム通知音が鳴るようになりました。
実機でも、鬼しつこいモードでカスタム音が鳴ることを確認しました。

アプリアイコンを作る

アプリアイコンはCanvaで作成しました。
最初は画像生成AIを使おうとしましたが、漢字の「帰」が崩れやすかったため、Canvaのコード生成や手動編集を使いました。

最終的には、以下の要素を入れました。

  • 深いネイビーの背景
  • 大きな白い「帰」
  • 三日月
  • 線路モチーフ

iOSのAppIconとして使うため、最終的に1024×1024pxのPNGにしました。
Xcode上では以下に設定しました。

Assets.xcassets
↓
AppIcon

Gitで小さく区切って進める

今回の開発では、機能ごとに小さくcommitして進めました。

例:
git commit -m "Add settings store"
git commit -m "Add notification manager"
git commit -m "Build main screen"
git commit -m "Add custom app icon"
git commit -m "Show scheduled notification list"
git commit -m "Add notification intensity modes"

AIに実装してもらう場合でも、差分確認と小さなcommitはかなり重要だと感じました。
特に、途中でSettingsStore.swiftの既存処理が消えかけた場面がありました。

AIが提示した差分をそのまま受け入れるのではなく、人間側で

  • 既存処理が消えていないか
  • 保存処理が維持されているか
  • メソッド呼び出しが新しい定義と合っているか
  • UIと実際の通知状態がズレていないか

を確認する必要がありました。

8. AI駆動開発で良かったところ

今回AIを使って良かったのは、個人開発の初速がかなり上がったことです。
特に以下はAIとの相性が良いと感じました。

  • MVP要件の整理
  • ファイル構成の提案
  • SwiftUIの実装補助
  • 通知ロジックの実装
  • エラー原因の切り分け
  • 実機検証時の詰まりポイント整理
  • READMEや開発ログの整理
  • UI改善案の壁打ち

一人で調べながら進めるよりも、かなりテンポよく進めることができました。

9. AIに任せきりにしない方がよかったところ

一方で、AIに任せきりにしない方が良いと感じた点もあります。

特に以下です。

  • 差分レビュー
  • 既存処理が消えていないかの確認
  • Xcode設定
  • 実機iPhoneでの動作確認
    • 通知が実際に届くかどうか
    • 画面上の表示と内部状態が一致しているか

AIはコードを生成してくれますが、実機特有の挙動や、ユーザー体験として自然かどうかは、実際に触って確認する必要があります。

今回も、通知が来ない問題は、実際にシミュレーターや実機で試して初めて見えてきました。

10. 完成したMVP

最終的に、今回のMVPでは以下ができるようになりました。

  • 帰宅リミット時刻を設定できる
  • しつこさモードを選べる
  • モードに応じた通知が予約される
  • 通知予定一覧を表示できる
  • 鬼しつこいモードでは専用文言が出る
  • 鬼しつこいモードではカスタム通知音が鳴る
  • 「出発した!」で通知をキャンセルできる
  • 実機iPhoneで通知が届く
  • Macとの接続を切ってもアプリを使える
  • カスタムアイコンと夜っぽいUIで世界観を出せた

11. まとめ

今回のAI駆動開発をやってみて感じたのは、以下の2点です。

  • AIは単にコードを書く存在ではなく、個人開発の伴走者としてかなり有効
  • 要件定義〜実装・実機確認まで早すぎる
    • これまでの個人開発では、構想からある程度形にするまで半年ほどかかっていたが、今回は、AIに実装を手伝ってもらいながら進めることで、約3時間ほどでMVPとして動く状態まで持っていくことができた

学びを一言でまとめると、

  • AI駆動開発は「全部AIに任せる」ものではなく、AIに実装を進めてもらいながら、人間が目的・仕様・差分・実機挙動を確認していく開発スタイル

だと感じています。

今後は、もう少し実際に使いながら改善点を見つけて、機能やUIを育てていきたいです。
この記事が少しでも参考になったら、いいねやストックしてもらえるととってもうれしいです!

あとがき:

※ 私はアラームをつけても一瞬でスヌーズを止めてしまうタイプなので、「それなら通知が何回も来たらいいのでは?」という安直な発想から作りはじめました。

稼働確認をしていて気づいたのですが、そもそも通知を見ない可能性がある、という根本的な自分のバグがあることに気づきました。

アプリを作ったことで、終電を逃さないためには「通知を増やす」だけではなく、「通知を見てちゃんと動く自分」も必要です。引き続き自身もアップデートしてまいります…!

3
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?