FREE TRIAL: Accelerate your app development career with Hacking with Swift+! >>

Wrapping a UIViewController in a SwiftUI view

Paul Hudson    @twostraws   

SwiftUI is a really fantastic framework for building apps, but right now it’s far from complete – there are many things it just can’t do, so you need to learn to talk to UIKit if you want to add more advanced functionality. Sometimes this will be to integrate existing code you wrote for UIKit (for example, if you work for a company with an existing UIKit app), but other times it will be because UIKit and Apple’s other frameworks provide us with useful code we want to show inside a SwiftUI layout.

In this project we’re going to ask users to import a picture from their photo library. UIKit comes with dedicated code for doing just this, but that hasn’t been ported to SwiftUI and so we need to write that bridge ourself.

Before we write the code, there are three things you need to know, all of which are a bit like UIKit 101 but if you’ve only used SwiftUI they will be new to you.

First, UIKit has a class called UIView, which is the parent class of all views in the layouts. So, labels, buttons, text fields, sliders, and so on – those are all views.

Second, UIKit has a class called UIViewController, which is designed to hold all the code to bring views to life. Just like UIView, UIViewController has many subclasses that do different kinds of work.

Third, UIKit uses a design pattern called delegation to decide where work happens. So, when it came to deciding how our code should respond to a text field’s value changing, we’d create a custom class with our functionality and make that the delegate of our text field.

SwiftUI makes us structure our code very differently, not least that we mostly use structs for views rather than classes. However, SwiftUI kind of blends UIView and UIViewController into a single View protocol, which makes our code much simpler.

Anyway, all this matters because UIKit’s system of asking the user to select a photo from their library uses a view controller called UIImagePickerController, and delegate protocols called UINavigationControllerDelegate and UIImagePickerControllerDelegate. SwiftUI can’t use these two directly, so we need to wrap them.

We’re going to start simple and work our way up. Wrapping a UIKit view controller requires us to create a struct that conforms to the UIViewControllerRepresentable protocol. This builds on View, which means the struct we’re defining can be used inside a SwiftUI view hierarchy, however we don’t provide a body property because the view’s body is the view controller itself – it just shows whatever UIKit sends back.

Conforming to UIViewControllerRepresentable does require us to implement two methods: one called makeUIViewController(), which is responsible for creating the initial view controller, and another called updateUIViewController(), which is designed to let us update the view controller when some SwiftUI state changes.

These methods have really precise signatures, so I’m going to show you a neat shortcut. The reason the methods are long is because SwiftUI needs to know what type of view controller our struct is wrapping, so if we just straight up tell Swift that type Xcode will help us do the rest.

Press Cmd+N to make a new file, choose Swift File, and name it ImagePicker.swift. Add import SwiftUI to the top of the new file, then give it this code:

struct ImagePicker: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIImagePickerController

That isn’t enough code to compile correctly, but when Xcode shows an error saying “Type ImagePicker does not conform to protocol UIViewControllerRepresentable”, please click the red and white circle to the left of the error and select “Fix”. This will make Xcode write the two methods we actually need, and in fact those methods are actually enough for Swift to figure out the view controller type so you can delete the typealias line.

You should have a struct like this:

struct ImagePicker: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {

We aren’t going to be using updateUIViewController(), so you can just delete the “code” line from there so that the method is empty.

However, the makeUIViewController() method is important, so please replace its existing “code” line with this:

let picker = UIImagePickerController()
return picker

We’ll add some more code to that shortly, but that’s actually all we need to wrap a basic view controller.

Our ImagePicker struct is a valid SwiftUI view, which means we can now show it in a sheet just like any other SwiftUI view. This particular struct is designed to show an image, so we need an optional Image view to hold the selected image, plus some state that determines whether the sheet is showing or not.

Replace your current ContentView struct with this:

struct ContentView: View {
    @State private var image: Image?
    @State private var showingImagePicker = false

    var body: some View {
        VStack {

            Button("Select Image") {
               self.showingImagePicker = true
        .sheet(isPresented: $showingImagePicker) {

Go ahead and run that, either in the simulator or on your real device. When you tap the button the default UIKit image picker should slide up letting you browse through all your photos, and when you select one it will disappear.

However, no image will appear in our view, despite us having just selected one. You see, even though we placed a SwiftUI Image into our view, nowhere do we assign to it the selection from our UIImagePickerController.

To make that happens requires a wholly new concept: coordinators.

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for iOS devs who want to become complete senior developers — from October 18th to 24th. Learn how to apply iOS app architecture patterns through a series of lectures and practical coding sessions.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.4/5

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.