Rails 8로 만든 긴급 신고 웹앱 바로신고를 Hotwire Native으로 iOS 앱으로 감싸서 TestFlight에 올리기까지의 과정을 정리합니다.
기술 스택
- Backend: Rails 8 + Turbo
- iOS: Hotwire Native 1.2.2 + XcodeGen
- 빌드: Makefile 자동화
프로젝트 구조
ios/
├── project.yml # XcodeGen 설정
├── ExportOptions.plist # App Store 내보내기
├── Makefile # 빌드 자동화
└── BaroSingo/
├── AppDelegate.swift
├── SceneController.swift
├── AppTab.swift
├── Bridge/
│ ├── FormComponent.swift
│ ├── HapticComponent.swift
│ └── ShareComponent.swift
└── Resources/
├── Assets.xcassets/
└── path-configuration.json
삽질 1: Hotwire Native API 변경
Hotwire.config.userAgent — 읽기 전용
// ❌ 컴파일 에러: 'userAgent' is a get-only property
Hotwire.config.userAgent = "BaroSingo iOS"
// ✅ 해결: makeCustomWebView 사용
Hotwire.config.makeCustomWebView = { configuration in
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.customUserAgent = "BaroSingo iOS/1.0 Turbo Native"
return webView
}
Hotwire.loadPathConfiguration — 존재하지 않는 API
// ❌ 컴파일 에러: no member 'loadPathConfiguration'
Hotwire.loadPathConfiguration(from: [source])
// ✅ 해결: config.pathConfiguration.sources 직접 설정
Hotwire.config.pathConfiguration.sources = [
.file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!),
.server(URL(string: "\(baseURL)/api/hotwire/path-configuration")!)
]
Bridge Component에서 ViewController 접근
// ❌ 컴파일 에러: optional type must be unwrapped
delegate.webView?.findViewController()
// ✅ 해결: delegate?.destination 사용
guard let viewController = delegate?.destination as? UIViewController else { return }
교훈: Hotwire Native는 버전별 API 변경이 잦다. 공식 소스코드와 실제 동작하는 프로젝트를 참고하는 게 가장 확실하다.
삽질 2: TestFlight 업로드 — 4개 에러 한번에
IPA를 xcrun altool로 업로드했더니 4개 validation 에러가 한꺼번에 터졌다.
에러 1, 2: AppIcon 누락
Missing required icon file. The bundle does not contain an app icon
for iPhone of exactly '120x120' pixels
for iPad of exactly '152x152' pixels
원인: Asset Catalog에 AppIcon.appiconset이 없었다.
해결: 1024px 원본 아이콘 생성 후 sips로 13개 사이즈 일괄 생성.
for size in 20 29 40 58 60 76 80 87 120 152 167 180 1024; do
sips -z $size $size appicon_1024.png --out "icon_${size}.png"
done
에러 3: iPad 방향 누락
you need to include all of the orientations to support iPad multitasking
원인: project.yml에서 iPad 방향에 UIInterfaceOrientationPortraitUpsideDown이 빠져있었다.
# ❌ 부족
UISupportedInterfaceOrientations~ipad:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
# ✅ 수정 — PortraitUpsideDown 추가
UISupportedInterfaceOrientations~ipad:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
에러 4: CFBundleIconName 누락
A value for the Info.plist key 'CFBundleIconName' is missing
해결: project.yml Info.plist properties에 추가.
CFBundleIconName: AppIcon
교훈: altool 업로드 전에 xcrun altool --validate-app으로 먼저 검증하면 업로드 시간을 아낄 수 있다.
삽질 3: ASC에서 앱 생성
ERROR: Cannot determine the Apple ID from Bundle ID
App Store Connect에 앱이 등록되지 않은 상태에서 업로드하면 발생한다. fastlane produce는 비대화형 셸에서 동작하지 않으므로, ASC REST API로 Bundle ID를 등록하거나 웹에서 직접 생성해야 한다.
스크린샷 사이즈
ASC가 요구하는 정확한 픽셀 사이즈:
| 디스플레이 | 사이즈 |
|---|---|
| 6.9" | 1320×2868 또는 1290×2796 |
| 6.5" | 1242×2688 또는 1284×2778 |
AI로 생성한 이미지는 정확한 사이즈가 나오지 않으므로, 비율이 같은 큰 이미지를 생성한 뒤 sips -z로 정확한 사이즈로 리사이즈하는 방식이 효과적이다.
최종 빌드 명령어
# 1. Xcode 프로젝트 생성
xcodegen generate
# 2. Release 아카이브
xcodebuild -project BaroSingo.xcodeproj \
-scheme BaroSingo -configuration Release \
-destination 'generic/platform=iOS' \
-archivePath build/BaroSingo.xcarchive \
-allowProvisioningUpdates archive
# 3. IPA 내보내기
xcodebuild -exportArchive \
-archivePath build/BaroSingo.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath build/ipa
# 4. TestFlight 업로드
xcrun altool --upload-app \
-f build/ipa/BaroSingo.ipa -t ios \
--apiKey $ASC_API_KEY --apiIssuer $ASC_ISSUER
삽질 4: 법적 검토 — 긴급신고 앱, 개인이 만들어도 되나?
앱 심사 제출 전 법적 리스크를 검토했다.
문제 없는 부분
- 문자 신고 자체는 합법: 112, 119, 120 모두 공식적으로 문자 신고를 지원
- 민간 앱 개발 제한 없음: 정부의 ‘긴급신고 바로앱’이 있지만, 민간이 신고 편의 앱을 만드는 것을 금지하는 법은 없음
- 120 다산콜센터:
02-120으로 문자 전송 가능, 사진 첨부도 가능
주의할 법률
| 법률 | 내용 | 벌칙 |
|---|---|---|
| 형법 제137조 | 위계에 의한 공무집행방해 (허위신고) | 5년 이하 징역 |
| 112신고법 제18조 | 거짓 112 신고 | 500만원 이하 과태료 |
| 112신고법 | 신고자 정보 목적외 이용 | 5년 이하 징역 / 5천만원 이하 벌금 |
앱에 반영한 대응
- 허위신고 경고: 신고 버튼 아래에 형사처벌 경고 문구 추가
- 면책 조항: “정부 공식 앱이 아니며, 문자 신고 작성을 돕는 편의 도구” 명시
- 긴급 전화 권고: 112/119 선택 시 “긴급 상황에는 전화가 더 빠릅니다” 안내 + 전화 연결 버튼
- 개인정보 최소화: 회원가입 없이 사용, 위치정보는 전송 후 즉시 파기
- 개인정보 처리방침: 별도 페이지로 작성하여 ASC에 등록
Apple 심사 대응
심사 메모에 다음을 명시:
“이 앱은 사용자가 문자 신고 내용을 작성하는 것을 돕는 편의 도구입니다. 실제 문자 전송은 iOS 기본 메시지 앱을 통해 이루어지며, 앱 자체가 직접 긴급 기관에 데이터를 전송하지 않습니다.”
이렇게 하면 Apple의 “긴급 서비스 관련 앱” 심사 기준도 통과할 수 있다.
결론
Hotwire Native는 Rails 웹앱을 빠르게 네이티브 앱으로 감쌀 수 있는 훌륭한 도구다. 다만 API 변경이 잦고, TestFlight 업로드 시 Asset Catalog과 Info.plist 설정을 꼼꼼히 챙겨야 한다. 법적 리스크도 사전에 검토해서 면책 조항과 경고 문구를 넣어두면 심사에서 리젝 확률을 줄일 수 있다. 한번 패턴을 잡아두면 다음 프로젝트부터는 30분 안에 끝낼 수 있다.
💬 댓글
비밀번호를 기억해두면 나중에 내 댓글을 삭제할 수 있어요.