Rails 앱을 Hotwire Native로 iOS 앱 만들어 TestFlight 올리기까지의 삽질 기록

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 변경이 잦다. 공식 소스코드와 실제 동작하는 프로젝트를 참고하는 게 가장 확실하다. ...

2026-03-07 · 4분 소요 · Seunghan
Hotwire Native Ios Tab Bar Patterns

Hotwire Native iOS 탭바 앱 구축 — HotwireTabBarController 적용기와 삽질 모음

Rails 앱을 Hotwire Native로 래핑할 때 단일 Navigator 대신 HotwireTabBarController 패턴으로 전환하면서 생긴 문제들을 정리한다. 시뮬레이터에서는 안 보이던 버그가 TestFlight에서 터지고, 로컬 개발 환경 설정이 꼬이는 등 여러 지점에서 시간을 날렸다. 1. HotwireTabBarController 기본 구조 단일 Navigator 대신 탭별로 독립적인 Navigator와 WKWebView를 갖는 구조다. // AppTab.swift enum AppTab: String, CaseIterable { case home, ai, request var systemImage: String { switch self { case .home: return "house" case .ai: return "message" case .request: return "checkmark.circle" } } var selectedSystemImage: String { switch self { case .home: return "house.fill" case .ai: return "message.fill" case .request: return "checkmark.circle.fill" } } var url: URL { let base = AppDelegate.baseURL switch self { case .home: return base.appendingPathComponent("dashboard") case .ai: return base.appendingPathComponent("conversations") case .request: return base.appendingPathComponent("service_requests") } } var hotwireTab: HotwireTab { HotwireTab( title: "", image: UIImage(systemName: systemImage)!, selectedImage: UIImage(systemName: selectedSystemImage)!, url: url ) } } // SceneController.swift 핵심 부분 private lazy var tabBarController: HotwireTabBarController = { let controller = HotwireTabBarController(navigatorDelegate: self) controller.load(AppTab.allCases.map(\.hotwireTab)) // 탭 아이콘만 표시, 텍스트 제거 controller.viewControllers?.forEach { vc in vc.tabBarItem.title = nil vc.tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) (vc as? UINavigationController)?.delegate = self } return controller }() 탭 제목을 없애고 아이콘만 남기려면 tabBarItem.title = nil과 imageInsets 조정이 같이 필요하다. title만 nil로 하면 아이콘 위치가 내려가지 않아서 어색하게 보인다. ...

2025-12-26 · 5분 소요 · Seunghan
Ios Testflight 4 Validation Errors Xcodegen

iOS TestFlight 업로드 4가지 검증 오류 — xcodegen 프로젝트 완전 해결

xcrun altool --upload-app 성공 직후 App Store Connect에서 이메일이 왔다. ITMS-90704: Missing Icon - The bundle does not contain an app icon for iPhone of exactly '120x120' pixels... ITMS-90704: Missing Icon - The bundle does not contain an app icon for iPad of exactly '152x152' pixels... ITMS-90905: Missing Info.plist value - CFBundleIconName ITMS-90474: The orientations UIInterfaceOrientationPortrait were provided... you need to include all orientations to support iPad multitasking 4가지 오류가 한꺼번에. 하나씩 해결한 기록이다. ...

2025-12-09 · 3분 소요 · Seunghan
Flutter Ipa No Codesign Api Key Testflight

flutter build ipa 실패 원인과 --no-codesign + API Key로 TestFlight 배포하기

Flutter iOS 앱을 여러 Apple 계정으로 관리하다 보면 한 프로젝트에서는 make testflight가 잘 되는데 다른 프로젝트에서는 동일한 Makefile이 실패하는 상황이 생긴다. 오늘 겪은 케이스를 정리한다. 증상 ❌ Error (Xcode): No signing certificate "iOS Development" found: No "iOS Development" signing certificate matching team ID "XXXXXXXX" with a private key was found. flutter build ipa 실행 시 위 오류로 실패한다. Distribution 인증서는 키체인에 있는데 Development 인증서가 없다는 메시지다. 원인: flutter build ipa 내부에서 일어나는 일 flutter build ipa는 내부적으로 다음 순서로 동작한다. ...

2025-11-04 · 4분 소요 · Seunghan
Flutter Ios Build Dark Mode Logout Debugging

Flutter iOS 배포 삽질 모음: 빌드 오류 5종 + 다크모드 하드코딩 + 로그아웃 버그

빌드를 올리려는데 한꺼번에 여러 문제가 터졌다. 코드 생성기가 실패하고, 없어진 파일이 있고, 빌드 번호 규칙을 몰라서 거절당하고, UI는 다크모드가 하드코딩되어 있고, 로그아웃은 토큰을 안 지웠다. 하나씩 정리한다. 1. Retrofit 옵션 파라미터 문법 오류 → .g.dart 생성 실패 증상 dart run build_runner build 실행 시 일부 API 서비스 파일에서: Expected to find ')' 원인 Retrofit의 추상 메서드에서 옵션 파라미터({}) 위치를 잘못 씀. // ❌ 잘못된 문법 — 닫는 중괄호 뒤에 쉼표 Future<Response> getItems( @Path('id') String id, {@Query('type') String? type}, // ← 이렇게 쓰면 안 됨 ); // ✅ 올바른 문법 — 포지셔널 파라미터 뒤에 { 바로 열기 Future<Response> getItems( @Path('id') String id, { @Query('type') String? type, }); Dart 문법에서 옵션 파라미터는 마지막 포지셔널 파라미터 바로 뒤에 {를 열어야 한다. },로 닫은 뒤 쉼표를 찍으면 파서가 다음 인자로 인식하려다 실패한다. ...

2025-11-01 · 5분 소요 · Seunghan
Flutter Sso Localhost Rails Uninitialized Constant Debug

Flutter SSO 로그인 실패 + Rails 서버 크래시 동시 디버깅 기록

TestFlight에서 소셜 로그인(Apple, Google)이 전부 실패하는 버그를 잡다가 서버도 크래시되고 있다는 걸 같이 발견했다. 각각 원인이 달랐고 둘 다 잡아야 앱이 정상 동작했다. 증상 실기기(TestFlight)에서 Apple 로그인, Google 로그인 버튼을 누르면 다음 에러가 표시됐다: Apple 로그인 실패: DioException [connection error]: The connection errored: Connection refused This indicates an error which most likely cannot be solved by the library. Error: SocketException: Connection refused (OS Error: Connection refused, errno = 61), address = localhost, port = 56837 Google 로그인 실패: DioException [connection error]: ... address = localhost, port = 56839 두 가지가 이상했다: ...

2025-10-01 · 3분 소요 · Seunghan
Sign In With Apple Testflight Entitlement Errors

Sign In with Apple 추가 후 TestFlight 빌드 에러 2연타 해결

Flutter iOS 앱에 Sign In with Apple을 추가하면서 TestFlight 빌드까지 두 가지 에러를 연달아 만났다. 각각 원인이 달라서 정리해둔다. 배경 Sign In with Apple을 활성화하려면 코드만 짜면 되는 게 아니다. Apple Developer Portal에서 App ID에 capability를 추가하고, 프로비저닝 프로파일을 반드시 재생성해야 한다. 기존 프로파일은 Sign In with Apple entitlement를 포함하지 않으므로 그냥 빌드하면 실패한다. 순서대로 하면: developer.apple.com → Identifiers → App ID 선택 Sign In with Apple 체크 → Edit → “Enable as a primary App ID” 선택 → Save Profiles → 기존 App Store 프로파일 Edit → Generate → Download 다운받은 .mobileprovision 파일을 ~/Library/MobileDevice/Provisioning Profiles/ 에 복사 여기까지 하면 준비 완료처럼 보이는데, 막상 flutter build ipa 를 돌리면 에러가 나온다. ...

2025-09-10 · 2분 소요 · Seunghan
Ios Sso Entitlements Testflight Errors

iOS TestFlight 배포 삽질 모음: SSO 에러부터 entitlements mismatch까지

Flutter 앱 여러 개를 TestFlight에 올리면서 반복적으로 마주친 에러들을 정리했다. 1. Apple Sign-In 에러 1000 SignInWithAppleAuthorizationException(AuthorizationErrorCode.unknown, The operation couldn't be completed. (com.apple.AuthenticationServices.AuthorizationError error 1000.)) 원인 Runner.entitlements에 Sign in with Apple capability가 없어서 발생한다. 해결 두 곳 모두 설정해야 한다. ① ios/Runner/Runner.entitlements <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> ② Apple Developer Console developer.apple.com → Identifiers → 앱 Bundle ID 선택 → Sign in with Apple 체크 → Save 프로비저닝 프로파일이 이미 있다면 재생성이 필요하다. ...

2025-08-30 · 4분 소요 · Seunghan
Ios Itms 90683 Permission Strings

App Store Connect ITMS-90683: Info.plist 권한 purpose string 누락 오류 해결

TestFlight에 IPA를 업로드하고 몇 분 후 App Store Connect에서 메일이 온다. ITMS-90683: Missing purpose string in Info.plist The app's Info.plist file is missing a required purpose string for one or more of the following API categories: NSPhotoLibraryUsageDescription 업로드 자체는 성공했지만 앱 배포 전 Apple이 자동으로 검사해서 이 메일을 보낸다. 수정하지 않으면 App Store 심사 제출 시 거절된다. 왜 이 오류가 발생하는가 iOS는 카메라, 사진 라이브러리, 마이크 등 민감한 API에 접근할 때 사용자에게 권한 팝업을 보여준다. 이 팝업에 표시되는 설명 문구가 Info.plist에 없으면 Apple이 오류로 처리한다. ...

2025-08-27 · 3분 소요 · Seunghan
Flutter Testflight Makefile Automation

Flutter TestFlight 업로드 자동화 - Makefile로 한 줄에 끝내기

Flutter iOS 앱을 TestFlight에 올리는 과정은 단계가 많다. flutter build ipa, Xcode 아카이브, altool 업로드… Makefile로 묶어두면 make testflight 한 줄로 끝난다. 최종 Makefile .PHONY: build-ipa testflight clean EXPORT_OPTIONS = ios/ExportOptions.plist API_KEY = YOUR_API_KEY_ID API_ISSUER = YOUR_ISSUER_ID IPA_DIR = build/ios/ipa IPA_FILE = $(IPA_DIR)/Talkk.ipa # ← 앱 Display Name과 일치해야 함 build-ipa: flutter build ipa --release --export-options-plist=$(EXPORT_OPTIONS) testflight: build-ipa @echo "📦 TestFlight 업로드 중..." xcrun altool --upload-app \ --type ios \ --file "$(IPA_FILE)" \ --apiKey $(API_KEY) \ --apiIssuer $(API_ISSUER) \ --verbose @echo "✅ TestFlight 업로드 완료!" clean: flutter clean && flutter pub get ExportOptions.plist 설정 flutter build ipa는 내부적으로 Xcode 아카이브 후 IPA를 만든다. 이 과정에서 서명 방식, 팀 ID, App Store Connect API 키 등을 지정하는 파일이 필요하다. ...

2025-08-20 · 3분 소요 · Seunghan
개인정보처리방침 문의