NEW: Learn to build amazing SwiftUI apps for macOS with my new book! >>

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. Apple’s APIs come with dedicated code for doing just this, but that hasn’t been ported to SwiftUI and so we need to write that bridge ourself. Instead, it’s built into a separate framework called PhotosUI, which was designed to work with UIKit and so requires us to look at the way UIKit works.

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:

  1. 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.

  2. 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.

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

All this matters because asking the user to select a photo from their library uses a view controller called PHPickerViewController, and the delegate protocol PHPickerViewControllerDelegate. 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.

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

struct ImagePicker: UIViewControllerRepresentable {

}

That protocol 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 fill in that struct with 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.

Add this code to the struct now:

typealias UIViewControllerType = PHPickerViewController

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: Context) -> PHPickerViewController {
        code
    }

    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
        code
    }
}

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:

var config = PHPickerConfiguration()
config.filter = .images

let picker = PHPickerViewController(configuration: config)
return picker

That creates a new photo picker configuration, asking it to provide us only images, then uses that to create and return a PHPickerViewController that does the actual work of selecting an image.

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 {
            image?
                .resizable()
                .scaledToFit()

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

Go ahead and run that, either in the simulator or on your real device. When you tap the button the default iOS image picker should slide up letting you browse through all your photos.

However, nothing will happen when an image is selected, and the Cancel button won’t do anything either. You see, even though we’ve created and presented a valid PHPickerViewController, we haven’t actually told it how to respond to user interactions.

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

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

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

BUY OUR BOOKS
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.5/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.