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:
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.
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.
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.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's all new Paywall Editor allow you to remotely configure your paywall view without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.