1. はじめに
「最近あんまり個人開発してないなあ」「AI駆動開発やってみようかな」「業務で触っているiOSも個人開発で試してみたい」と思い、AIを使ったiOS個人開発に挑戦してみました。
作ったのは、終電を逃さないために帰る時間を通知してくれるiOSアプリです。
アプリ名は 「帰れ、私」。
名前の通り、飲み会や外出先で「まだいける」「あと少しだけ」と粘ってしまう自分に対して、帰る時間をしつこく通知してくれるアプリです。
※ 私はアラームをつけても一瞬でスヌーズを止めてしまうタイプなので、「それなら通知が何回も来たらいいのでは?」という安直な発想から作りはじめました。
この記事では、AIを使ったiOS個人開発の記録として、以下の内容を整理します。
- ✅ Claude Code と ChatGPT を使った開発の進め方
- ✅ 終電を逃さないアプリ「帰れ、私」で作った機能
- ✅ 実機iPhoneで動かすまでに詰まったこと
- ✅ 通知が来ない問題の原因と対応
- ✅ AIに任せてよかったこと・人間が確認すべきこと
AIを使った個人開発の進め方が、少しでも参考になれば幸いです!
※ これまでの個人開発実績はこちら
2. 作ったもの
今回作ったアプリは、帰宅リミット時刻を設定すると、その時間に向けてローカル通知を送ってくれるiOSアプリです。
主な機能
- 帰宅リミット時刻の設定
- 設定時刻の保存
- ローカル通知の予約
- 通知予定一覧の表示
- 「出発した!」ボタンによる通知キャンセル
- しつこさモードの選択
- やさしめ
- 標準
- 鬼しつこい
- 鬼しつこいモード専用の通知文言
- 鬼しつこいモード専用のカスタム通知音
- カスタムアプリアイコン
実機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 = "通知が許可されていません。設定アプリから通知を許可してください。"
}
}
}
この修正で、初回起動時の通知予約が安定しました。
アプリらしさを出すために工夫したこと
鬼しつこいモードの通知文言
鬼しつこいモードでは、通知回数を増やし、文言も強めにしました。
例として、以下のような通知が届きます。
- 伊能忠敬界隈になるの?歩いて帰る気?
- タクシー代かかるよ。ほんとにいいの?
- 終電逃します。今すぐ出てください
単なるリマインダーではなく、自分に刺さる言葉で帰宅を促すことで、アプリの個性が出ました。
カスタム通知音を追加する
鬼しつこいモードだけ、専用の通知音を鳴らすようにしました。
まず、短い警告音を作成し、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を育てていきたいです。
この記事が少しでも参考になったら、いいねやストックしてもらえるととってもうれしいです!
あとがき:
※ 私はアラームをつけても一瞬でスヌーズを止めてしまうタイプなので、「それなら通知が何回も来たらいいのでは?」という安直な発想から作りはじめました。
稼働確認をしていて気づいたのですが、そもそも通知を見ない可能性がある、という根本的な自分のバグがあることに気づきました。
アプリを作ったことで、終電を逃さないためには「通知を増やす」だけではなく、「通知を見てちゃんと動く自分」も必要です。引き続き自身もアップデートしてまいります…!



