SwiftUI는 UIKit과 함께 사용할 수 있으며 SwiftUI로 앱 전체를 한 번에 재작성하는 대신 점진적으로 전환하는 것이 현실적이고 효율적입니다. UIKit의 기존 ViewController와 SwiftUI의 뷰를 통합할 수 있으므로 필요한 부분부터 점진적으로 SwiftUI로 전환하는 방법에 대해서 정리해 보았습니다.
UIKit의 ViewController에서 SwiftUI 뷰를 사용할 수 있도록 UIHostingController를 제공합니다.
let swiftUIView = ContentView()
let hostingController = UIHostingController(rootView: swiftUIView)
present(hostingController, animated: true)
반대로 SwiftUI에서 기존 UIKit의 UIViewController나 UIView를 포함할 수 있습니다. 이를 통해 SwiftUI와 UIKit 간의 상호 운용성을 유지하면서도 SwiftUI를 점진적으로 도입할 수 있습니다.
UIKit의 컴포넌트를 UIViewControllerRepresentable를 상속받아 선언형 컴포넌트로 변경시켜주는 형태입니다.
struct MyUIKitViewController: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> SomeUIKitViewController {
return SomeUIKitViewController()
}
func updateUIViewController(_ uiViewController: SomeUIKitViewController, context: Context) {
// 업데이트 로직
}
}
기존 앱에서 사용하는 데이터 모델과 ViewModel은 SwiftUI와 호환되도록 설계합니다. SwiftUI에서는 단방향 데이터 흐름을 유지하므로 기존 데이터 모델이 이 흐름을 따르도록 수정해야 할 수도 있습니다.
기존 데이터 모델을 SwiftUI와 호환되도록 ObservableObject와 @Published 속성으로 변환합니다. 데이터 변경이 UI에 반영되도록 SwiftUI의 반응형 상태 관리를 도입합니다.
class MyViewModel: ObservableObject {
@Published var data = "Initial Data"
}
SwiftUI에서는 상태가 하나의 출처에서 관리되고 이를 자식 뷰로 전달하는 방식이 일반적이므로, 기존의 복잡한 상태 관리 로직을 단순화할 수 있습니다.
UIKit과 SwiftUI는 서로 다른 생명 주기 이벤트를 가지고 있습니다. SwiftUI는 onAppear, onDisappear와 같은 생명 주기 메서드를 사용하며, UIKit에서는 viewDidLoad, viewWillAppear 등이 사용됩니다. SwiftUI로 전환할 때는 이 차이를 고려하여 상태 초기화와 클린업을 구현해야 합니다.
SwiftUI에서는 onAppear에서 데이터 초기화, onDisappear에서 클린업 작업을 처리할 수 있습니다.
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.onAppear {
// 초기화 작업
}
.onDisappear {
// 클린업 작업
}
}
}
UIKit에서는 UIViewController가 상태를 관리하던 방식에서 SwiftUI로 전환할 때는 @StateObject와 @ObservedObject로 상태를 관리하게 됩니다.
ViewModel을 ViewController에서 SwiftUI View로 주입
기존 앱의 ViewModel이나 데이터를 SwiftUI View에 전달할 때 @ObservedObject나 @EnvironmentObject를 사용하여 SwiftUI와 일관된 상태 관리를 유지할 수 있습니다.
class MyViewController: UIViewController {
private let viewModel = MyViewModel()
override func viewDidLoad() {
super.viewDidLoad()
let swiftUIView = ContentView(viewModel: viewModel)
let hostingController = UIHostingController(rootView: swiftUIView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.frame = view.bounds
hostingController.didMove(toParent: self)
}
}
SwiftUI는 상태 관리와 UI 구성이 분리되어야 최상의 성능을 발휘합니다. 기존 UIKit 코드에서는 UI와 로직이 혼합되어 있을 가능성이 있으므로, SwiftUI로 전환하면서 ViewModel을 통해 비즈니스 로직을 분리하는 리팩토링을 진행합니다.
뷰 로직은 ViewModel에 포함하고, SwiftUI 뷰에서는 데이터 표시와 사용자 입력만 처리합니다. 이 패턴을 통해 SwiftUI의 반응형 데이터 바인딩을 쉽게 적용할 수 있습니다.
SwiftUI는 PreviewProvider를 통해 다양한 기기에서 UI를 빠르게 확인할 수 있는 미리보기 기능을 제공합니다. 기존 UIKit에서 시뮬레이터와 기기 테스트에 많은 시간이 걸렸다면, SwiftUI 미리보기 기능을 적극 활용하여 레이아웃과 UI 상태를 빠르게 검토할 수 있습니다.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: MyViewModel())
.previewDevice("iPhone 13")
}
}
SwiftUI로 전환할 때, 중복된 UIKit UI 요소들을 SwiftUI에서 재사용 가능한 컴포넌트로 컴포넌트화하여 전환하면 향후 유지보수가 훨씬 수월해집니다. 작은 UI 요소들을 독립적인 컴포넌트로 만들어 코드의 재사용성과 가독성을 높일 수 있습니다.
struct CustomButton: View {
let title: String
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}