Better Programming

Advice for programmers.

Follow publication

How to Use ‘UIViewRepresentable’ With SwiftUI

Anupam Chugh
Better Programming
Published in
5 min readMay 8, 2020

Photo by Radek Grzybowski on Unsplash

SwiftUI has brought a paradigm shift in the way we build user interfaces in our applications, but it doesn’t offer native support for all the UI controls right now. Now, one could wonder how to add a UIActivityIndicator, MKMapView, WKWebView, or UIPageControl into their SwiftUI-based applications.

Gladly, SwiftUI provides us with a UIViewRepresentable protocol that allows us to wrap UIKit views and use them from SwiftUI views. There’s also a UIViewControllerRepresentable that lets us integrate UIViewControllers in SwiftUI.

Our goal

  • Understanding how UIViewRepresentable works and exploring its lifecycle
  • Working with Coordinators to pass data from UIKit view to SwiftUI. We’ll see how to embed and interact with a UISearchBar from our SwiftUI view.
  • Creating a generic wrapper that lets us quickly integrate any UIView in the SwiftUI interface

‘UIViewRepresentable’ Protocol

Adopting UIViewRepresentable in our custom instances lets us create and manage UIKit views from the SwiftUI interface. The simplest implementation requires you to handle two methods: makeUIView andupdateUIView.

Here’s an example of how to use them when embedding a UIActivityIndicator in SwiftUI:

  • The makeUIView allows us to set up our representable view (UIActivityIndicatorView, in our case). It’ll be called only once during the SwiftUI view’s lifecycle.
  • The updateUIView gets triggered whenever its enclosing SwiftUI view changes its state. Inside this method, you can do changes to the view information. For getting data from the SwiftUI view, we leveraged a Binding property wrapper — it can read and write a value owned by a source of truth (State, in our case).

Let’s look at a simple SwiftUI application that shows and hides the UIActivityIndicatorView on a button click.

In the @Binding property wrapper, let’s define an explicit dependency where its initial value is derived from a SwiftUI state. So whenever the button is pressed, the @State gets toggled, and, accordingly, the binding value changes, thereby triggering the updateUIView.

Working With Coordinators

While the @Binding property wrapper helps communicate data from SwiftUI to the UIKit view, sometimes we need to do it the other way round as well.

SwiftUI’s coordinators act as the bridge between UIKit and a SwiftUI view. They allow us to communicate UIKit view changes to the SwiftUI interface by using delegates or target actions. Coordinator is a class in which we can implement the UIKit view protocol delegates.

UIKit controls have their own delegate methods that let us add annotations to MKMapView, for instance, or update the current page index in a UIPageViewController. We can fulfill these delegate methods using the coordinator and invoke the updateUIView method using a binding value.

Here’s the code of a custom Coordinator class that implements the UISearchBarDelegate protocol to notify text updates to the SwiftUI representable view:

Next up, let's add the makeCoordinator() method into the UIViewRepresentable wrapper structure.

Inside the makeUIView method, we’ve set the coordinator property present in the context argument to the delegate property of the UISearchControl.

In the above code, every time the Coordinator class’s UISearchBarDelegate is triggered, the text is passed onto the updateUIView, which eventually updates the UISearchBar view. Here’s a screengrab of the SwiftUI application, in which the UISearchBar is added onto a SwiftUI list.

The ‘UIViewRepresentable’ Lifecycle

Now that we’ve seen how the UIKit views are embedded in SwiftUI and how dataflow works using bindings and coordinators to update the views, let’s visualize the lifecycle of a UIKit representable view:

The dismantleUIView behaves like a deinit for the UIKit representable view. You can do the cleanup work — such as removing notification observers, timer invalidation, etc. — in it.

The lifecycle of a UIViewController inside a SwiftUI view works the same way and has similar function signatures — replace makeUIView with makeUIViewController and so on.

Generic ‘UIViewRepresentable’

In the previous sections, we saw how adding a simple UIActivityIndicatorView and UISearchBar required creating separate wrapper structures.

Not only does it increase the boilerplate code, but it also splits the view logic —for example, the UIActivityIndicatorView was instantiated in SwiftUI, and the animation toggle logic resided in the UIViewRepresentable.

Fortunately, we can work around these issues by creating a generic UIViewRepresentable structure that lets you embed any UIKit view. Let’s write a generic type that can wrap any UIView in the UIViewRepresentable:

The @autoclosure attribute applied to the closure parameter above automatically creates a closure from the expression you pass in. It helps in deferring the execution of the closure until it’s actually needed.

Using the above generic Anything wrapper, we can embed any UIKit view in SwiftUI. Of course, to create a custom coordinator instance, you can extend the above generic and set up the UI control-specific delegates.

Here’s a look at how the UIActivityIndicatorView is embedded using the Anything wrapper in the SwiftUI body:

Anything(UIActivityIndicatorView(style: .large)) {
if self.shouldAnimate {
$0.startAnimating()
} else {
$0.stopAnimating()
}
}

In the following illustration, we’ve wrapped four more UIKit controls in the generic wrapper — UISearchBar, a custom charts UI, a UITextView, and a UIPageControl.

Conclusion

So we saw how UIViewRepresentable helps in integrating the missing SwiftUI controls using a UIKit wrapper structure. Subsequently, we worked with coordinators to update the SwiftUI view from the UIKit and saw the lifecycle of events. Finally, we created a generic wrapper for UIViews.

The full source code is available in this GitHub repository.

Thanks for reading. If you’re looking to get started with the Combine framework, here’s a good starting point for you:

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app