
【개인 개발】 Claude Code와 ChatGPT로 iOS 개인 개발해 보기: 막차를 놓치지 않는 앱 「돌아가, 나」를 만들기까지
요약
Claude Code와 ChatGPT를 활용하여 iOS 앱 '돌아가, 나'를 개발한 개인 프로젝트 기록입니다. AI를 통해 구현, 코드 생성, 사양 정리 및 문제 해결 과정을 진행하며 AI 주도 개발(AI-driven development)의 실무 적용 사례를 보여줍니다.
핵심 포인트
- Claude Code를 활용한 파일 수정 및 코드 생성 프로세스
- ChatGPT를 이용한 사양 정리 및 구현 방침 상담
- SwiftUI와 UserNotifications 기반의 iOS 앱 구현
- AI 도구를 활용한 개발 효율성 및 문제 해결 경험
「최근 개인 개발을 별로 안 했네」 「AI 주도 개발 (AI-driven development)을 해볼까」 「업무에서 다루고 있는 iOS도 개인 개발로 시험해 보고 싶다」라고 생각하여, AI를 사용한 iOS 개인 개발에 도전해 보았습니다.
만든 것은, 막차를 놓치지 않도록 귀가 시간을 알려주는 iOS 앱입니다.
앱 이름은 「돌아가, 나 (帰れ、私)」.
이름 그대로, 술자리나 외출지에서 「아직 더 놀 수 있어」 「조금만 더」라며 버티는 자신을 향해, 귀가 시간을 끈질기게 알려주는 앱입니다.
※ 저는 알람을 맞춰도 순식간에 스누즈 (Snooze)를 꺼버리는 타입이라, 「그렇다면 알림이 여러 번 오면 되지 않을까?」라는 단순한 발상에서 만들기 시작했습니다.
이 기사에서는 AI를 사용한 iOS 개인 개발의 기록으로서, 다음과 같은 내용을 정리합니다.
- ✅ Claude Code와 ChatGPT를 사용한 개발 진행 방식
- ✅ 막차를 놓치지 않는 앱 「돌아가, 나」로 만든 기능
- ✅ 실제 iPhone 기기에서 구동하기까지 막혔던 부분
- ✅ 알림이 오지 않는 문제의 원인과 대응
- ✅ AI에게 맡겨서 좋았던 점 · 인간이 확인해야 할 점
AI를 사용한 개인 개발 진행 방식이 조금이라도 참고가 된다면 좋겠습니다!
※ 지금까지의 개인 개발 실적은 이쪽
이번에 만든 앱은 귀가 제한 시간을 설정하면, 그 시간을 향해 로컬 알림 (Local Notification)을 보내주는 iOS 앱입니다.
-
귀가 제한 시간 설정
-
설정 시간 저장
-
로컬 알림 예약
-
알림 예정 목록 표시
-
「출발했다!」 버튼을 통한 알림 취소
-
끈질김 모드 선택
- 부드럽게
- 표준
- 귀신같이 끈질기게
-
귀신같이 끈질기게 모드 전용 알림 문구
-
귀신같이 끈질기게 모드 전용 커스텀 알림음
-
커스텀 앱 아이콘
실제 iPhone 기기에서의 동작 확인도 실시했습니다.
UI는 최종적으로 밤 느낌이 나는 네이비 기조의 디자인으로 했습니다.
이번 개발 환경 · 기술 스택은 다음과 같습니다.
- macOS
- Xcode
- SwiftUI
- UserNotifications
- UserDefaults
- Git / GitHub
- GitHub CLI
- Claude Code
- ChatGPT
AI 활용 방법은 다음과 같습니다.
- Claude Code: 구현, 파일 수정, 코드 생성
- ChatGPT: 사양 정리, 차이점 리뷰 (Diff review), 구현 방침 상담, 막히는 포인트의 분류
- Canva: 앱 아이콘 제작
- AI 생성 음성: 귀신같이 끈질기게 모드용 알림음 제작
먼저 GitHub 상에 리포지토리 (Repository)를 생성하고, 로컬에 clone 했습니다.
gh repo clone [리포지토리 URL]
cd [프로젝트 폴더]
그 후, Xcode에서 신규 iOS 앱 프로젝트를 생성하고, GitHub로 push 했습니다.
git add .
git commit -m "Initial iOS project"
git push origin main
이 시점에서는 거의 Xcode 템플릿 그대로인 상태입니다.
이번 구현에서는 Claude Code를 사용했습니다. (Homebrew로 설치)
brew install --cask claude-code
설치 후, 버전을 확인합니다.
claude --version
그 후, 프로젝트 디렉토리에서 Claude Code를 실행했습니다.
claude
먼저 Claude Code에게 현재 프로젝트 구성을 확인하게 하고, MVP (Minimum Viable Product)로서 필요한 파일과 구현 순서를 정리하도록 했습니다.
첫 번째 MVP에서는 다음 3개 파일을 중심으로 진행하는 방침으로 정했습니다.
LastTrainGuardian/
├── ContentView.swift
├── SettingsStore.swift
...
역할은 다음과 같습니다.
| 파일 | 역할 |
|---|---|
| ContentView.swift | 메인 화면 |
| ... |
이 단계에서 MVP 요구사항으로서 다음을 정리했습니다.
- 귀가 제한 시간을 입력할 수 있다
- 입력한 시간을 저장할 수 있다
- 알림을 설정할 수 있다
- 여러 타이밍에 알림을 받을 수 있다
- 「출발했다!」 버튼으로 알림을 취소할 수 있다
먼저 귀가 제한 시간을 저장하는 SettingsStore.swift를 작성했습니다.
UserDefaults를 사용하여 앱을 닫아도 이전에 설정한 시간을 복원할 수 있도록 했습니다.
import Foundation
import Combine
class SettingsStore: ObservableObject {
...
@Published를 사용함으로써, 시간이 변경되었을 때 SwiftUI 화면 측으로 변경 사항이 전달되도록 했습니다.
다음으로, 로컬 알림 (Local Notification)을 관리하는 NotificationManager.swift를 작성했습니다.
처음에는 다음과 같은 알림 타이밍으로 설정했습니다.
- 60분 전
- 30분 전
- 15분 전
- 5분 전
- 리미트(Limit) 시각
content.title = "帰れ、私"
content.body = entry.body
content.sound = .default
로컬 알림에는 UNUserNotificationCenter를 사용했습니다.
알림 예약에는 UNCalendarNotificationTrigger를 사용하고 있습니다.
화면에는 다음을 배치했습니다.
- 귀가 리미트 시각의 DatePicker
- 알림 설정 버튼
- 출발했다! 버튼
- 조작 결과 메시지
- 알림 예정 목록
처음에는 심플한 흰색 배경의 화면이었습니다.
그 후, 알림 예정 목록을 추가하여 어떤 시각에 어떤 알림이 예약되었는지 확인할 수 있도록 했습니다.
if !scheduledNotifications.isEmpty {
VStack(alignment: .leading, spacing: 6) {
Text("通知予定")
...
이를 통해 알림을 설정한 직후에 "몇 건이 예약되었는지", "몇 시에 알림이 오는지"를 알 수 있게 되었습니다.
시뮬레이터에서는 비교적 금방 작동했지만, 실제 iPhone 기기에서 구동하기까지는 몇 가지 막히는 부분이 있었습니다.
실제 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 disabled
따라서 iPhone 측에서 다음과 같이 설정했습니다.
설정
↓
개인정보 보호 및 보안
...
나아가, 개발자 인증서 (Developer Certificate)가 신뢰되지 않는다는 에러도 발생했습니다.
The application could not be launched because the Developer App Certificate is not trusted.
따라서 iPhone 측에서 다음과 같이 설정했습니다.
설정
↓
일반
...
이로써 실제 iPhone에 앱을 설치할 수 있게 되었습니다.
실제 기기에서 알림이 오지 않는 경우도 있었습니다. 조사해 보니 주로 다음과 같은 원인이 있었습니다.
앱을 포그라운드 (Foreground) 상태로 유지하면 알림이 보이지 않음
iOS에서는 앱이 전면에 있는 상태에서는 알림 배너가 표시되지 않을 수 있습니다.
이번 앱의 실제 사용 시나리오는 다음과 같습니다.
알림 설정
↓
앱을 닫음 / 단말기 잠금
...
따라서 테스트 시에도 앱을 백그라운드 (Background)로 이동시키자 알림이 도착하는 것을 확인할 수 있었습니다.
DatePicker는 시·분만 표시하고 있더라도, 내부적으로는 년·월·일을 포함하는 Date를 가지고 있습니다.
그렇기 때문에 전날 저장한 시각을 그대로 사용하면, 알림 시각이 과거로 취급되어 알림이 전부 스킵될 가능성이 있었습니다.
그래서 알림 예약 시에는 저장된 Date의 "시·분"만 사용하고, 날짜는 오늘로 보정하도록 했습니다.
private func resolvedLimitTime(from returnLimitTime: Date) -> Date {
let calendar = Calendar.current
let now = Date()
...
이를 통해 저장된 Date의 날짜가 오래되었더라도, 알림 설정 시에는 "오늘 또는 내일의 해당 시각"으로 취급할 수 있게 되었습니다.
처음에는 다음과 같이, 알림 권한 요청 직후에 알림을 예약했습니다.
NotificationManager.shared.requestAuthorization()
NotificationManager.shared.scheduleNotifications(...)
하지만, 알림 권한 요청은 비동기 (Asynchronous) 방식입니다.
그래서 권한 결과를 받은 후에 알림을 예약하는 형태로 변경했습니다.
NotificationManager.shared.requestAuthorization { granted in
DispatchQueue.main.async {
if granted {
...
이 수정을 통해 첫 실행 시의 알림 예약이 안정화되었습니다.
'귀신같이 집요한 모드'에서는 알림 횟수를 늘리고, 문구도 더 강하게 설정했습니다.
예를 들어, 다음과 같은 알림이 도착합니다.
- 이노 타다타카(伊能忠敬) 급이 되려는 거야? 걸어서 갈 생각이야?
- 택시비 나와. 정말 괜찮겠어?
- 막차 놓쳐요. 지금 바로 나가세요
단순한 리마인더 (Reminder)가 아니라, 자신에게 꽂히는 말로 귀가를 재촉함으로써 앱의 개성이 살아났습니다.
'귀신같이 집요한 모드'에서만 전용 알림음이 울리도록 설정했습니다.
먼저 짧은 경고음을 제작하여 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
이번 개발에서는 기능별로 작게 커밋 (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에게 구현을 맡기는 경우에도 차이점 확인 (Diff check)과 작은 커밋은 상당히 중요하다는 것을 느꼈습니다.
특히 중간에 SettingsStore.swift의 기존 로직이 사라질 뻔한 상황이 있었습니다.
AI가 제시한 차이점을 그대로 받아들이는 것이 아니라, 인간 측에서 다음과 같은 사항을 확인해야 했습니다.
- 기존 로직이 사라지지 않았는가
- 저장 로직이 유지되고 있는가
- 메서드 호출이 새로운 정의와 일치하는가
- UI와 실제 알림 상태가 어긋나지 않았는가
이번에 AI를 사용해서 좋았던 점은 개인 개발의 초속 (Velocity)이 상당히 빨라졌다는 것입니다.
특히 다음과 같은 부분은 AI와 궁합이 좋다고 느꼈습니다.
- MVP 요건 정리
- 파일 구성 제안
- SwiftUI 구현 보조
- 알림 로직 구현
- 에러 원인 분류
- 실기기 검증 시 막히는 포인트 정리
- README 및 개발 로그 정리
- UI 개선안 브레인스토밍 (Wall-hitting)
혼자서 조사하며 진행하는 것보다 훨씬 템포 있게 진행할 수 있었습니다.
반면, AI에게 전적으로 맡기지 않는 것이 좋다고 느낀 점도 있습니다.
특히 다음과 같습니다.
- 차이점 리뷰 (Diff review)
- 기존 로직이 사라지지 않았는지 확인
- Xcode 설정
- 실기기 iPhone에서의 동작 확인
- 알림이 실제로 도착하는지 여부
- 화면상의 표시와 내부 상태가 일치하는지 여부
AI는 코드를 생성해 주지만, 실기기 특유의 동작이나 사용자 경험(UX)으로서 자연스러운지는 실제로 만져보며 확인할 필요가 있습니다.
이번에도 알림이 오지 않는 문제는 실제로 시뮬레이터나 실기기에서 테스트해 보고 나서야 비로소 발견할 수 있었습니다.
최종적으로 이번 MVP(Minimum Viable Product, 최소 기능 제품)에서는 다음과 같은 기능이 가능해졌습니다.
- 귀가 제한 시간을 설정할 수 있음
- 집요함 모드를 선택할 수 있음
- 모드에 따른 알림이 예약됨
- 알림 예정 목록을 표시할 수 있음
- '귀신같이 집요한 모드'에서는 전용 문구가 나옴
- '귀신같이 집요한 모드'에서는 커스텀 알림음이 울림
- "출발했다!"를 통해 알림을 취소할 수 있음
- 실기기 iPhone에서 알림이 도착함
- Mac과의 연결을 끊어도 앱을 사용할 수 있음
- 커스텀 아이콘과 밤 분위기의 UI로 세계관을 구현함
이번 AI 주도 개발(AI-driven development)을 해보며 느낀 점은 다음 두 가지입니다.
AI는 단순히 코드를 쓰는 존재가 아니라, 개인 개발의 동반자로서 매우 유효하다
요건 정의부터 구현, 실기기 확인까지 속도가 너무 빠르다 - 기존의 개인 개발에서는 구상부터 어느 정도 형태를 갖추기까지 반년 정도 걸렸으나, 이번에는 AI의 구현 도움을 받으며 진행한 결과 약 3시간 만에 MVP로서 동작하는 상태까지 만들 수 있었습니다.
배운 점을 한마디로 요약하자면,
AI 주도 개발은 "전부 AI에게 맡기는 것"이 아니라, AI에게 구현을 진행하게 하면서 인간이 목적·사양·차이점·실기기 동작을 확인해 나가는 개발 스타일
이라고 느낍니다.
앞으로는 조금 더 실제로 사용하면서 개선점을 찾아 기능과 UI를 키워나가고 싶습니다.
이 기사가 조금이라도 도움이 되었다면, '좋아요'나 '저장(Stock)'을 해주시면 정말 기쁘겠습니다!
※ 저는 알람을 맞춰도 순식간에 스누즈(Snooze)를 꺼버리는 타입이라, "그렇다면 알림이 여러 번 오면 되지 않을까?"라는 단순한 발상에서 만들기 시작했습니다.
작동 확인을 하며 깨달은 점인데, 애초에 알림을 보지 않을 가능성이 있다는 근본적인 저 자신의 버그가 있다는 것을 깨달았습니다.
앱을 만든 결과, 막차를 놓치지 않기 위해서는 "알림을 늘리는 것"뿐만 아니라, "알림을 보고 제대로 움직이는 자신"도 필요합니다. 저 자신도 계속해서 업데이트해 나가겠습니다...!
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기