-
[Flutter] 크로스 플랫폼의 함정: Android 15 대응이 iOS UI를 망가뜨렸을 때IT 2025. 6. 23. 21:03
Flutter로 크로스 플랫폼 앱을 개발할 때 가장 짜릿하면서도 위험한 순간은 바로 한 플랫폼을 위한 수정이 다른 플랫폼에서 예상치 못한 버그를 일으킬 때입니다. 저희 팀은 최근 Android 15의 최신 Edge-to-Edge 디스플레이에 대응하는 과정에서 바로 이 함정에 빠졌습니다.
Android에서는 완벽하게 작동하던 코드가 iOS의 하단 메뉴 바(Bottom Menu Bar)를 밀어 올리고 상태 표시줄(Status Bar)의 일관성을 깨뜨리는 문제를 낳았습니다. 이 글에서는 문제의 원인을 깊이 파고들고, Flutter에서 각 플랫폼의 고유한 특성을 존중하며 문제를 해결한 과정을 공유하고자 합니다.
🚨 문제 발생: 하나의 코드가 낳은 두 개의 결과
Android 15의 몰입감 높은 UI를 위해 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge) 설정을 적용했습니다. 그 결과는 놀라웠습니다. Android에서는 의도한 대로 완벽한 Edge-to-Edge UI가 구현되었습니다. 하지만 iOS에서는 다음과 같은 문제가 발생했습니다.
- iOS 하단 메뉴 바 위치 오류: SafeArea가 적용되었음에도 불구하고 하단 메뉴 바가 비정상적으로 위로 올라와 레이아웃이 깨졌습니다.
- 상태 표시줄 UI 불일치: 두 플랫폼에서 상태 표시줄의 스타일과 동작 방식이 달라 일관된 사용자 경험을 제공하기 어려웠습니다.
원인 분석: 무엇이 잘못되었나?
문제의 원인은 크게 세 가지였습니다. 모두 플랫폼 간의 근본적인 차이를 간과한 데서 비롯되었습니다.
1. Edge-to-Edge 설정의 무차별 적용
가장 큰 원인은 Android 전용 설정을 플랫폼 분기 없이 그대로 사용한 것이었습니다.
// 🚨 문제가 된 코드 - 모든 플랫폼에 동일하게 적용 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);SystemUiMode.edgeToEdge는 Android를 위한 설정입니다. 이 코드가 iOS에까지 적용되면서 iOS 고유의 레이아웃 시스템과 충돌을 일으켰습니다.
2. 불필한 Bottom Padding 계산
iOS에서 SafeArea가 이미 하단 안전 영역을 확보해 줌에도 불구하고, 추가적인 Padding을 계산하여 적용한 것도 문제였습니다.
// 🚨 문제가 된 코드 - iOS에서 불필요한 padding 적용 bottomPadding: Theme.of(context).platform == TargetPlatform.iOS ? MediaQuery.of(context).padding.bottom : 0,이 코드는 결과적으로 하단에 이중으로 여백을 주어 메뉴 바를 위로 밀어 올렸습니다.
3. 상태 표시줄 시스템의 차이 미고려
마지막으로, Android와 iOS의 상태 표시줄 처리 방식이 근본적으로 다르다는 점을 고려하지 않고 코드를 작성한 것이 UI 불일치를 초래했습니다.
핵심 지식: Android와 iOS의 상태 표시줄은 어떻게 다른가?
이 문제를 완전히 이해하고 해결하기 위해서는 두 플랫폼의 상태 표시줄 시스템 차이를 알아야 합니다.
Android 상태 표시줄 시스템
Android는 앱이 상태 표시줄을 직접 제어할 수 있는 유연한 시스템을 제공합니다. SystemUiOverlayStyle을 통해 다양한 속성을 설정할 수 있습니다.
- 특징: statusBarColor로 배경색을 직접 변경할 수 있으며, Edge-to-Edge 모드에서는 이를 투명하게 만들어 콘텐츠를 뒤로 확장시킵니다. 앱 실행 중 언제든지 실시간 변경이 가능합니다.
- 주요 지원 속성:
- statusBarColor: ✅ 배경색 직접 설정
- statusBarIconBrightness: ✅ 아이콘 밝기 (어둡게/밝게)
- systemNavigationBarColor: ✅ 하단 네비게이션 바 색상
- systemNavigationBarIconBrightness: ✅ 하단 네비게이션 바 아이콘 밝기
iOS 상태 표시줄 시스템
반면, iOS는 상태 표시줄을 시스템이 관리하며 앱은 제한적인 제어만 가능합니다.
- 특징: 상태 표시줄은 항상 투명하며, 배경색은 그 뒤에 배치된 콘텐츠의 색상에 의해 시각적으로 결정됩니다. 앱은 오직 아이콘의 밝기(Brightness.light / Brightness.dark)만 제어할 수 있습니다.
- 주요 지원 속성:
- statusBarColor: ❌ 무시됨 (항상 투명)
- statusBarIconBrightness: ✅ 아이콘 밝기만 설정 가능
- statusBarBrightness: ✅ iOS 전용 설정 (아이콘 밝기를 결정하는 데 사용)
- systemNavigationBar...: ❌ 하드웨어 네비게이션 바가 없으므로 모두 무시됨
플랫폼별 상태 표시줄 설정 예시
이러한 차이 때문에 동일한 UI 효과를 내기 위해서도 플랫폼별로 다른 접근이 필요합니다.
예시: 빨간색 상태 표시줄 만들기
// Android: 색상을 직접 설정 if (Platform.isAndroid) { SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.red, // ✅ 실제로 빨간색으로 변경됨 statusBarIconBrightness: Brightness.light, ), ); } // iOS: 상태 표시줄 뒤에 배경을 배치 if (Platform.isIOS) { // 1. UI 구조로 배경 생성 Container( color: Colors.red, // 상태 표시줄 영역까지 확장되는 배경 child: SafeArea( child: YourContent(), ), ); // 2. 아이콘 색상만 설정 SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, ), ); }🔧 해결 과정: 플랫폼을 존중하는 코드로 개선하기
문제의 원인과 플랫폼별 특성을 파악한 후, 다음과 같이 4단계에 걸쳐 코드를 수정했습니다.
1단계: 플랫폼별 시스템 UI 설정 분리
Platform.isAndroid 분기 처리를 통해 Edge-to-Edge 모드가 Android에서만 활성화되도록 수정했습니다.
// ✅ 해결된 코드 - 플랫폼별 분기 처리 if (Platform.isAndroid) { // Android 15 Edge-to-Edge 대응 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); } else if (Platform.isIOS) { // iOS 기본 시스템 UI 모드 유지 SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); }2단계: Bottom Padding 계산 로직 제거
두 플랫폼 모두 SafeArea 위젯을 통해 안전 영역을 관리하도록 통일하고, 불필요한 수동 패딩 계산 로직을 제거했습니다.
// ✅ 해결된 코드 - SafeArea에 의존하도록 단순화 // 수동으로 padding을 계산하던 기존 로직을 완전히 제거하고, // 각 화면의 Scaffold를 SafeArea 위젯으로 감싸는 것으로 충분합니다.3단계: 상태 표시줄 설정 플랫폼별 최적화
상태 표시줄 스타일을 설정하는 함수 내부에 플랫폼 분기 코드를 추가하여, 각 OS가 지원하는 속성만 사용하도록 최적화했습니다.
// ✅ 해결된 코드 - 플랫폼별 Status Bar 설정 void _updateStatusBar() { SystemUiOverlayStyle style; if (Platform.isAndroid) { // Android - statusBarColor와 systemNavigationBar 모두 지원 style = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent, systemNavigationBarIconBrightness: Brightness.dark, ); } else { // iOS - statusBarIconBrightness와 statusBarBrightness만 지원 style = const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.light, // iOS 전용 ); } SystemChrome.setSystemUIOverlayStyle(style); }4단계: 홈 화면 스크롤 시 상태 표시줄 처리 분기
스크롤에 따라 동적으로 상태 표시줄을 변경하는 로직에도 플랫폼별 차이를 적용했습니다. Android에서는 배경색에 맞춰 아이콘 색을 바꾸지만, iOS에서는 일관성을 위해 검은색 아이콘으로 고정했습니다.
// ✅ 해결된 코드 - 홈 화면 스크롤 대응 void _updateStatusBarForScroll() { SystemUiOverlayStyle style; bool isInPurpleArea = _checkIfInPurpleArea(); // 스크롤 위치 체크 if (isInPurpleArea) { if (Platform.isAndroid) { // Android: 보라색 배경 위에서는 흰색 아이콘 style = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.light, ); } else { // iOS: 일관성을 위해 검은색 아이콘 유지 style = const SystemUiOverlayStyle(statusBarIconBrightness: Brightness.dark); } } else { // 흰색 영역에서는 두 플랫폼 모두 검은색 아이콘 style = const SystemUiOverlayStyle(statusBarIconBrightness: Brightness.dark); } SystemChrome.setSystemUIOverlayStyle(style); }최종 결과: 두 플랫폼 모두에서 최적의 UI를 찾다
이러한 개선을 통해 두 플랫폼 모두에서 안정적이고 일관된 UI를 구현할 수 있었습니다.
- Android:
- Edge-to-Edge 모드를 유지하여 현대적이고 몰입감 있는 UI 제공.
- 배경색에 따라 아이콘 색상을 동적으로 변경하여 가시성 확보.
- iOS:
- 기본 시스템 UI 모드를 사용하여 안정적인 레이아웃 유지.
- SafeArea 기반으로 자연스러운 하단 바 위치 확보.
- 모든 화면에서 일관된 검은색 상태 표시줄 아이콘으로 통일성 유지.
교훈: 크로스 플랫폼 개발의 핵심
이번 경험을 통해 얻은 가장 큰 교훈은 **"Flutter에서 플랫폼별 UI 처리의 중요성, 특히 상태 표시줄처럼 시스템에 깊이 의존하는 요소는 근본적인 차이를 이해하고 존중해야 한다"**는 것입니다.
플랫폼별 분기 처리가 코드 복잡도를 높일 수는 있지만, 각 OS 사용자에게 최적화된 경험을 제공하기 위한 필수적인 투자입니다. 성공적인 크로스 플랫폼 앱은 코드를 공유하는 것을 넘어, 각 플랫폼의 생태계를 이해하고 그에 맞게 섬세하게 조정할 때 비로소 완성됩니다.
'IT' 카테고리의 다른 글
[Python] Higher-Order Functions와 Generator로 Django FCM 코드 중복 제거 (0) 2026.01.21 블로그 7개월. 공백의 이유 (0) 2026.01.09 [Flutter] Android 15의 Edge-to-Edge 완벽 정복기 (0) 2025.06.23 [Flutter] iOS에서 다른 앱의 음악이 꺼지는 문제 해결하기 (0) 2025.06.20 [Flutter] Android 음악 재생 방해 문제 해결기 (0) 2025.06.19