За годы работы разработчиком iOS, я собрал множество инструментов и полезных штук, которые облегчают процесс разработки. В этой статье, я хочу поделиться одним из таких инструментов. Это будет не большая статья. Я покажу, как пользоваться этой утилитой, продемонстрирую её в действии. Надеюсь, что статья окажется полезной для вас.
Как можно начать переезжать на SwiftUI? Постепенно. Переписывать целый экран слишком дорого по времени, а вот переписать ячейку или кнопку куда быстрее. Таким способом можно мигрировать на SwiftUI, шаг за шагом переписывая экран. В этом поможет класс HostingView.
#if canImport(UIKit)
import UIKit
import SwiftUI
/// HostingView allows you to use SwiftUI View in UIKit code
///
/// Example:
///
///```swift
///
/// // SwiftUI
/// struct SomeView: View {
///
/// var body: some View {
/// Text("Hello World!")
/// }
/// }
///
/// // UIKit
/// class RootViewController: UIViewController {
///
/// override func viewDidLoad() {
/// super.viewDidLoad()
///
/// // SwiftUI View -> UIView
/// let swiftUIView = SomeView()
/// let uiKitView = HostingView(rootView: swiftUIView)
///
/// view.addSubview(uiKitView)
///
/// // Without this, there may be problems with safeArea in SwiftUI View
/// uiKitView.addChildControllerTo(self)
/// }
/// }
/// ```
///
/// Warning:
/// iPhone models without SafeArea may experience extra ridges.
/// There are two ways to solve the problem: either call
/// the HostinView method hostingView.addChildControllerTo(self)
/// or use the .ignoresSafeArea() method in SwiftUI View.
public final class HostingView<T: View>: UIView {
private(set) var hostingController: UIHostingController<T>
public var rootView: T {
get { hostingController.rootView }
set { hostingController.rootView = newValue }
}
public init(rootView: T, frame: CGRect = .zero) {
hostingController = UIHostingController(rootView: rootView)
super.init(frame: frame)
backgroundColor = .clear
hostingController.view.backgroundColor = backgroundColor
hostingController.view.frame = self.bounds
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(hostingController.view)
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func addChildControllerTo(_ controller: UIViewController) {
controller.addChild(hostingController)
hostingController.didMove(toParent: controller)
}
public func removeChildControllerTo(_ controller: UIViewController) {
hostingController.willMove(toParent: nil)
hostingController.removeFromParent()
}
}
#endif
Пример Использования
Рассмотрим простой пример, демонстрирующий использование HostingView
для встраивания SwiftUI View внутри класса UIViewController
.
Создание SwiftUI View:
struct SomeView: View {
var body: some View {
Text("Hello World!")
}
}
Интеграция в UIKit:
class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swiftUIView = SomeView()
let view = HostingView(rootView: swiftUIView)
}
}
Особенности
Встроенный UIHostingController:
HostingView
используетUIHostingController
для рендеринга SwiftUI Views. Это обеспечивает точное отображение и поведение SwiftUI компонентов внутри UIKit.Поддержка Автоизменения Размеров: Компоненты SwiftUI автоматически изменяют свои размеры под контейнер UIKit, что обеспечивает гибкость и удобство при разработке.
Что нужно учитывать?
Важно учитывать особенности Safe Area на разных моделях iPhone. На моделях без safeArea может появиться отступ. Решить проблему можно двумя путями, или вызвать у HostinView методhostingView.addChildControllerTo(self)
, или использовать метод .ignoresSafeArea()
в SwiftUI View .
Заключение
Этот подход позволяет разработчикам постепенно переходить к использованию SwiftUI, минимизируя риски и издержки, связанные с полной перепиской приложений.
Еще статьи Swift Utilities:
Комментарии (4)
Gargo
03.12.2023 11:05плохо, что UIView хранит в себе UIViewController. Может стоило завести какой-нибудь контейнер, который хранит в себе эти две сущности хотя бы на одном уровне?
VAnsimov Автор
03.12.2023 11:05Контейнером мы усложним себе жизнь и какой-то серьезной проблемы не решим. В любом случае, каждый может модифицировать и менять под свои нужды????
house2008
А разве не нужно вызывать на контроллере ?
Вы же вью UIHostingController добавили во вью другого контроллера. Вроде при добавлении как child там происходит правильная синхронизация life cycle-ов контроллеров. Не скажу, что большой опыт интеграции SwiftUI + UIKit, но без этого вроде иногда были проблемы с safe area и еще какие-то мелкие баги. Но признаю, что есть участки кода и без явного добавления как child и вроде работает.
VAnsimov Автор
Да, про это я не упоминал в статье. Спасибо что подсказали. Поправил статью и расширил код чтобы было удобнее с этим работать