SwiftUI + UIKit 혼합 아키텍처 실전 가이드
아직까지는 SwiftUI는 완벽하지 않다 — 그래서 혼합이 필요하다
SwiftUI는 2019년 WWDC에서 처음 공개된 이후 매년 발전해왔지만,
2025년 현재에도 UIKit과의 완전한 대체 관계라기보다는
보완적인 공존 관계로 활용하는 것이 가장 현실적인 접근입니다.
특히 대규모 프로젝트나 기존 레거시 앱에서는
“SwiftUI의 선언적 생산성과 UIKit의 안정적 제어력을 동시에 활용”하는
혼합 아키텍처(Hybrid Architecture) 가 실무 표준으로 자리 잡고 있습니다.
이 글에서는 SwiftUI 버전별 주요 변화부터, UIKit과의 연결 방식, 데이터 흐름 관리,
그리고 실제 적용 예시까지 단계별로 정리하겠습니다.
1. SwiftUI 버전별 주요 변화 요약
| iOS 버전 | 주요 변화 | 실무 포인트 |
| iOS 13 (2019) | SwiftUI 첫 등장, 선언적 UI, Combine 연동 | 부분 도입 수준, UIKit 공존 필요 |
| iOS 14 (2020) | Lazy Stack/Grid, App Lifecycle(@main) | 성능 향상, SwiftUI 단독 앱 가능 |
| iOS 15 (2021) | AsyncImage, refreshable, searchable | 리스트/피드 앱에서 완전 사용 가능 |
| iOS 16 (2022) | NavigationStack, Layout API | 네비게이션 신뢰성 향상, 복잡한 구조 대응 |
| iOS 17 (2023) | Observation 프레임워크(@Observable) | Combine 대체 가능, 렌더링 효율 극대화 |
| iOS 18 (2024) | 차트·멀티윈도우·리치 텍스트 강화 | B2B/생산성 앱에서 SwiftUI-only 실현 가능 |
| iOS 26 (2025) | Liquid Glass UI, 새 렌더링 구조, Toolbar/GlassEffect API, WebView | 디자인·성능 혁신, UIKit 공존성 고려 |
2. UIKit과 SwiftUI 혼합 구조의 실제 필요성
SwiftUI는 아직 UIKit이 제공하는 세밀한 UI 제어(예: 커스텀 트랜지션, 입력 제스처, 뷰 계층 접근)까지 완벽히 대체하지 못합니다.
따라서 아래와 같은 상황에서는 혼합 구조가 이상적입니다.
혼합 구조가 필요한 대표 사례
- 대규모 레거시 프로젝트 유지보수
- 복잡한 네비게이션 흐름 (Deep Link, Modal Chain)
- 커스텀 애니메이션 또는 Transition 제어
- 서드파티 UIKit 기반 SDK (예: 광고, 결제, 지도 등) 사용 시
3. UIKit → SwiftUI 주입: UIHostingController
import SwiftUI
struct UserProfileView: View {
var username: String
var body: some View {
VStack {
Text("Hello, \(username)")
.font(.title)
.padding()
Button("닫기") {
print("Dismiss")
}
}
}
}
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let hostingVC = UIHostingController(rootView: UserProfileView(username: "까냥"))
addChild(hostingVC)
view.addSubview(hostingVC.view)
hostingVC.view.frame = view.bounds
hostingVC.didMove(toParent: self)
}
}
4. SwiftUI → UIKit 주입: UIViewControllerRepresentable
import SwiftUI
import UIKit
struct PhotoPickerView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}
5. 데이터 흐름 및 상태 관리 전략
- SwiftUI는 @StateObject, UIKit은 ObservableObject 인스턴스를 공유
- ViewModel을 EnvironmentObject로 전달하면 양방향 동기화 가능
- iOS 17 이상에서는 @Observable을 활용하여 Combine 없이도 반응형 상태 관리 가능
@Observable
class UserViewModel {
var username: String = "홍길동"
}
6. SwiftUI Bottom Sheet를 UIKit 프로젝트에 적용하기
struct PaymentSheet: View {
var body: some View {
VStack(spacing: 20) {
Text("결제 완료").font(.title)
Button("닫기") { print("닫기 동작") }
}
.presentationDetents([.medium, .large])
}
}
class MainViewController: UIViewController {
@IBAction func showSheet() {
let hosting = UIHostingController(rootView: PaymentSheet())
hosting.modalPresentationStyle = .pageSheet
if let sheet = hosting.sheetPresentationController {
sheet.detents = [.medium(), .large()]
}
present(hosting, animated: true)
}
}
7. iOS 26에서의 SwiftUI 변화 및 적용 포인트
주요 추가 기능
- Liquid Glass 디자인 언어 도입으로 반투명·굴절 효과 UI 적용 가능
- GlassEffectContainer / .glassEffect() API로 유리 질감 표현 지원
- 새 Toolbar API (ToolbarSpacer, TitleToolbarItemPlacement) 추가
- 검색 UI 배치 개선 — iPad는 상단, iPhone은 하단 자동 배치
- 새 렌더링 엔진: SwiftUI 뷰가 레이어 기반으로 동작 (성능 향상)
실무 적용 가이드
- UIKit+SwiftUI 혼합 시, Liquid Glass 효과와 UIVisualEffectView 조합 테스트 필요
- SwiftUI Toolbar API 변경 대응 코드 분리 (iOS 25 이하 호환성 고려)
- 뷰 계층 구조를 직접 조작하는 SDK 사용 시 렌더링 호환성 검증 필요
예시: GlassEffect 적용
VStack {
Text("SwiftUI 26 Liquid Glass")
.font(.headline)
.padding()
}
.glassEffect(.systemUltraThinMaterial)
8. iOS 26에서 새로 추가된 SwiftUI WebView 기능
1) SwiftUI 전용 WebView 도입
iOS 26부터는 SwiftUI에서 별도의 UIKit 래핑 없이도 웹 콘텐츠를 직접 표시할 수 있는 WebView 컴포넌트가 추가되었습니다.
이는 내부적으로 WKWebView를 기반으로 하지만 SwiftUI 선언형 문법과 완벽히 통합되어 작동합니다.
import SwiftUI
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://www.apple.com")!)
}
}
2) WebPage 모델 클래스
- WebPage 타입은 웹페이지의 상태, URL, 제목, 로딩 진행률 등을 관찰할 수 있는 Observable 객체입니다.
- SwiftUI의 상태 관리(@State, @Observable)와 직접 연결되어 있습니다.
@State private var page = WebPage()
WebView(page)
.onAppear { page.load(URLRequest(url: URL(string: "https://developer.apple.com")!)) }
3) 자바스크립트 실행 및 커스텀 URL 스킴
- WebPage의 callJavaScript(_:) 메서드를 사용하면 JS 코드를 직접 실행할 수 있습니다.
- URLSchemeHandler를 등록해 커스텀 스킴(myapp://)을 처리할 수 있습니다.
let config = WebView.Configuration()
config.urlSchemeHandlers[URLScheme("myapp")] = MyHandler()
let page = WebPage(configuration: config)
4) 스크롤 및 제스처 제어
- SwiftUI 모디파이어로 스크롤·확대 제스처 제어 가능
- 예: .scrollBounceBehavior(.basedOnSize, axes: [.vertical])
- iPad·macOS 공통 코드에서도 스크롤 일관성 유지 가능
5) 실무 적용 시 주의점
- 최소 지원 OS를 iOS 26 이상으로 설정해야 함
- 이전 버전 호환을 위해선 UIViewRepresentable 기반 WKWebView 래퍼 유지 필요
- 쿠키·CORS 정책은 여전히 WebKit 엔진 정책을 따름
- 네비게이션 타이틀, 진행률, 오류 처리 등은 WebPage의 옵저버로 처리 가능
결론
SwiftUI는 iOS 26에 이르러 완전한 “렌더링 세대 전환”을 이루었으며,
UIKit과 SwiftUI의 경계가 점차 사라지고 있습니다.
- 기존 프로젝트: UIKit 중심 + SwiftUI 점진 도입
- 신규 프로젝트: SwiftUI 중심 + UIKit 커스텀 보완
- iOS 26 이상: GlassEffect / Observation / Toolbar 개선 및 자체 WebView로 완전 SwiftUI 앱 실현 가능
'Dev Study > SwiftUI' 카테고리의 다른 글
| SwiftUI에서 많이 하는 실수 - @EnvironmentObject를 남용하거나 주입을 잊어서 크래시가 나는 실수 (0) | 2025.12.05 |
|---|---|
| SwiftUI에서 많이 하는 실수 - @ObservedObject와 @StateObject를 잘못 사용해 ViewModel이 계속 초기화되는 실수 (1) | 2025.12.05 |
| SwiftUI에서 많이 하는 실수 - @State와 @Binding을 혼동해서 잘못 사용하는 실수 (0) | 2025.12.05 |
| SwiftUI 전용 개발을 위한 최적의 iOS Deploy Target 가이드(25년 기준) (0) | 2025.11.10 |
| foregroundColor vs tint (0) | 2023.10.28 |
| SwiftUI 아이폰 회전시 Landscape, Portrait 이벤트 (0) | 2023.10.24 |
| SwiftUI Color Hex 값으로 생성하기 (0) | 2023.10.08 |
| SwiftUI + UIKit (0) | 2023.05.10 |

