Getting started with UIKit in SwiftUI and vice versa

SwiftUI can work together with UIKit by making it easier to add SwiftUI views to UIKit and visa versa using a few extensions shared in this blog post. SwiftUI was introduced in iOS 13 at 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 released and you might consider dropping iOS 12 which allows you to write new views in SwiftUI. Therefore, a blog post to explain how you can use SwiftUI within a UIKit application and existing UIKit views in SwiftUI.

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

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.

Adding a UIKit View to a SwiftUI View

Obviously, we also want to know how to add UIKit views to a SwiftUI view. We probably have a lot of existing code that we don’t want to throw away but rather use in our newly build views. I’ve earlier explained how you can use Xcode Previews with existing UIKit views which we’ll take to the next level in this post.

To make it easier to work with UIView elements in SwiftUI I’ve created a convenience library called SwiftUIKitView. Explaining the code from this library will be a lot to cover but instead I can explain to you how you can make use of this framework.

Adding a UIKit UIView to SwiftUI

By importing SwiftUIKitView we can make use of a new method called swiftUIView(layout:) which will convert our UIView into a SwiftUI View:

import SwiftUI
import SwiftUIKitView

struct SwiftUIwithUIKitView: View {
    var body: some View {
        UIKitView() // <- This is a `UIKit` view.
            .swiftUIView(layout: .intrinsic) // <- This is a SwiftUI `View`.
    }
}

The framework allows setting different kinds of layouts:

  • Intrinsic: auto size to the intrinsic size of the UIView
  • Fixed width: combine an intrinsic height with a fixed width
  • Fixed size: apply a fixed size to the UIView

Depending on your use-case you can pick the layout you need.

Using Writable Key Paths references to update properties

We can make use of the KeyPathReferenceWritable protocol that comes with the framework to update the views in a way we’re used to with SwiftUI:

import SwiftUI
import SwiftUIKitView

struct SwiftUIwithUIKitView: View {
    var body: some View {
        UILabel() // <- This is a `UIKit` view.
            .swiftUIView(layout: .intrinsic) // <- This is a SwiftUI `View`.
            .set(\.text, to: "Hello, UIKit!")
            .fixedSize()
    }
}

The swiftUIView method returns a UIViewContainer instance that conforms to this protocol by default. As you can see in this example we can set the text of the label by using the set(_, to:) method. Any writable property can be updated this way.

Using the same technique to create Xcode Previews of UIView elements

Another big benefit of using the SwiftUIKitView framework is that you can create Xcode Previews of existing UIView elements in your project.

struct UILabelExample_Preview: PreviewProvider {
    static var previews: some View {
        UILabel() // <- This is a `UIKit` view.
            .swiftUIView(layout: .intrinsic) // <- This is a SwiftUI `View`.
            .set(\.text, to: "Hello, UIKit!")
            .preview(displayName: "UILabel Preview Example")
    }
}

This results in the following preview:

An Xcode Preview of a UIKit UIView element.
An Xcode Preview of a UIKit UIView element.

This will make it absolutely easier for you to start implementing SwiftUI in existing UIKit projects. There are many reasons why it’s important to start thinking about adopting SwiftUI in existing projects today.

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

At WeTransfer, we’ve been started this transition not long ago to make steps forward as soon as possible. You can read more about our early conclusions in this blog post.

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.

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!