이 글은 누구를 위한 것인가
- iOS 18 / Android 15 대응이 필요한 앱 개발자
- 새 OS 출시에 맞춰 앱을 업데이트해야 하는 팀
- Breaking Change와 신기능을 빠르게 파악하고 싶은 엔지니어
들어가며
iOS 18과 Android 15는 2024년 하반기 출시 이후 2026년 현재 대다수 기기에 배포되어 있다. 이제 두 OS를 지원하지 않으면 사용자 경험 저하뿐 아니라 앱스토어 정책에도 영향을 받는다.
Apple은 2025년 4월부터 새 앱 및 업데이트에 iOS 17 SDK 이상을 요구한다. Google도 Android 14 target SDK 요구를 강화하고 있다. 더 이상 미룰 수 없다.
이 글은 bluefoxdev.kr의 모바일 OS 업데이트 대응 가이드 를 참고하고, iOS 18/Android 15 실전 적용 관점에서 확장하여 작성했습니다.
1. iOS 18 주요 변경사항
1.1 프라이버시: 연락처 접근 세분화
// iOS 18 이전: 연락처 전체 접근 또는 거부만 가능
// iOS 18: 사용자가 특정 연락처만 선택해서 공유 가능
import Contacts
// 접근 상태 확인 (새로운 .limited 케이스)
let status = CNContactStore.authorizationStatus(for: .contacts)
switch status {
case .authorized:
// 전체 접근
case .limited:
// iOS 18 새 케이스: 일부 연락처만 접근 가능
// UI에 "더 많은 연락처 추가" 옵션 제공 권장
case .restricted, .denied, .notDetermined:
// 기존과 동일
@unknown default:
break
}
// iOS 18에서 제한적 접근 처리
func handleLimitedContactAccess() {
// 사용자에게 추가 연락처 선택 유도
let store = CNContactStore()
// 사용 가능한 연락처로 기능 제한하거나
// 사용자에게 더 많은 접근 요청
}
1.2 Swift Data + SwiftUI 개선
import SwiftData
import SwiftUI
// iOS 18: @Query에 정렬, 필터 조건을 동적으로 변경 가능
struct ContentView: View {
@State private var sortOrder = SortDescriptor(\Item.createdAt, order: .reverse)
@State private var searchText = ""
// iOS 18: Query를 뷰 내에서 동적으로 구성
@Query var items: [Item]
// iOS 17: 컴파일 타임에 고정된 쿼리만 가능했음
// iOS 18: 런타임에 predicate 변경 가능
var body: some View {
List(items.filter {
searchText.isEmpty || $0.name.contains(searchText)
}) { item in
Text(item.name)
}
.searchable(text: $searchText)
}
}
@Model
class Item {
var name: String
var createdAt: Date
init(name: String) {
self.name = name
self.createdAt = Date()
}
}
1.3 RealityKit 2.0 - Vision Pro 대응
// visionOS 앱에도 적용 필요 (iOS 18 SDK 포함)
import RealityKit
// iOS 18: SpatialTrackingSession 공식 API
struct ImmersiveSpaceView: View {
var body: some View {
RealityView { content in
// 3D 콘텐츠 배치
let entity = ModelEntity(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .blue, isMetallic: true)]
)
content.add(entity)
}
}
}
1.4 Breaking Changes 주의
iOS 18에서 deprecated/removed:
[제거됨]
- UIAlertView → UIAlertController 사용
- UIActionSheet → UIAlertController 사용
- addSubview(_:) on UIWindow directly → windowScene 사용
[Deprecated]
- UINavigationController.isNavigationBarHidden
→ navigationBarBackButtonHidden Toolbar
- NSURLSession completionHandler 일부 API
→ async/await 버전 사용
[동작 변경]
- UIKit 앱에서 Scene-based lifecycle이 기본값
Info.plist에 UIApplicationSceneManifest 추가 필요
2. Android 15 주요 변경사항
2.1 예측 뒤로 가기 (Predictive Back) 의무화
// Android 15: targetSdk 35 앱은 Predictive Back 지원 필수
// AndroidManifest.xml
// android:enableOnBackInvokedCallback="true" 설정 필요
// (이미 선언한 경우 Android 15에서 더 엄격하게 적용)
// 기존 방식 (deprecated)
@Deprecated
override fun onBackPressed() {
// 사용 금지
}
// 올바른 방식
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this) {
// 뒤로 가기 처리
// isEnabled = false 로 기본 동작 복구 가능
}
}
}
// Fragment에서의 처리
class MyFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
requireActivity().onBackPressedDispatcher.addCallback(
this, // LifecycleOwner
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Fragment 뒤로 가기 처리
}
}
)
}
}
2.2 사진 선택기 개선 (Photo Picker)
// Android 15: PhotoPicker가 기본 동작, READ_MEDIA_IMAGES 권한 없어도 사용 가능
// 올바른 구현 (권한 불필요)
val pickMedia = registerForActivityResult(
ActivityResultContracts.PickVisualMedia()
) { uri ->
if (uri != null) {
// 선택된 이미지 처리
processImage(uri)
}
}
// 실행
pickMedia.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
// 여러 장 선택
val pickMultipleMedia = registerForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(5) // 최대 5장
) { uris ->
uris.forEach { uri -> processImage(uri) }
}
2.3 알림 권한 강화
// Android 15: 정확한 알람(Exact Alarm) 권한 강화
// SCHEDULE_EXACT_ALARM 또는 USE_EXACT_ALARM 선언 필요
// AndroidManifest.xml
// <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
// 또는
// <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
// 런타임 권한 확인
fun canScheduleExactAlarms(): Boolean {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
alarmManager.canScheduleExactAlarms()
} else {
true
}
}
// 설정으로 이동 유도
fun requestExactAlarmPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
}
}
2.4 targetSdk 35 대응 체크리스트
Android 15 (targetSdk 35) 필수 대응:
[ ] onBackPressed() 완전 제거 → OnBackPressedDispatcher 사용
[ ] READ_MEDIA_IMAGES/VIDEO 권한 → Photo Picker 사용으로 권한 불필요
[ ] Exact Alarm 권한 검토
[ ] Foreground Service 타입 명시 (dataSync, mediaPlayback, etc.)
[ ] Edge-to-Edge 레이아웃 적용 (WindowInsets 처리)
[ ] OpenSSL 버전 업그레이드 (NDK 앱)
3. Edge-to-Edge 레이아웃 (공통)
3.1 iOS 18 Safe Area 처리
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
.ignoresSafeArea(.container, edges: .top) // 상단 확장
.safeAreaInset(edge: .bottom) {
// 하단 고정 요소 (탭바 위)
BottomActionBar()
}
}
}
3.2 Android 15 Edge-to-Edge
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Android 15: enableEdgeToEdge() 필수 (targetSdk 35)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
// WindowInsets 처리
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
insets
}
}
}
4. 버전별 지원 정책
[권장 지원 범위 - 2026년 기준]
iOS:
최소 지원: iOS 16 (전체 기기의 95%+)
타겟: iOS 17/18 최신 기능 활용
AppStore 제출 요건: iOS 17 SDK 이상
Android:
최소 지원: Android 7.0 (API 24) → 일반적
권장 최소: Android 10 (API 29) → 보안 기능 활용
타겟 SDK: API 35 (Android 15)
Play Store 요건: targetSdk 34+ (2025년)
마무리: 배포 전 체크리스트
iOS 18 대응:
□ Xcode 16 이상으로 빌드 확인
□ 연락처 .limited 케이스 처리
□ UIAlertView/UIActionSheet 완전 제거
□ Scene-based lifecycle 확인
□ Swift 6 동시성 경고 해결
Android 15 대응:
□ targetSdk 35로 올리고 테스트
□ onBackPressed() 완전 제거
□ Predictive Back 애니메이션 테스트
□ enableEdgeToEdge() 적용
□ Photo Picker 도입
□ Foreground Service 타입 명시
새 OS 지원은 "한번에 완전히"보다 "핵심 Breaking Change 먼저, 신기능은 점진적으로" 접근하는 것이 현실적이다.