Using UIKit & SwiftUI Together
By: Riccardo Cipolleschi
Tags: Swift, Swiftui, Apple, iOS, Mobile App Development

A year has passed and SwiftUI has progressed a lot. They implemented lazy containers to speed up the performances, the technology is more stable and more mature, and with the release of iOS 14, the majority of users can benefit from SwiftUI and Combine.

Today, it’s safer to adopt SwiftUI. Despite this, many apps are still too complex to be developed easily with this framework: imagine that you want to write a complex video editor (like Splice). At the moment, it will be very hard to achieve that very same level of detail and precision.

Nonetheless, the benefits of SwiftUI are real. It is extremely fast to develop simple screens, like a setting screen or a screen to deliver simple information to the user. So, why not try to use this tool for these screens and rely on UIKit for the usual complex screens? In today’s article, I’d like to show you how UIKit and SwiftUI can work together!

The App

First of all, let’s see what is our goal: we want to build a simple screen in SwiftUI and present it on top of a UIKit View Controller ( VC ). I decided to start from the code of last week’s article. The SwiftUI’s view contains some useful facts about the importance of drinking water and a button to support the app (we are not going to implement the full payment flow!)

This is a small video of the final result:

SwiftUI View

Let’s start by implementing our view in SwiftUI.

In Xcode, Press ⌘+N to create a new file. In the filter, in the top-right corner, type SwiftUI . Select the Swift file for the SwiftUI view and press Next . Type a name for the view (I choose to call it InfoView ).

Xcode will prepare the template for a SwiftUI view, including its preview. All the code that is rendered on the screen is the View returned by the body property. So, let’s start implementing our body property:

At the moment, this view is just a static view. We will see in a while how can we plug some real interaction. Notice that the Button ’s action closure just contains a print statement to verify that everything is working.

Note: I’m still learning and levelling up my SwiftUI skills. So if you know any other, and more efficient, way to write this view, please reach out to me on Linkedin: I’d be super happy to discuss the topic together!

The ViewController

SwiftUI view can be rendered on screen by using a special UIKit ’s ViewController called UIHostingController . This controller is generic in the SwiftUI view renders and it takes care of the whole view’s lifecycle.

We need to create a custom subclass of a UIHostingControl to push it on top of our DrinkVC . So:

Press ⌘+N to create a new Swift file. Select Swift File and press Next . Give a name to the file (in my case, I used InfoVC ). Copy the following code into the file

This is all you need to do to create a VC that is able to render a SwiftUI view. We add an empty init to create the SwiftUI view and the required initializer.

To connect it with the previous VC, open the DrinkVC.swift file and add the following code to the setupInteraction() method.

The simple, good-old present method will get the job done. If we run the code now, we should be able to see the DrinkIt VC and, by tapping on the i button, the SwiftUI view should appear. Of course, we have not implemented any logic, therefore neither the dismiss button nor the support button will work. (However, you can swipe down the modal view to dismiss it!)

Implementing the Logic

There are several possibilities to wire everything together. I decided to use an MVVM approach to organize my code better. Furthermore, SwiftUI is tightly coupled with the Combine framework: by using the ObservableObject protocol and the @Published and the @ObservedObject property wrappers, we can achieve an automatic update of the UI when our model changes, in a pure MVVM spirit.

The ViewModel

As the first thing, let’s implement the ViewModel ( VM ). The VM contains the data required by the view, along with some logic to make them easier to consume. It has to conform to the ObservableObject so that it can contain some Combine’s publishers, and therefore it must be a class .

It can also contain some functions that the view can invoke. So go to the InfoVC.swift file and add this code at the bottom of the page.

In this VM we have:

A couple of closures that will be populated by the VC and that the view could use to respond when the button is pressed.

and that the view could use to respond when the button is pressed. a list of WaterFact to configure the InfoView .

to configure the . the publisher for the number of people that are supporting the app.

The @Publisher annotation is important: it is part of the machinery to trigger the automatic re-rendering of the view when that value changes.

At the bottom of the VM , you can find a support struct to organize the WaterFact . We are going to use a ForEach control in our InfoView, so it has to conform to Hashable .

Connecting the VM With the VC

Let’s connect the VC with the VM . In the InfoVC.swift file:

In the init, in line 3 , you can see a couple of changes. First of all, we are creating the VM and passing it to the SwiftUI view. Then, we set up the interactions within the view model, basically updating the closures within the VM itself.

Note: the SwiftUI view will retain a strong reference to our VM . Therefore, to avoid memory leaks, it’s better not to hold a reference in the VC but let the SwiftUI view handle it for us.

At the moment, our code is not compiling: the SwiftUI view still does not know anything about the VM . This is the last step to make everything work.

Connecting the VM With the View

Now, let’s open the InfoView.swift file. We are going to update our view to:

Render the data contained in the VM .

. Execute some logic.

I commented out the code that has not changed with respect to the previous snippet of code. Let’s focus on the new parts:

We add a model property that holds our view model. It has the @ObservedObject property wrapper that enables the automatic update of SwiftUI ( line 4 .) We connect the dismiss closure of the VM with the close button ( line 14 .). We can leverage the functional paradigm to pass the function as if it was a parameter. We remove the five separate calls to waterReason leveraging the ForEach construct ( line 24 .) We connect the supportingPeople property of the VM with the incentivizing label ( line 31 .) We connect the supportApp closure of the VM with the support button ( line 35 .) Again, we leverage the functional approach to pass the closure as a parameter.

And that’s it. If we run our app now, we should be able to present the VC , support the app, and dismiss it!

Conclusion

SwiftUI is a powerful tool to quickly iterate over simple views and controls. It still not suitable for all the use cases, but why shouldn’t we benefit from the advantages it provides?

In today's article, we explored a way to have UIKit and SwiftUI living together in our apps. The code of this article can be found at this link. Beware to check out the right branch!

The presented approach is not the only way to achieve this result. Feel free to explore and test different approaches. I’ll be very happy if you would like to share them with me. You can reach me out on Linkedin.