-
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