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
.
PickerController
is less customizableSearching 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.
Therefore, consider a method of acquiring data directly from the photo library using PhotoKit
and setting it in the UI.
First, add NSPhotoLibraryUsageDescription
to Info.plist
as well as UIImagePickerViewController.
(The character string described here will be displayed at the time of access request)
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.
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)
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
**.
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.
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.)
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