Flutter 튜토리얼 57편: iOS 플랫폼 통합
요약
핵심 요지
- 문제 정의: Flutter 앱이 iOS 플랫폼에서 네이티브처럼 동작하려면 Xcode 설정과 iOS 특화 기능 통합이 필요하다.
- 핵심 주장: Xcode 설정, 런치 스크린, Platform Views1, iOS 디버깅으로 네이티브 수준의 앱을 만들 수 있다.
- 주요 근거: macOS와 Xcode를 통해 시뮬레이터와 실제 기기에서 테스트할 수 있다.
- 실무 기준: 런치 스크린은 Storyboard로 구성하고, Platform Views로 네이티브 UIView를 임베드한다.
- 한계: iOS 개발은 macOS에서만 가능하며, Apple Developer 계정이 필요하다.
문서가 설명하는 범위
- iOS 개발 환경 설정
- 런치 스크린 구현
- Platform Views로 네이티브 뷰 임베드
- iOS 특화 디버깅
읽는 시간: 20분 | 난이도: 중급
참고 자료
- Set up iOS development - iOS 개발 환경 설정
- Adding a launch screen - 런치 스크린
- Platform Views - 네이티브 뷰 임베드
- iOS debugging - iOS 디버깅
문제 상황
Flutter 앱을 iOS에서 실행하려면 macOS 개발 환경과 Xcode 설정이 필요합니다. 또한 앱이 시작될 때 보여줄 런치 스크린, 네이티브 iOS 뷰 통합, iOS 특화 디버깅 기법 등 플랫폼 특화 기능이 필요합니다.
iOS 통합이 필요한 상황
개발 환경 → macOS, Xcode, iOS Simulator 설정시작 화면 → 앱 로딩 중 런치 스크린 표시네이티브 뷰 → 지도, 웹뷰 등 기존 iOS 뷰 활용디버깅 → iOS 특화 문제 진단과 해결문제는 다음과 같습니다.
- iOS 개발은 macOS에서만 가능하다.
- Apple Developer 계정 없이는 실제 기기 배포가 제한된다.
- 런치 스크린은 Storyboard로 구현해야 한다.
- iOS 특화 문제는 Xcode 도구를 사용해야 진단할 수 있다.
해결 방법
Flutter는 iOS 플랫폼과의 긴밀한 통합을 지원합니다. Xcode를 통한 개발 환경 설정부터 네이티브 기능 통합까지 단계별로 알아봅니다.
챕터 1: iOS 개발 환경 설정
Why
NOTEFlutter 앱을 iOS 시뮬레이터나 실제 기기에서 실행하려면 Xcode와 iOS 개발 도구가 필요합니다. macOS에서만 iOS 앱을 빌드할 수 있습니다.
macOS → Xcode → iOS SDK → 시뮬레이터/기기
What
NOTEiOS 개발에 필요한 구성 요소입니다.
구성 요소 역할 macOS iOS 개발을 위한 운영체제 Xcode Apple의 IDE와 개발 도구 iOS SDK iOS 앱 빌드에 필요한 라이브러리 CocoaPods iOS 의존성 관리 도구
How
TIP1. Xcode 설치
App Store에서 Xcode를 설치합니다. 또는 터미널에서:
Terminal window xcode-select --install2. Xcode 명령줄 도구 설정
Terminal window sudo xcode-select --switch /Applications/Xcode.app/Contents/Developersudo xcodebuild -runFirstLaunch3. iOS 시뮬레이터 확인
Xcode 실행 후 Window > Devices and Simulators에서 시뮬레이터를 확인합니다.
또는 터미널에서:
Terminal window open -a Simulator4. CocoaPods 설치
Terminal window sudo gem install cocoapodsApple Silicon Mac의 경우:
Terminal window sudo gem install cocoapods# 또는brew install cocoapods5. 라이선스 동의
Terminal window sudo xcodebuild -license accept6. 설정 확인
Terminal window flutter doctorXcode 항목이 녹색 체크로 표시되어야 합니다.
Terminal window flutter devicesiOS 시뮬레이터가 목록에 표시됩니다.
7. 실제 기기에서 테스트
실제 iOS 기기에서 테스트하려면:
- Apple ID로 Xcode에 로그인
- Xcode에서 프로젝트 열기:
open ios/Runner.xcworkspace- Signing & Capabilities에서 Team 선택
- 기기를 USB로 연결하고 신뢰 설정
Watch out
WARNING무료 Apple ID로는 7일마다 앱을 다시 설치해야 합니다. 실제 기기 배포를 계속하려면 Apple Developer Program($99/년)에 가입하세요.
무료 Apple ID:- 시뮬레이터 무제한- 실제 기기 7일 제한- App Store 배포 불가Apple Developer Program:- 시뮬레이터/기기 무제한- App Store 배포 가능- TestFlight 베타 테스트
결론: macOS에 Xcode를 설치하고 명령줄 도구를 설정하면 Flutter 앱을 iOS 시뮬레이터나 실제 기기에서 실행할 수 있습니다.
챕터 2: 런치 스크린 구현
Why
NOTEiOS 앱이 시작될 때 Flutter 엔진이 초기화되는 동안 빈 화면이 보이면 사용자 경험이 나빠집니다. 런치 스크린을 설정하면 앱 로고나 브랜드 이미지를 표시하며 자연스럽게 앱을 시작할 수 있습니다.
앱 실행 → 런치 스크린 표시 → Flutter 엔진 초기화 → 앱 화면
What
NOTEiOS 런치 스크린은 Storyboard로 구현합니다.
파일 역할 LaunchScreen.storyboard 런치 스크린 UI 정의 Assets.xcassets 이미지 에셋 저장 Info.plist 런치 스크린 설정 참조
How
TIP1. Xcode에서 프로젝트 열기
Terminal window open ios/Runner.xcworkspace2. LaunchScreen.storyboard 편집
프로젝트 네비게이터에서 Runner > Runner > LaunchScreen.storyboard를 선택합니다.
기본 구성:
- View Controller 안에 View가 있음
- 기본적으로 Flutter 로고와 텍스트가 포함됨
3. 이미지 에셋 추가
Runner > Assets.xcassets를 선택합니다. 우클릭하여 Import를 선택하고 런치 스크린 이미지를 추가합니다.
이미지 크기 권장:
@1x: 기본 크기@2x: 2배 크기@3x: 3배 크기4. Storyboard에서 UI 구성
LaunchScreen.storyboard에서:
- 기존 요소 삭제 (또는 수정)
- + 버튼으로 Image View 추가
- 이미지 에셋 선택
- Auto Layout 제약 조건 설정
Auto Layout 예시 (중앙 정렬):
이미지 선택 → Editor > Align > Horizontally in Container이미지 선택 → Editor > Align > Vertically in Container5. 배경색 설정
View를 선택하고 Attributes Inspector에서 Background 색상을 변경합니다.
6. Safe Area 고려
// storyboard에서 Use Safe Area Layout Guides 체크// 노치가 있는 기기에서 콘텐츠가 잘리지 않도록코드로 런치 스크린 커스터마이징 (선택)
ios/Runner/AppDelegate.swift:import Flutterimport UIKit@main@objc class AppDelegate: FlutterAppDelegate {override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {// 런치 스크린 표시 시간 연장 (선택)// Thread.sleep(forTimeInterval: 1.0)GeneratedPluginRegistrant.register(with: self)return super.application(application, didFinishLaunchingWithOptions: launchOptions)}}
Watch out
WARNING런치 스크린에는 동적 콘텐츠를 넣을 수 없습니다. 네트워크 호출, 애니메이션, 사용자 데이터 등은 불가능합니다.
// ❌ 런치 스크린에서 불가능- 네트워크 요청- 복잡한 애니메이션- 사용자별 맞춤 콘텐츠- 로딩 프로그레스 바// ✅ 런치 스크린에서 가능- 정적 이미지- 단순 배경색- 앱 로고복잡한 시작 화면이 필요하면 Flutter에서 별도의 스플래시 위젯을 구현하세요.
결론: LaunchScreen.storyboard에서 이미지와 배경색을 설정하면 앱 시작 시 런치 스크린이 표시됩니다.
챕터 3: Platform Views로 네이티브 뷰 임베드
Why
NOTE지도, 웹뷰, 카메라 프리뷰 등 기존 iOS 네이티브 뷰를 Flutter 위젯 트리에 직접 포함해야 할 때가 있습니다. Platform Views를 사용하면 네이티브 UIView를 Flutter 앱에 임베드할 수 있습니다.
Flutter 위젯 트리 ← Platform View ← iOS UIView/UIViewController
What
NOTEiOS Platform Views는 UIView를 Flutter에 임베드합니다.
클래스 역할 FlutterPlatformViewFactory 플랫폼 뷰 생성 팩토리 FlutterPlatformView 플랫폼 뷰 프로토콜 UiKitView (Flutter) iOS 네이티브 뷰 표시 위젯
How
TIP1. 플랫폼 뷰 클래스 생성
ios/Runner/NativeView.swift:import Flutterimport UIKitclass NativeViewFactory: NSObject, FlutterPlatformViewFactory {private var messenger: FlutterBinaryMessengerinit(messenger: FlutterBinaryMessenger) {self.messenger = messengersuper.init()}func create(withFrame frame: CGRect,viewIdentifier viewId: Int64,arguments args: Any?) -> FlutterPlatformView {return NativeView(frame: frame,viewIdentifier: viewId,arguments: args,binaryMessenger: messenger)}func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {return FlutterStandardMessageCodec.sharedInstance()}}class NativeView: NSObject, FlutterPlatformView {private var _view: UIViewinit(frame: CGRect,viewIdentifier viewId: Int64,arguments args: Any?,binaryMessenger messenger: FlutterBinaryMessenger?) {_view = UIView()super.init()createNativeView(view: _view, arguments: args)}func view() -> UIView {return _view}func createNativeView(view: UIView, arguments args: Any?) {view.backgroundColor = UIColor.systemBluelet label = UILabel()label.text = "iOS 네이티브 뷰"label.textColor = UIColor.whitelabel.textAlignment = .centerlabel.frame = CGRect(x: 0, y: 0, width: 200, height: 50)label.center = view.centerif let params = args as? [String: Any],let text = params["text"] as? String {label.text = text}view.addSubview(label)}}2. AppDelegate에서 팩토리 등록
ios/Runner/AppDelegate.swift:import Flutterimport UIKit@main@objc class AppDelegate: FlutterAppDelegate {override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)// Platform View 팩토리 등록let factory = NativeViewFactory(messenger: registrar(forPlugin: "native-view")!.messenger())registrar(forPlugin: "native-view")!.register(factory,withId: "native-view" // viewType 식별자)return super.application(application, didFinishLaunchingWithOptions: launchOptions)}}3. Flutter에서 Platform View 사용
import 'package:flutter/material.dart';import 'package:flutter/services.dart';class NativeViewExample extends StatelessWidget {const NativeViewExample({super.key});@overrideWidget build(BuildContext context) {// viewType은 iOS에서 등록한 것과 일치해야 함const String viewType = 'native-view';// creationParams로 초기 데이터 전달final Map<String, dynamic> creationParams = {'text': 'Flutter에서 전달한 텍스트',};return Scaffold(appBar: AppBar(title: const Text('iOS Platform View 예제')),body: Column(children: [const Text('Flutter 위젯'),// iOS Platform View 임베드SizedBox(height: 100,child: UiKitView(viewType: viewType,creationParams: creationParams,creationParamsCodec: const StandardMessageCodec(),),),const Text('Flutter 위젯 계속'),],),);}}4. 플랫폼별 분기
import 'dart:io';import 'package:flutter/foundation.dart';Widget buildPlatformView() {const viewType = 'native-view';const creationParams = {'text': '네이티브 뷰'};if (Platform.isIOS) {return UiKitView(viewType: viewType,creationParams: creationParams,creationParamsCodec: const StandardMessageCodec(),);} else if (Platform.isAndroid) {return AndroidView(viewType: viewType,creationParams: creationParams,creationParamsCodec: const StandardMessageCodec(),);}return const Text('지원하지 않는 플랫폼');}
Watch out
WARNINGPlatform Views는 성능 오버헤드가 있습니다. 특히 스크롤 리스트 안에서 사용하면 성능이 크게 저하됩니다.
// ❌ 성능 문제: 리스트 아이템마다 Platform ViewListView.builder(itemCount: 50,itemBuilder: (context, index) => UiKitView(viewType: 'native-view',),)// ✅ Platform View 최소화Column(children: [Expanded(child: FlutterListView()),SizedBox(height: 250,child: UiKitView(viewType: 'map-view'),),],)가능하면 Flutter 위젯으로 구현하고, 네이티브 뷰가 꼭 필요한 경우에만 Platform Views를 사용하세요.
결론: FlutterPlatformViewFactory를 구현하고 등록하면 UiKitView 위젯으로 네이티브 UIView를 Flutter에 임베드할 수 있습니다.
챕터 4: iOS 특화 디버깅
Why
NOTEFlutter 앱에서 iOS 특화 문제가 발생하면 Xcode의 디버깅 도구를 사용해야 합니다. 메모리 문제, 네이티브 크래시, 성능 문제 등은 Xcode Instruments로 진단할 수 있습니다.
문제 발생 → Xcode 도구로 분석 → 원인 파악 → 해결
What
NOTEiOS 디버깅에 사용하는 도구입니다.
도구 용도 Xcode Debugger 브레이크포인트, 변수 확인 Instruments 성능, 메모리, 네트워크 프로파일링 Console.app 시스템 로그 확인 lldb 저수준 디버깅
How
TIP1. Xcode에서 디버깅
Terminal window # Xcode에서 프로젝트 열기open ios/Runner.xcworkspaceXcode에서 Product > Run을 선택하면 디버거가 연결됩니다.
2. 네이티브 코드에 브레이크포인트 설정
Xcode에서 Swift/Objective-C 코드에 브레이크포인트를 설정할 수 있습니다.
// AppDelegate.swift에서override func application(...) -> Bool {// 여기에 브레이크포인트 설정print("앱 시작") // ← 클릭하여 브레이크포인트return super.application(...)}3. 콘솔 로그 확인
Flutter 로그와 네이티브 로그를 모두 확인:
Terminal window # 터미널에서 Flutter 로그flutter logs# Xcode Console에서 네이티브 로그 확인# View > Debug Area > Activate Console4. Instruments로 성능 분석
Xcode에서 Product > Profile (Cmd+I)을 선택합니다.
주요 Instruments:
- Time Profiler: CPU 사용량 분석
- Allocations: 메모리 할당 추적
- Leaks: 메모리 누수 탐지
- Network: 네트워크 활동 모니터링
5. 메모리 문제 디버깅
// 메모리 경고 수신override func applicationDidReceiveMemoryWarning(_ application: UIApplication) {print("메모리 경고 수신")}Instruments의 Allocations 도구로 메모리 사용량을 확인합니다.
6. 크래시 로그 분석
크래시가 발생하면:
- Window > Devices and Simulators 열기
- 기기 선택
- View Device Logs 클릭
- 크래시 로그 확인
7. 네트워크 디버깅
// Info.plist에 ATS 예외 추가 (개발용)// App Transport Security Settings > Allow Arbitrary Loads = YESInstruments의 Network 도구로 네트워크 요청을 분석합니다.
8. Flutter와 네이티브 간 통신 디버깅
Method Channel 디버깅:
// Flutter 측const channel = MethodChannel('com.example/channel');try {final result = await channel.invokeMethod('someMethod');print('결과: $result');} on PlatformException catch (e) {print('에러: ${e.message}');}// iOS 측let channel = FlutterMethodChannel(name: "com.example/channel",binaryMessenger: controller.binaryMessenger)channel.setMethodCallHandler { call, result inprint("메서드 호출: \(call.method)")print("인수: \(String(describing: call.arguments))")if call.method == "someMethod" {result("성공")} else {result(FlutterMethodNotImplemented)}}
Watch out
WARNINGRelease 빌드에서는 디버거를 연결할 수 없습니다. 프로덕션 크래시를 분석하려면 크래시 리포팅 서비스를 사용하세요.
// Firebase Crashlytics 등 사용dependencies:firebase_crashlytics: ^3.0.0void main() async {WidgetsFlutterBinding.ensureInitialized();await Firebase.initializeApp();// Flutter 에러 캡처FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;runApp(const MyApp());}
결론: Xcode Debugger와 Instruments를 사용하면 iOS 특화 문제를 진단하고 해결할 수 있습니다.
한계
iOS 플랫폼 통합에는 몇 가지 한계가 있습니다.
- macOS 필수: iOS 앱 개발과 빌드는 macOS에서만 가능합니다.
- Apple Developer 비용: 실제 기기 배포와 App Store 배포에는 연간 $99의 개발자 계정이 필요합니다.
- 런치 스크린 제한: Storyboard로만 구현 가능하며 동적 콘텐츠를 넣을 수 없습니다.
- Platform Views 오버헤드: 네이티브 뷰 임베드는 성능에 영향을 주며, 많이 사용하면 프레임 드롭이 발생합니다.
Footnotes
-
Platform Views(플랫폼 뷰): 네이티브 Android 또는 iOS 뷰를 Flutter 위젯 트리에 임베드하는 기능이다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!