iOS 18 & Android 15 개발자 변경사항 총정리: 앱 대응 가이드

모바일

iOS 18Android 15모바일 개발API 변경앱 마이그레이션

이 글은 누구를 위한 것인가

  • 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 먼저, 신기능은 점진적으로" 접근하는 것이 현실적이다.