SwiftUI 2.0 & UIKit & Storyboard (Part 2)

# Part 2: SwiftUI 2.0 & UIKit
The article is broken in three different parts:

- [Part 1: SwiftUI 2.0 & Storyboard](https://qiita.com/loic-meister-guild/items/61256e5edf06af48eb30)
- Part 2: SwiftUI 2.0 & UIKit [日本語](https://qiita.com/loic-meister-guild/items/a45574e6059209b8c87a)
- [Part 3: Storyboard & SwiftUI 2.0](https://qiita.com/loic-meister-guild/items/dc24c7319baba675564c)

The second part is about using UIKit elements in a SwiftUI 2.0 application.

## Use UIKit elements in SwiftUI
To be able to use UIKit views and view controllers in SwiftUI, They have to be wrapped in types conforming to the [UIViewRepresentable](https://developer.apple.com/documentation/swiftui/uiviewrepresentable) and [UIViewControllerRepresentable](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) protocols.
### The XCode Project
Create a new SwiftUI app project
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/2eb13d16-1b15-5b6c-0a80-963291f9665d.png)
Name the project
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/ddaa13b7-77fd-7404-249b-18395aa0d9da.png)
Create a new __Swift__ file
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/2f326aa3-6637-e3c4-3575-4d9d5696b6e1.png)
Name it
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/28fbef11-e2b6-3d4a-0a62-61689ff347a7.png)
Now we have to create a type conforming to the [`UIViewControllerRepresentable`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) protocol to wrap a UIKit component ...
### ImagePicker: The [`UIViewControllerRepresentable`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) conforming type
First, let's create the `ImagePicker` type to wrap the [`UIImagePickerController`](https://developer.apple.com/documentation/uikit/uiimagepickercontroller).

The [`UIViewControllerRepresentable`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) protocol has 2 requirements:

- a [`makeUIViewController(context:)`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makeuiviewcontroller(context:)) method that creates and configure a `UIViewController`
- an [`updateUIViewController(_:context:)`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/updateuiviewcontroller(_:context:)) method that updates the state of the specified view controller with new information from SwiftUI. **We will leave it blank** as we won't use it for our example.

```Swift:ImagePicker.swift
import Foundation
import SwiftUI
import AVKit

struct ImagePicker: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        // ...
    }
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
    }
}
```

Next, let's create the [`UIImagePickerController`](https://developer.apple.com/documentation/uikit/uiimagepickercontroller) in the [`makeUIViewController(context:)`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makeuiviewcontroller(context:)) method:

```Swift:Swift
    let imagePicker = UIImagePickerController()
```
Then let's configure it:

- Set the user interface for choosing among saved pictures and movies _(To set the user interface for taking a new picture or movie use `.camera` instead)_:

```Swift:Swift
    imagePicker.sourceType = .photoLibrary
```
- Specify the media types to be accessed by the media picker controller, images and movies:

```Swift:Swift
    imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) ?? ["public.image", "public.movie"]
```
- Disable editing of the selected still images and movies:

```Swift:Swift
    imagePicker.allowsEditing = false
```
- Set the video recording and transcoding quality:

```Swift:Swift
    imagePicker.videoQuality = .typeHigh
```
- Set the image picker’s delegate object that will handle the image picker's event notifications:

```Swift:Swift
    imagePicker.delegate = context.coordinator
```
※　We will discuss the delegate object in the next section ...

- And last, return the [`UIImagePickerController`](https://developer.apple.com/documentation/uikit/uiimagepickercontroller):

```Swift:Swift
    return imagePicker
```
Finally, to be able to change the source type when creating an `ImagePicker`, define a variable to hold the default `.photoLibrary` source type to be used during configuration in the [`makeUIViewController(context:)`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makeuiviewcontroller(context:)) method:

```Swift:Swift
struct ImagePicker: UIViewControllerRepresentable {
    var sourceType: UIImagePickerController.SourceType = .photoLibrary
    // ...
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        // ...
        imagePicker.sourceType = sourceType
        imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: sourceType) ?? ["public.image", "public.movie"]

        // ...
    }
    // ...
}
```
The source type of the [`UIImagePickerController`](https://developer.apple.com/documentation/uikit/uiimagepickercontroller) can then be changed at creation time like below:

```Swift:SwiftUI
ImagePicker(sourceType: .camera, /* ... */)
```

### Coordinator: The [`UIImagePickerController`](https://developer.apple.com/documentation/uikit/uiimagepickercontroller)'s delegate
To communicate changes occurring within the view controller to other parts of the SwiftUI interface, a custom **coordinator** object must be provided to the SwiftUI view.

To listen to changes in UIKit components, delegates are used. Delegates handle event notifications before or after the event itself has been handled by the system.

And, to listen to [`UIImagePickerController`](https://developer.apple.com/documentation/uikit/uiimagepickercontroller) changes our `Coordinator` will have to conform to the [`UIImagePickerControllerDelegate`](https://developer.apple.com/documentation/uikit/uiimagepickercontrollerdelegate) and [`UINavigationControllerDelegate`](https://developer.apple.com/documentation/uikit/uinavigationcontrollerdelegate) protocols, as well as inherit from `NSObject` to conform to the [`NSObjectProtocol`](https://developer.apple.com/documentation/objectivec/nsobjectprotocol) protocol which cannot be declared in Swift.

Let's declare our `Coordinator` type with a constructor setting a reference to the `ImagePicker` instance whose changes it observes:
 
```Swift:Swift
struct ImagePicker: UIViewControllerRepresentable {
    // ...
    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parent: ImagePicker
        init(_ parent: ImagePicker) {
            self.parent = parent
        }
        // ...
    }
    // ...
}
```
##### [`imagePickerController(_:didFinishPickingMediaWithInfo:)`](https://developer.apple.com/documentation/uikit/uiimagepickercontrollerdelegate/1619126-imagepickercontroller)
To catch the "user picked an image or a movie" event notification, we need to implement the [`imagePickerController(_:didFinishPickingMediaWithInfo:)`](https://developer.apple.com/documentation/uikit/uiimagepickercontrollerdelegate/1619126-imagepickercontroller) method telling the delegate that the user picked a still image or a movie.

In it, we check whether the user picked an image or a movie:

- If the user picked an image, the `info` parameter `UIImagePickerController.InfoKey.originalImage` contains an `UIImage` object.
- If the user picked a movie, the `info` parameter `.mediaURL` contains an `URL` object.

```Swift:Swift
struct ImagePicker: UIViewControllerRepresentable {
    // ...
    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        // ...
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
                // ...
            }
            if let videoURL = info[.mediaURL] as? URL {
                // ...
            }
            // ...
        }
    }
    // ...
}
```
To communicate to the SwiftUI interface what files the user picked, we chose to use a binding to an external `Array` of `URL`s passed as argument to the `ImagePicker` at creation time.

```Swift:Swift
struct ImagePicker: UIViewControllerRepresentable {
    // ...
    @Binding var selectedFiles: Array<URL>
    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        // ...
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
                // ...
                parent.selectedFiles.append(imagePath.absoluteURL)
            }
            if let videoURL = info[.mediaURL] as? URL {
                parent.selectedFiles.append(videoURL)
            }
            // ...
        }
    }
    // ...
}
```
When a user picks a movie, we get an `URL`, which can directly be added to the url array.
But, when the user picks an image, we have to first write a temporary file to disk, before adding the `URL` representation of its path to the url array:

```Swift:Swift
    // ...
    if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
        let imageName = UUID().uuidString
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let imagePath = paths[0].appendingPathComponent(imageName)

        if let jpegData = image.jpegData(compressionQuality: 0.8) {
            try? jpegData.write(to: imagePath)
        }

        parent.selectedFiles.append(imagePath.absoluteURL)
    }
    // ...
}
```
The last step in our [`imagePickerController(_:didFinishPickingMediaWithInfo:)`](https://developer.apple.com/documentation/uikit/uiimagepickercontrollerdelegate/1619126-imagepickercontroller) method is to dismiss the picker using the environment property `presentationMode`:

```Swift:Swift
struct ImagePicker: UIViewControllerRepresentable {
    // ...
    @Environment(\.presentationMode) private var presentationMode
    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        // ...
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            // ...
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
    // ...
}
```
##### Custom Coordinator Object
To provide our own **coordinator** object instead of the default implementation provided, we need to implement the [`makeCoordinator()`](https://developer.apple.com/documentation/swiftui/nsviewcontrollerrepresentable/makecoordinator()-1enxe) method in `ImagePicker`:

```Swift:Swift
struct ImagePicker: UIViewControllerRepresentable {
    // ...
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    // ...
}
```
### The ImagePicker Source Code

```Swift:ImagePicker.swift
import Foundation
import SwiftUI
import AVKit

struct ImagePicker: UIViewControllerRepresentable {
    var sourceType: UIImagePickerController.SourceType = .photoLibrary
    @Binding var selectedFiles: Array<URL>
    @Environment(\.presentationMode) private var presentationMode
    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parent: ImagePicker
        init(_ parent: ImagePicker) {
            self.parent = parent
        }
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
                let imageName = UUID().uuidString
                let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
                let imagePath = paths[0].appendingPathComponent(imageName)
                if let jpegData = image.jpegData(compressionQuality: 0.8) {
                    try? jpegData.write(to: imagePath)
                }
                parent.selectedFiles.append(imagePath.absoluteURL)
            }
            if let videoURL = info[.mediaURL] as? URL {
                parent.selectedFiles.append(videoURL)
            }
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = sourceType
        imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: sourceType) ?? ["public.image", "public.movie"]
        imagePicker.allowsEditing = false
        imagePicker.videoQuality = .typeHigh
        imagePicker.delegate = context.coordinator
        return imagePicker
    }
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
    }
}
```
### The ContentView Source Code
Now, let's add our `ImagePicker` to the App `ContentView`.

We use a [`Button`](https://developer.apple.com/documentation/swiftui/button) to present our `ImagePicker` in a sheet using the [`sheet(isPresented:onDismiss:content:)`](https://developer.apple.com/documentation/SwiftUI/AnyView/sheet(isPresented:onDismiss:content:)) method:

```Swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    @State var imageFiles = Array<URL>()
    @State private var isShowingPhotoLibrary = false

    var body: some View {
        VStack {
            Text("Pick an Image!")
                .padding()
            Button(action: {
                self.isShowingPhotoLibrary = true
            }) {
                Image(systemName: "photo.fill")
                    .font(.system(size: 40))
            }.sheet(isPresented: self.$isShowingPhotoLibrary) {
                ImagePicker(sourceType: .photoLibrary, selectedFiles: $imageFiles)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```
## The App
Launch the App, and in the content view, click on the button to open the image picker:
![test-uikit-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/90cd7e31-20d9-70c9-901e-13b72c0581d5.png)
In the picker, select an image:
![test-uikit-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/a03c5147-9d6a-f4ab-5a92-0ce33f30948a.png)
I added a `print` statement at the end of the [`imagePickerController(_:didFinishPickingMediaWithInfo:)`](https://developer.apple.com/documentation/uikit/uiimagepickercontrollerdelegate/1619126-imagepickercontroller) to display the contents of the url array.

After picking the first image:

```
selectedFiles= [
    file:///Users/loic/Library/Developer/CoreSimulator/Devices/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/data/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Documents/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
]
```
After picking a second image:

```
selectedFiles= [
    file:///Users/loic/Library/Developer/CoreSimulator/Devices/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/data/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Documents/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
    file:///Users/loic/Library/Developer/CoreSimulator/Devices/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY/data/Containers/Data/Application/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY/Documents/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY
]
```
It works!

That wraps up this article about using UIKit elements in SwiftUI.
## See also
- [Interfacing with UIKit](https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit)