ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • UIKit에서 SwiftUI View 활용하기
    iOS 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

     

    'iOS' 카테고리의 다른 글

    Class와 Performance in Swift  (0) 2023.02.02
    UIButton 커스텀(Image & Title)  (0) 2023.02.01
    Actor in Swift concurrency  (0) 2022.12.31
    Diffable DataSource  (2) 2022.12.29
    Localization in iOS  (0) 2022.12.14

    댓글

Designed by Tistory.