jsdw_ios/QuickLocation/Component/PhotoPicker/PhotoPickerStore.swift

170 lines
6.4 KiB
Swift

//
// PhotoPickerStore.swift
// QuickLocation
//
import UIKit
import Photos
struct PhotoAlbum {
let collection: PHAssetCollection
let title: String
let count: Int
let coverAsset: PHAsset?
}
final class PhotoPickerStore {
private let imageManager = PHCachingImageManager()
private let imageOptions: PHImageRequestOptions = {
let options = PHImageRequestOptions()
options.deliveryMode = .opportunistic
options.resizeMode = .fast
options.isNetworkAccessAllowed = true
return options
}()
private(set) var albums: [PhotoAlbum] = []
private(set) var assets: [PHAsset] = []
func requestAccess(_ completion: @escaping (Bool) -> Void) {
let finish: (PHAuthorizationStatus) -> Void = { status in
DispatchQueue.main.async {
if #available(iOS 14, *) {
completion(status == .authorized || status == .limited)
} else {
completion(status == .authorized)
}
}
}
if #available(iOS 14, *) {
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
if status == .notDetermined {
PHPhotoLibrary.requestAuthorization(for: .readWrite, handler: finish)
} else {
finish(status)
}
} else {
let status = PHPhotoLibrary.authorizationStatus()
if status == .notDetermined {
PHPhotoLibrary.requestAuthorization(finish)
} else {
finish(status)
}
}
}
func reloadAlbums() {
var list: [PhotoAlbum] = []
let smart = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil)
smart.enumerateObjects { collection, _, _ in
guard let album = self.makeAlbum(collection) else { return }
if collection.assetCollectionSubtype == .smartAlbumUserLibrary {
list.insert(album, at: 0)
} else {
list.append(album)
}
}
let user = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil)
user.enumerateObjects { collection, _, _ in
guard let album = self.makeAlbum(collection) else { return }
list.append(album)
}
albums = list
if let first = albums.first {
loadAssets(in: first)
} else {
assets = []
}
}
func loadAssets(in album: PhotoAlbum) {
assets = fetchImages(in: album.collection)
}
func requestThumbnail(for asset: PHAsset, size: CGSize, completion: @escaping (UIImage?) -> Void) -> PHImageRequestID {
let scale = UIScreen.main.scale
let target = CGSize(width: size.width * scale, height: size.height * scale)
return imageManager.requestImage(for: asset,
targetSize: target,
contentMode: .aspectFill,
options: imageOptions) { image, info in
let degraded = (info?[PHImageResultIsDegradedKey] as? Bool) ?? false
if image != nil || !degraded {
completion(image)
}
}
}
func cancelRequest(_ id: PHImageRequestID) {
imageManager.cancelImageRequest(id)
}
func requestFullImages(for assets: [PHAsset], completion: @escaping ([UIImage]) -> Void) {
guard !assets.isEmpty else {
completion([])
return
}
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.resizeMode = .none
options.isNetworkAccessAllowed = true
options.isSynchronous = false
let group = DispatchGroup()
var images: [UIImage?] = Array(repeating: nil, count: assets.count)
var finished = Array(repeating: false, count: assets.count)
let lock = NSLock()
let maxSide = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height) * UIScreen.main.scale * 2
let target = CGSize(width: maxSide, height: maxSide)
for (index, asset) in assets.enumerated() {
group.enter()
imageManager.requestImage(for: asset,
targetSize: target,
contentMode: .aspectFit,
options: options) { image, info in
let degraded = (info?[PHImageResultIsDegradedKey] as? Bool) ?? false
let cancelled = (info?[PHImageCancelledKey] as? Bool) ?? false
let error = info?[PHImageErrorKey] as? Error
lock.lock()
let already = finished[index]
if !already, !degraded || image == nil || cancelled || error != nil {
finished[index] = true
if !degraded { images[index] = image }
lock.unlock()
group.leave()
} else {
lock.unlock()
}
}
}
group.notify(queue: .main) {
completion(images.compactMap { $0 })
}
}
private func makeAlbum(_ collection: PHAssetCollection) -> PhotoAlbum? {
if collection.assetCollectionSubtype == .smartAlbumAllHidden { return nil }
let hiddenTitles: Set<String> = ["最近删除", "Recently Deleted", "Hidden", "已隐藏"]
if let title = collection.localizedTitle, hiddenTitles.contains(title) { return nil }
let images = fetchImages(in: collection)
guard !images.isEmpty else { return nil }
let title = collection.localizedTitle?.isEmpty == false ? collection.localizedTitle! : "相册"
return PhotoAlbum(collection: collection, title: title, count: images.count, coverAsset: images.first)
}
private func fetchImages(in collection: PHAssetCollection) -> [PHAsset] {
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.image.rawValue)
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let result = PHAsset.fetchAssets(in: collection, options: options)
var list: [PHAsset] = []
list.reserveCapacity(result.count)
result.enumerateObjects { asset, _, _ in
list.append(asset)
}
return list
}
}