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분 | 난이도: 중급


참고 자료#


문제 상황#

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#

NOTE

Flutter 앱을 iOS 시뮬레이터나 실제 기기에서 실행하려면 Xcode와 iOS 개발 도구가 필요합니다. macOS에서만 iOS 앱을 빌드할 수 있습니다.

macOS → Xcode → iOS SDK → 시뮬레이터/기기

What#

NOTE

iOS 개발에 필요한 구성 요소입니다.

구성 요소역할
macOSiOS 개발을 위한 운영체제
XcodeApple의 IDE와 개발 도구
iOS SDKiOS 앱 빌드에 필요한 라이브러리
CocoaPodsiOS 의존성 관리 도구

How#

TIP

1. Xcode 설치

App Store에서 Xcode를 설치합니다. 또는 터미널에서:

Terminal window
xcode-select --install

2. Xcode 명령줄 도구 설정

Terminal window
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch

3. iOS 시뮬레이터 확인

Xcode 실행 후 Window > Devices and Simulators에서 시뮬레이터를 확인합니다.

또는 터미널에서:

Terminal window
open -a Simulator

4. CocoaPods 설치

Terminal window
sudo gem install cocoapods

Apple Silicon Mac의 경우:

Terminal window
sudo gem install cocoapods
# 또는
brew install cocoapods

5. 라이선스 동의

Terminal window
sudo xcodebuild -license accept

6. 설정 확인

Terminal window
flutter doctor

Xcode 항목이 녹색 체크로 표시되어야 합니다.

Terminal window
flutter devices

iOS 시뮬레이터가 목록에 표시됩니다.

7. 실제 기기에서 테스트

실제 iOS 기기에서 테스트하려면:

  1. Apple ID로 Xcode에 로그인
  2. Xcode에서 프로젝트 열기: open ios/Runner.xcworkspace
  3. Signing & Capabilities에서 Team 선택
  4. 기기를 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#

NOTE

iOS 앱이 시작될 때 Flutter 엔진이 초기화되는 동안 빈 화면이 보이면 사용자 경험이 나빠집니다. 런치 스크린을 설정하면 앱 로고나 브랜드 이미지를 표시하며 자연스럽게 앱을 시작할 수 있습니다.

앱 실행 → 런치 스크린 표시 → Flutter 엔진 초기화 → 앱 화면

What#

NOTE

iOS 런치 스크린은 Storyboard로 구현합니다.

파일역할
LaunchScreen.storyboard런치 스크린 UI 정의
Assets.xcassets이미지 에셋 저장
Info.plist런치 스크린 설정 참조

How#

TIP

1. Xcode에서 프로젝트 열기

Terminal window
open ios/Runner.xcworkspace

2. 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에서:

  1. 기존 요소 삭제 (또는 수정)
  2. + 버튼으로 Image View 추가
  3. 이미지 에셋 선택
  4. Auto Layout 제약 조건 설정

Auto Layout 예시 (중앙 정렬):

이미지 선택 → Editor > Align > Horizontally in Container
이미지 선택 → Editor > Align > Vertically in Container

5. 배경색 설정

View를 선택하고 Attributes Inspector에서 Background 색상을 변경합니다.

6. Safe Area 고려

// storyboard에서 Use Safe Area Layout Guides 체크
// 노치가 있는 기기에서 콘텐츠가 잘리지 않도록

코드로 런치 스크린 커스터마이징 (선택)

ios/Runner/AppDelegate.swift:

import Flutter
import 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#

NOTE

iOS Platform Views는 UIView를 Flutter에 임베드합니다.

클래스역할
FlutterPlatformViewFactory플랫폼 뷰 생성 팩토리
FlutterPlatformView플랫폼 뷰 프로토콜
UiKitView (Flutter)iOS 네이티브 뷰 표시 위젯

How#

TIP

1. 플랫폼 뷰 클래스 생성

ios/Runner/NativeView.swift:

import Flutter
import UIKit
class NativeViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.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: UIView
init(
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.systemBlue
let label = UILabel()
label.text = "iOS 네이티브 뷰"
label.textColor = UIColor.white
label.textAlignment = .center
label.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
label.center = view.center
if 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 Flutter
import 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});
@override
Widget 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#

WARNING

Platform Views는 성능 오버헤드가 있습니다. 특히 스크롤 리스트 안에서 사용하면 성능이 크게 저하됩니다.

// ❌ 성능 문제: 리스트 아이템마다 Platform View
ListView.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#

NOTE

Flutter 앱에서 iOS 특화 문제가 발생하면 Xcode의 디버깅 도구를 사용해야 합니다. 메모리 문제, 네이티브 크래시, 성능 문제 등은 Xcode Instruments로 진단할 수 있습니다.

문제 발생 → Xcode 도구로 분석 → 원인 파악 → 해결

What#

NOTE

iOS 디버깅에 사용하는 도구입니다.

도구용도
Xcode Debugger브레이크포인트, 변수 확인
Instruments성능, 메모리, 네트워크 프로파일링
Console.app시스템 로그 확인
lldb저수준 디버깅

How#

TIP

1. Xcode에서 디버깅

Terminal window
# Xcode에서 프로젝트 열기
open ios/Runner.xcworkspace

Xcode에서 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 Console

4. Instruments로 성능 분석

Xcode에서 Product > Profile (Cmd+I)을 선택합니다.

주요 Instruments:

  • Time Profiler: CPU 사용량 분석
  • Allocations: 메모리 할당 추적
  • Leaks: 메모리 누수 탐지
  • Network: 네트워크 활동 모니터링

5. 메모리 문제 디버깅

// 메모리 경고 수신
override func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
print("메모리 경고 수신")
}

Instruments의 Allocations 도구로 메모리 사용량을 확인합니다.

6. 크래시 로그 분석

크래시가 발생하면:

  1. Window > Devices and Simulators 열기
  2. 기기 선택
  3. View Device Logs 클릭
  4. 크래시 로그 확인

7. 네트워크 디버깅

// Info.plist에 ATS 예외 추가 (개발용)
// App Transport Security Settings > Allow Arbitrary Loads = YES

Instruments의 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 in
print("메서드 호출: \(call.method)")
print("인수: \(String(describing: call.arguments))")
if call.method == "someMethod" {
result("성공")
} else {
result(FlutterMethodNotImplemented)
}
}

Watch out#

WARNING

Release 빌드에서는 디버거를 연결할 수 없습니다. 프로덕션 크래시를 분석하려면 크래시 리포팅 서비스를 사용하세요.

// Firebase Crashlytics 등 사용
dependencies:
firebase_crashlytics: ^3.0.0
void 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#

  1. Platform Views(플랫폼 뷰): 네이티브 Android 또는 iOS 뷰를 Flutter 위젯 트리에 임베드하는 기능이다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Flutter 튜토리얼 57편: iOS 플랫폼 통합
https://moodturnpost.net/posts/flutter/flutter-platform-ios/
작성자
Moodturn
게시일
2026-01-08
Moodturn

목차