170 lines
6.4 KiB
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
|
|
}
|
|
}
|