Swift Utilities — Integrating SwiftUI into UIKit

Vyacheslav Ansimov
2 min readDec 3, 2023

--

Over the years as an iOS developer, I’ve accumulated a variety of tools and helpful items that make the development process easier. In this article, I want to share one of those tools. This will not be a long article. I’ll show how to use this utility and demonstrate it in action. I hope the article will be useful for you.

How can you start moving to SwiftUI? Gradually. Rewriting an entire screen is too time-consuming, but rewriting a cell or button is much faster. In this way, you can migrate to SwiftUI, gradually rewriting the screen. The HostingView class will help with this.

// 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()
}
}

Usage Example

Let’s look at a simple example that demonstrates how to use HostingView to embed a SwiftUI View inside a UIViewController class.

Creating a SwiftUI View:

struct SomeView: View {
var body: some View {
Text("Hello World!")
}
}

UIKit integration:

class RootViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

let swiftUIView = SomeView()
let view = HostingView(rootView: swiftUIView)
}
}

Peculiarities

Built-in UIHostingController: HostingView uses UIHostingController to render SwiftUI Views. This ensures SwiftUI components display and behave accurately within UIKit.

Auto-Resize Support: SwiftUI components automatically resize to fit the UIKit container, providing flexibility and ease of development.

What to consider?

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

Conclusion

This approach allows developers to gradually transition to using SwiftUI while minimizing the risks and costs associated with complete application rewrites.

More stories

--

--