Customized Camera in SwiftUI

Selin Acar
Mac O’Clock
Published in
5 min readOct 12, 2020

--

Thought I should let the world know how I created a custom camera UI on SwiftUI since most of the articles that I'm seeing are related to Swift, not SwiftUI.

1. Set up the camera/image picker

If you’ve done this, cool, you can move on to step 2. Else, this tutorial helped get me all set up and might help you too.

2. Add a variable to toggle camera capture and image gallery selection

In the parent struct’s initializer for the image picker, add in a variable to help toggle between the camera capture and selecting an image from the photo library.

/* THE PARENT STRUCT */@State var mode: UIImagePickerController.SourceType = .cameravar body: some View {
...
ImagePicker(image: $image, mode: $mode)
...
}

And in the Image picker struct, create a corresponding binding variable:

/* THE IMAGE PICKER STRUCT */@Binding var mode: UIImagePickerController.SourceType

3. Turn off the native camera buttons

In my project where I put the camera, the controls didn't seem right within its view so I wanted to customize it. The first thing for customizing is to turn off the native camera controls:

In the struct that conforms to UIViewControllerRepresentable, within the makeUIViewController method (the method that sets up the image picker view), set the .showsCameraControls property (of the UIImagePickerController to be returned by the method), to false:

/* THE IMAGE PICKER STRUCT*/func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {

if (mode == .camera){
picker.showsCameraControls = false

}

Also, in the updateUIViewController method (the method that updates the camera view), set uiViewController.showsCameraControls to false:

/* THE IMAGE PICKER STRUCT*/func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {

//if uiViewController.sourceType == .camera
uiViewController.showsCameraControls = false
}

Now the camera won’t show any controls, and in the next step, you’ll add custom camera control buttons.

4. Create the camera control buttons (capture, flash, etc.)

In the parent view that holds the image picker view, create a button to capture an image.

/* THE PARENT STRUCT */Button(action: {
}) {
Text(“Capture”)
}
.foregroundColor(.purple) //set the color of the button text

Use a @State variable in the parent struct that holds the imagepicker view, and a corresponding @Binding in the imagepicker struct.

Whenever the State variable in the parent view is updated, the binding in the imagepicker struct will be updated as well, and the updateUIViewController method will be called.

Set the button action to update the value of the variable used to signal the imagepicker that an image should be taken.

In the example below, the variable is called didTapCapture.

/* THE PARENT STRUCT */@State var didTapCapture: Bool? = false
...
var body: some View {
Button(action: {
didTapCapture = true
}) {
Text(“Capture”)
}.foregroundColor(.purple)
...
}

In the image picker struct, use the binding variable to determine if a photo should be captured. In the updateUIViewController method, use the .takePicture() method to capture an image if the didTapCapture variable has a value of true.

/* THE IMAGE PICKER STRUCT*/@Binding var didTapCapture: Bool?func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.sourceType = mode
if (mode == .camera){
uiViewController.showsCameraControls = false
if (didTapCapture == true){
uiViewController.takePicture()
}
}

Update the initializer in the parent view struct to account for the new binding variable added:

/* THE PARENT STRUCT */var body: some View {
...
ImagePicker(image: $image, mode: $mode, didTapCapture: $didTapCapture)
...
}

In both methods above, if the source type is .camera, you can also customize/set other camera properties such as .cameraDevice (set to use front/rear camera i.e. .front, .back), or cameraFlashMode (whether or not flash is on during image/video capture i.e. on, off, auto).

Follow this step again (adding a button, State and binding variables, then setting attributes) for adding the ability for flash (.cameraFlashMode).

/* THE PARENT STRUCT */@State var flash: Bool? = falsevar body: some View {
...
ImagePicker(image: $image, mode: $mode, didTapCapture: $didTapCapture, flash: $flash) ... Button(action: {
flash?.toggle()
}) {
if (flash == true){
Text(“Flash On”)
}else{
Text(“Flash off”)
}
}.foregroundColor(.blue)
...}/* THE IMAGE PICKER STRUCT*/@Binding var flash: Bool?func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.sourceType = mode
if (mode == .camera){
uiViewController.showsCameraControls = false
if (didTapCapture == true){
uiViewController.takePicture()
}
if let flashValue = flash{
uiViewController.cameraFlashMode = (flashValue ? .on : .off)
}
...
}

4. Update camera control buttons to images (or images and text)

In the above steps, we set the buttons to have text. We can change this so that the interface is more graphical, by changing that text to images or adding images in addition to the text. You simply just need to put an image where the text is, or next to the text if you want both image and text.

Image next to text:

/* THE PARENT STRUCT */Button(action: {
flash?.toggle()
}) {
if (flash == true){
Image("flashOnImage")
.font(.title)
Text(“Flash On”)

}else{
Image("flashOffImage")
.font(.title)
Text(“Flash off”)
}
}.foregroundColor(.blue)

Image only:

/* THE PARENT STRUCT */Button(action: {
flash?.toggle()
}) {
if (flash == true){
Image("flashOnImage")
.font(.title)

}else{
Image("flashOffImage")
.font(.title)
}
}.foregroundColor(.blue)

Note, if you use .body font, the button won’t work (I haven’t figured that one out yet).

Using the SF symbols app

To help with customizing the buttons of the camera overlay (to replace the buttons for flash, photo gallery etc.), the SF symbols app really helps with seeing what system icons can be used:

To use the icons as buttons, you just use them like so:

Image(systemName: “photo.on.rectangle”)
.font(.title)

So, implementing this for camera flash, from previous steps:

/* THE PARENT STRUCT */Button(action: {
flash?.toggle()
}) {
if (flash == true){
Image(systemName: "bolt.fill")
.font(.title)
}else{
Image(systemName: "bolt.slash.fill")
.font(.title)
}
}.foregroundColor(.blue)

… and Voila! You now have a camera with custom control buttons 📷

The downside of implementing a custom camera this way…

This implementation is not as customizable as an AVFoundation based camera (which gives control over aspect ratio, flash, etc.).
I’ve been out of luck in finding documentation/blog posts/videos for the implementation of an AVFoundation based camera in SwiftUI. But, that might be because I haven’t spent enough time looking things up or I’m just not as experienced as a developer. Either way, thought I should mention it!

That’s all folks!

Comments or notes are all appreciated. Thanks for reading!

--

--