iOS

UIKit에서 SwiftUI View 활용하기

열목 2023. 1. 2. 01:46

 

UIKit에서 SwiftUI View를 활용하기 위해서는 UIHostingController를 사용하면 됩니다.

 

UIHostingController

  • SwiftUI View 계층 구조를 포함하고 있는 UIViewController
  • 따라서, UIHostingController는 UIKit view controller의 모든 API를 활용할 수 있음.
  • UIKit에서 UIViewController를 사용하고 있는 곳에서 전부 사용 가능.
  • Modal, Navigation 모두 UIViewController 처리하는 것처럼 하면 됨. 굉장히 간단함.

 

 

UIKit에 SwiftUI View를 아래의 방식으로 어떻게 포함시킬 수 있는지 간단한 예시들로 알아보겠습니다.

  • Modal
  • Navigation
  • Embed

 

포함시킬 SwiftUI View 코드

import SwiftUI

struct MySwiftUIView: View {
    var body: some View {
        Text("This is SwiftUI View")
    }
}

 


Modal

import UIKit
import SwiftUI

class MyHostingViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureModal()
    }
    
    func configureAndPresentView() {
        let swiftUIView = MySwiftUIView()
        let hostingController = UIHostingController(rootView: swiftUIView)
        
        // popover view 사이즈가 SwiftUI 콘텐츠에 알맞게 설정됨.
        hostingController.sizingOptions = .preferredContentSize

        hostingController.modalPresentationStyle = .popover
        present(hostingController, animated: true)
    }
}
  • UIHostingController를 사용하기 위해 SwiftUI를 import 해야 함.
  • SwiftUI View를 인스턴스로 만들어서 UIHostingController의 rootView로 처리.
  • UIViewController를 Modal로 띄워줄 때와 마찬가지로 style 적용 후 present()

 

 


Navigation

@objc func configureAndPushView() {
    let swiftUIView = MySwiftUIView()
    let hostingController = UIHostingController(rootView: swiftUIView)
        
    navigationController?.pushViewController(hostingController, animated: true)
}

예를 들어서, UIButton을 만들어주고 target-action 패턴을 사용해서 push 한다면 위와 같이 사용할 수 있겠죠?

 

 


Embed

import UIKit
import SwiftUI

class MyHostingViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureEmbed()
    }
    
    func configureAndEmbedView() {
        let swiftUIView = MySwiftUIView()
        let hostingController = UIHostingController(rootView: swiftUIView)
        
        // Add the hosting controller as a child view controller
        self.addChild(hostingController)
        self.view.addSubview(hostingController.view)
        hostingController.didMove(toParent: self)
        
        // UIHostingController의 위치와 크기 조정
        // layout code
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

Embed하는 과정에서 translatesAutoresizingMaskIntoConstraints 프로퍼티를 처리해주는 것을 깜빡해서 어려움을 겪었습니다. 문제가 발생하면 항상 처음으로 돌아가서 구성해보는 게 중요한 것 같습니다. 이것 때문에 짧게나마 블로그에 작성하는 것이기도 하고요.

 

 


View 업데이트

 단순한 방법으로는 사용자 액션으로 인해 이벤트가 발생할 때마다 수동으로 rootView를 갈아끼워 주는 방법이 있습니다.

 다음으로, 모델 객체가 변경되었을 때, 그것을 알려줄 수 있는 ObservableObject 프로토콜을 채택하고, SwiftUI View에서 @OservedObject를 모델 인스턴스 선언 앞에 명시하여 바인딩해 줌으로써 body를 자동으로 업데이트하여 View를 다시 그릴 수도 있습니다.

 ObservableObject는 해당 객체의 @Published var가 변경되기 전에, 값을 내보내는 Publisher, objectWillChange 인스턴스 프로퍼티를 통해 값을 방출합니다. objectWillChange는 ObservableObject에 기본적으로 구현되어 있는 인스턴스 프로퍼티이므로 별도의 구현 없이 사용할 수 있습니다.

 

objectWillChange 사용 예시 코드(출처: Apple document)

class Contact: ObservableObject {
    @Published var name: String
    @Published var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func haveBirthday() -> Int {
        age += 1
        return age
    }
}

let john = Contact(name: "John Appleseed", age: 24)
cancellable = john.objectWillChange
    .sink { _ in
        print("\(john.age) will change")
}
print(john.haveBirthday())
// Prints "24 will change"
// Prints "25"

 

objectWillChange를 사용하지 않고 모델 객체를 참조하여 변경사항을 업데이트하는 예시

class MyModel: ObservableObject {
    @Published var data: Int
    ...
}

// SwiftUI View
struct MyView: View {
    @ObservedObject var data: MyModel
    
    var body: some View {
    ...
    }
}

 


참고

WWDC 2022 Use SwiftUI with UIKit

(https://developer.apple.com/videos/play/wwdc2022/10072/)

 

Apple document - ObservableObject