[Swift] Create an image selection UI with PhotoKit

Custom UIs Many photo apps, such as LINE, Twitter, and Pinterest, have a ** customized image selection UI **. This time, I will implement such a UI using PhotoKit.

The default PickerController is less customizable

Searching for something like "Swift Image Selection" will hit some implementations with UIImagePickerController. This is convenient when you want to implement it quickly without thinking about it, but the customization of the UI and display contents is limited, which narrows the design options.

Access your photo library with PhotoKit

Therefore, consider a method of acquiring data directly from the photo library using PhotoKit and setting it in the UI.

Get the user to grant access

First, add NSPhotoLibraryUsageDescription to Info.plist as well as UIImagePickerViewController. (The character string described here will be displayed at the time of access request) スクリーンショット 2021-01-19 11.27.00.png

Next, call authorizationStatus (for :) when you want to load the image and check if access to the photo library is allowed.

If the permission requested by the app is not given, requestAuthorization (for: handler :) prompts the user for permission.

//Request access to your photo library
func requireAuthToPhotoLib(){
    let currentStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
    
    //Allowed?
    if [.authorized, .limited].contains(currentStatus){
        print("User has already granted access to the library.")
        return
    }
    
    //Request permission
    PHPhotoLibrary.requestAuthorization(for: .readWrite) { (status) in
        switch status {
        case .authorized: //"Allow access to all photos"
            print("Authorized!")
            
        case .denied: //"not allowed"
            print("Denied")
            
        case .limited: //"Select a photo"
            print("Limited access")
            
        case .notDetermined: //The user has not decided what to do
            print("Not determined")
            
        case .restricted: //User cannot grant access to photo library
            print("Restricted")
            
        @unknown default:
            fatalError("!?")
        }
    }
}

If you call requestAuthorization (for: handler :), you will see a prompt like this: If you tap "** Select a photo ... " here, .limited, Tap " Allow access to all photos " to .authorized, If you tap " Do not allow **", .denied will be returned.

Get data from photo library

Use fetchAssets (with: PHFetchOptions) to fetch the contents of the photo library. You can set various options for PH FetchOptions as follows. (Citation: Official Document)

スクリーンショット 2021-01-19 16.15.27.png

However, this time, I simply set it to acquire ** 10 shots in descending order of shooting date and time **.

let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
fetchOptions.fetchLimit = 10
let fetchResult = PHAsset.fetchAssets(with: fetchOptions)

The return value PHFetchResult of fetchAssets can be referenced by a subscript like an array, and the size can also be obtained with .count.

fetchResult[0].mediaType == .image // true
fetchResult.count // 10

However, according to the Official Document, it seems that ** not all values ​​are prepared at that moment ** like an array **.

... Unlike an NSArray object, however, a PHFetchResult object dynamically loads its contents from the Photos library as needed, providing optimal performance even when handling a large number of results. ...

Therefore, it is not possible to ** process with map or filter **.

Generate thumbnail images from PHAsset

Next, take out PHAsset from the acquired PH FetchResult and generate a thumbnail. You can convert it to UIImage by usingPHCachingImageManager.requestImage (for :).

// (Assuming that UIImageView is placed on UICollectionView and displayed)
let cell: CollectionViewCell = .......

DispatchQueue.global().async {
    PHCachingImageManager().requestImage(
        for: asset,
        targetSize: self.layout.itemSize,
        contentMode: .aspectFill,
        options: nil
    ) { (image, nil) in

        //Called when the image is ready
        DispatchQueue.main.async {
            cell.image = image
        }
    }
}

According to the Official Documentation (https://developer.apple.com/documentation/photokit/phimagemanager/1616964-requestimage), the handler may be ** called multiple times **.

For an asynchronous request, Photos may call your result handler block more than once. Photos first calls the block to provide a low-quality image suitable for displaying temporarily while it prepares a high-quality image. (If low-quality image data is immediately available, the first call may occur before the method returns.) When the high-quality image is ready, Photos calls your result handler again to provide it.

In order to avoid the situation where it takes a long time to display the image, the handler is designed to pass the cached data in advance. You can minimize user latency by updating the UI inside the handler.

Reflected in UI

Finally, reflect the generated UIImage in UIImageView ...

done!

(I think there are various implementation methods for UI, so I will omit it here. See GitHub for code details.)

Finally

Thank you for reading this far.

PhotoKit seems to be a framework that has been around for a long time, but I first learned about it this time. Above all, I was surprised at the beauty of requestImage (). It's very likely that the UIImageView is updated as the handler is called multiple times (personally).

It seems that PHPickerViewController was introduced at WWDC2020, so I will try to touch it as well.

The source code is published on GitHub, so if you have any comments such as "This implementation is useless!" Or "You should use this class (method)!", Please comment or PR. is.

Recommended Posts

[Swift] Create an image selection UI with PhotoKit
Starting with Swift Swift UI
Progress made with Swift UI
Post an image with POSTMAN
Maybe it works! Create an image with Docker and share it!
Create an immutable class with JAVA
Create Master Detail in Swift UI
Create an excel file with poi
How to crop an image with libGDX
Let's create an instance with .new yourself. .. ..
Create an infinite scroll with Infinite Scroll and kaminari
[Java] Create an executable module with Gradle
I tried using Realm with Swift UI
[Swift5] Round the acquired image with UIImagePicker
Create an HTML/XML generator in Swift (dynamicMemberLookup)
Shiritori map app made with Swift UI
Create an or search function with Ransack.
Create an RSA encryption-enabled JSON API with wicket
Create cool API specifications with Micronaut + Swagger UI
Create an EC site with Rails5 ⑤ ~ Customer model ~
Create an Annotator that uses kuromoji with NLP4J [007]
Incorporate View created with Swift UI into ViewController
Create an EC site with Rails 5 ⑩ ~ Create an order function ~
Swift UI 100 knocks
Create an HTTPS file server for development with ring-jetty-adapter
Create an EC site with Rails5 ⑦ ~ Address, Genre model ~
Create an EC site with Rails5 ④ ~ Header and footer ~
Create a user with an empty password on CentOS7
Mandels to create an image of the Mandelbrot set
Create an EC site with Rails5 ⑥ ~ seed data input ~
Create a Service with an empty model Liferay 7.0 / DXP
Build an Android image for Orange Pi 4 with Docker
Display 3D objects of SceneKit with Swift UI, etc.