SwiftUI는 선언적 문법과 뛰어난 성능으로 iOS 개발자 사이에서 빠르게 인기를 얻고 있습니다. 그러나 기존 UIKit 프로젝트에 SwiftUI를 통합하려면 몇 가지 기술적인 이해가 필요합니다.
1. UIKit과 SwiftUI의 차이점과 통합의 이점
UIKit은 Imperative(명령형) 스타일로, 개발자가 UI의 세부 동작을 하나하나 지정해야 합니다. 반면, SwiftUI는 Declarative(선언형) 스타일로 UI 구성과 데이터 흐름을 간결하게 관리할 수 있습니다.
SwiftUI를 UIKit에 통합해야 하는 이유
- 최신 기능 활용: SwiftUI는 새로운 iOS 버전에 맞춰 혁신적인 기능을 제공합니다.
- 생산성 향상: 코드 작성이 간결해지고 유지보수가 용이합니다.
- 점진적 전환 가능: 기존 UIKit 프로젝트에 SwiftUI 뷰를 단계적으로 도입하여 리팩토링 부담을 줄일 수 있습니다.
2. UIKit에서 SwiftUI 뷰 사용하기
UIKit에 SwiftUI를 통합하려면 UIHostingController를 사용해야 합니다.
UIHostingController로 SwiftUI 뷰 임베드하기
import SwiftUI
import UIKit
struct MySwiftUIView: View {
var body: some View {
Text("SwiftUI View in UIKit")
.font(.title)
.padding()
}
}
class MyUIKitViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// SwiftUI View를 UIHostingController로 감싸기
let swiftUIView = MySwiftUIView()
let hostingController = UIHostingController(rootView: swiftUIView)
// UIKit ViewController에 추가
addChild(hostingController)
hostingController.view.frame = view.bounds
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
}
}
포인트:
- UIHostingController는 SwiftUI 뷰를 UIKit 환경에서 사용할 수 있도록 감싸주는 역할을 합니다.
- SwiftUI 뷰를 UIKit 뷰 계층에 추가하는 방식은 UIKit 개발자에게 익숙한 패턴을 제공합니다.
3. UIKit과 SwiftUI 간 데이터 공유
@ObservedObject
와 데이터 바인딩 활용
UIKit과 SwiftUI 간 데이터 공유는 상태 관리가 핵심입니다. SwiftUI에서는 ObservableObject와 Binding을 활용해 데이터 흐름을 관리할 수 있습니다.
import SwiftUI
class CounterModel: ObservableObject {
@Published var count: Int = 0
}
struct CounterView: View {
@ObservedObject var model: CounterModel
var body: some View {
VStack {
Text("Count: \(model.count)")
Button("Increment") {
model.count += 1
}
}
.padding()
}
}
class CounterViewController: UIViewController {
let model = CounterModel()
override func viewDidLoad() {
super.viewDidLoad()
let counterView = CounterView(model: model)
let hostingController = UIHostingController(rootView: counterView)
addChild(hostingController)
hostingController.view.frame = view.bounds
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
}
}
중요 포인트:
@ObservedObject
를 사용해 SwiftUI와 UIKit 사이에서 데이터 변경 사항을 실시간으로 동기화할 수 있습니다.- 데이터 변경은 SwiftUI 뷰와 UIKit 뷰 모두에서 반영됩니다.
4. SwiftUI와 UIKit 간 이벤트 처리: Coordinator 활용
UIKit의 Delegate 패턴과 SwiftUI의 선언형 구조를 연결하려면 Coordinator 패턴을 사용합니다. 이를 통해 UIKit 이벤트를 SwiftUI와 연결할 수 있습니다.
import SwiftUI
struct ButtonView: UIViewRepresentable {
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setTitle("Tap Me", for: .normal)
button.addTarget(context.coordinator, action: #selector(Coordinator.didTapButton), for: .touchUpInside)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator: NSObject {
@objc func didTapButton() {
print("Button tapped!")
}
}
}
이 방법을 통해 UIKit의 버튼 이벤트를 SwiftUI에서 처리할 수 있습니다.
5. UIKit에서 SwiftUI를 최대로 활용하는 팁
- UIViewControllerRepresentable 사용
UIKit의 기존 뷰를 SwiftUI에서 사용하려면UIViewControllerRepresentable
을 활용하세요. - 점진적 리팩토링
SwiftUI를 사용하여 점진적으로 UIKit 프로젝트를 리팩토링하면 기술 부채를 줄일 수 있습니다. - 동적 데이터 전달
ObservableObject와 EnvironmentObject를 적절히 활용하여 데이터를 전달하세요.
6. 결론
UIKit에서 SwiftUI를 사용하는 것은 iOS 앱 개발에서 더 많은 가능성을 열어줍니다. UIHostingController를 통해 SwiftUI 뷰를 통합하고, 상태 관리 및 이벤트 처리를 효율적으로 설계하면 SwiftUI의 이점을 극대화할 수 있습니다.
SwiftUI의 선언형 방식과 UIKit의 유연성을 결합하여 더 나은 사용자 경험을 제공하세요!