UIKit에서 SwiftUI를 사용하는 방법: 통합 가이드

SwiftUI는 선언적 문법과 뛰어난 성능으로 iOS 개발자 사이에서 빠르게 인기를 얻고 있습니다. 그러나 기존 UIKit 프로젝트에 SwiftUI를 통합하려면 몇 가지 기술적인 이해가 필요합니다.


1. UIKit과 SwiftUI의 차이점과 통합의 이점

UIKit은 Imperative(명령형) 스타일로, 개발자가 UI의 세부 동작을 하나하나 지정해야 합니다. 반면, SwiftUI는 Declarative(선언형) 스타일로 UI 구성과 데이터 흐름을 간결하게 관리할 수 있습니다.

SwiftUI를 UIKit에 통합해야 하는 이유

  1. 최신 기능 활용: SwiftUI는 새로운 iOS 버전에 맞춰 혁신적인 기능을 제공합니다.
  2. 생산성 향상: 코드 작성이 간결해지고 유지보수가 용이합니다.
  3. 점진적 전환 가능: 기존 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를 최대로 활용하는 팁

  1. UIViewControllerRepresentable 사용
    UIKit의 기존 뷰를 SwiftUI에서 사용하려면 UIViewControllerRepresentable을 활용하세요.
  2. 점진적 리팩토링
    SwiftUI를 사용하여 점진적으로 UIKit 프로젝트를 리팩토링하면 기술 부채를 줄일 수 있습니다.
  3. 동적 데이터 전달
    ObservableObject와 EnvironmentObject를 적절히 활용하여 데이터를 전달하세요.

6. 결론

UIKit에서 SwiftUI를 사용하는 것은 iOS 앱 개발에서 더 많은 가능성을 열어줍니다. UIHostingController를 통해 SwiftUI 뷰를 통합하고, 상태 관리 및 이벤트 처리를 효율적으로 설계하면 SwiftUI의 이점을 극대화할 수 있습니다.

SwiftUI의 선언형 방식과 UIKit의 유연성을 결합하여 더 나은 사용자 경험을 제공하세요!

Leave a Comment