Integrating SwiftUI with UIKit apps for early adoption

SwiftUI was introduced in iOS 13 in a time many of us have a big app built with UIKit. SwiftUI makes us enthusiastic just like when Swift was introduced to get started with this new framework for building apps.

When Swift was introduced we had to migrate from Objective-C to Swift. A lot of apps are most likely still using (partly) Objective-C as a transition can take quite some time. It’s good to know how to start the transition to make this process as short as possible. iOS 14 is getting close and you might consider dropping iOS 12 later this year which allows you to write new views in SwiftUI. Therefore, a blog post to explain how you can use SwiftUI within a UIKit application.

Adding SwiftUI views in a UIKit application early on makes your future self a lot happier.

The benefits of using SwiftUI as soon as possible in a UIKit app

Transitioning from a UIKit app to SwiftUI is time-consuming and will probably take years for larger apps. Apps written in Objective-C had to take the same transition when Swift was introduced and are likely still not completely moved over. Therefore, adopting SwiftUI early on comes with a few benefits:

  • No need to rewrite your new views in the future to SwiftUI
  • You start learning SwiftUI while developing as you normally do
  • The transition to SwiftUI takes time. The earlier you start, the earlier your app will be completely written in SwiftUI

Things to consider when adopting SwiftUI in a UIKit application

It’s important to know that you can only use SwiftUI on iOS 13 and up. On top of that, SwiftUI is a new technology that has been improved in later updates. Dropping iOS 12 means you can start with SwiftUI but it also means that you start using the first version of SwiftUI. This could lead to unexpected behaviour and bugs from the early days.

Using SwiftUI for new features only

Regarding this matter you could decide to use SwiftUI for new features only. This scenario is possible even if you’re not dropping iOS 12. You will use the availability APIs which makes your newly written views available only on the versions that support SwiftUI.

if #available(iOS 13.0, *) {
    presentSwiftUIView()
} else {
    // Fallback on earlier versions
}

You can decide to deliver new features with SwiftUI which makes that code future proof as it takes away the need to rewrite it in a few years. This obviously only works if you can decide to make a certain feature only available to users on iOS 13 and up.

Presenting a SwiftUI view in a UIKit view controller

Once you have a feature you’d like to develop with SwiftUI it’s time to integrate it using the SwiftUI framework. SwiftUI introduced an instance called UIHostingController that takes the responsibility of “hosting” a SwiftUI view.

UIHostingController itself inherits from UIViewController which makes it possible to work with it like you would with any other view controller instance. Therefore, the code to present a SwiftUI view is as simple as this:

func presentSwiftUIView() {
    let swiftUIView = SwiftUIView()
    let hostingController = UIHostingController(rootView: swiftUIView)
    present(hostingController, animated: true, completion: nil)
}

Adding a SwiftUI view to a UIKit view

The same technique applies to adding a SwiftUI view to a UIKit view hierarchy. The only difference is that you’re not presenting the view but adding it as a child view controller:

func addSwiftUIView() {
    let swiftUIView = SwiftUIView()
    let hostingController = UIHostingController(rootView: swiftUIView)

    /// Add as a child of the current view controller.
    addChild(hostingController)

    /// Add the SwiftUI view to the view controller view hierarchy.
    view.addSubview(hostingController.view)

    /// Setup the constraints to update the SwiftUI view boundaries.
    hostingController.view.translatesAutoresizingMaskIntoConstraints = false
    let constraints = [
        hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
        hostingController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
        view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor),
        view.rightAnchor.constraint(equalTo: hostingController.view.rightAnchor)
    ]

    NSLayoutConstraint.activate(constraints)

    /// Notify the hosting controller that it has been moved to the current view controller.
    hostingController.didMove(toParent: self)
}

There are a few things that we do here:

  • First, we add the hosting controller as a child to the current view controller
  • The view is added to the view hierarchy of the current view controller
  • Constraints are set up in code to update the boundaries of our SwiftUI view. You can learn more about writing Auto Layout in code here
  • The hosting controller is notified that its containing SwiftUI view has moved to a parent

Creating an extension to simplify adding SwiftUI views

To make this more efficient we can wrap this logic inside an extension on UIViewController. It allows us to write the same code as above as follows:

func addSwiftUIView() {
    let swiftUIView = SwiftUIView()
    addSubSwiftUIView(swiftUIView, to: view)
}

The extension for this looks mostly the same as our code before:

extension UIViewController {

    /// Add a SwiftUI `View` as a child of the input `UIView`.
    /// - Parameters:
    ///   - swiftUIView: The SwiftUI `View` to add as a child.
    ///   - view: The `UIView` instance to which the view should be added.
    func addSubSwiftUIView<Content>(_ swiftUIView: Content, to view: UIView) where Content : View {
        let hostingController = UIHostingController(rootView: swiftUIView)

        /// Add as a child of the current view controller.
        addChild(hostingController)

        /// Add the SwiftUI view to the view controller view hierarchy.
        view.addSubview(hostingController.view)

        /// Setup the contraints to update the SwiftUI view boundaries.
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        let constraints = [
            hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
            hostingController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
            view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor),
            view.rightAnchor.constraint(equalTo: hostingController.view.rightAnchor)
        ]

        NSLayoutConstraint.activate(constraints)

        /// Notify the hosting controller that it has been moved to the current view controller.
        hostingController.didMove(toParent: self)
    }
}

This makes it really easy to add views to your UIKit view hierarchy and even allows you to add SwiftUI views to container views defined in the interface builder.

Conclusion

Adding SwiftUI views in a UIKit application early on makes your future self a lot happier as you don’t have to rewrite it later on again. With a simple UIViewController extension method you can easily add a view in a few lines of code. Decide whether or not you can build a new feature with SwiftUI and make use of the UIHostingController to present SwiftUI views.

If you like to improve your SwiftUI knowledge, even more, check out the SwiftUI category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!