1 访问相册功能
1.1 配置相关
1.在Info.plist
文件中设置相册权限
<key>NSPhotoLibraryAddUsageDescription</key>
<string>允许此权限才能使用添加照片功能</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>允许此权限才能使用相册功能</string>
2.使用PHPhotoLibrary
的API和UI组件,需要导入以下2个头文件
import Photos
import PhotosUI
1.2 iOS 14
以后新新增选择照片...
权限
1.2.1 PHAuthorizationStatus
为.limited
,每次冷启动都会弹窗提示
- 在
Info.plist
文件中设置PHPhotoLibraryPreventAutomaticLimitedAccessAlert
为YES
可关闭弹窗
<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
<true/>
1.2.2 手动触发选择更多照片弹窗
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self)
也可以在设置->照片
中编辑所选照片
1.3 获取相册权限和请求相册权限
- iOS 14.0包括以上使用
@available(iOS 14, *)
open class func authorizationStatus(for accessLevel: PHAccessLevel) -> PHAuthorizationStatus
@available(iOS 14, *)
open class func requestAuthorization(for accessLevel: PHAccessLevel, handler: @escaping (PHAuthorizationStatus) -> Void)
相册访问方式新增PHAccessLevel
-
addOnly
只读 -
readWrite
读写
PHAuthorizationStatus
权限新增.limited
,请求权限在readWrite
时生效
func checkPHAuthorization(_ handler: @escaping ((Bool) -> Void)) {
// 获取相册权限
let authStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
switch (authStatus) {
// 允许访问所有照片
case .authorized:
handler(true)
// 选择照片...
case .limited:
handler(true)
// 首次弹窗权限未授权
case .notDetermined:
// 请求相册权限
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
DispatchQueue.main.async {
switch status {
case .authorized, .limited:
handler(true)
default:
handler(false)
}
}
}
// 拒绝等
default:
handler(false)
}
}
- iOS 14.0以下使用
@available(iOS, introduced: 8, deprecated: 100000)
open class func authorizationStatus() -> PHAuthorizationStatus
@available(iOS, introduced: 8, deprecated: 100000)
open class func requestAuthorization(_ handler: @escaping (PHAuthorizationStatus) -> Void)
func checkPHAuthorization(_ handler: @escaping ((Bool) -> Void)) {
let authStatus = PHPhotoLibrary.authorizationStatus()
switch (authStatus) {
// 允许访问所有照片,选择照片...
case .authorized:
handler(true)
case .notDetermined:
PHPhotoLibrary.requestAuthorization { status in
DispatchQueue.main.async {
switch status {
case .authorized:
handler(true)
default:
handler(false)
}
}
}
default:
handler(false)
}
}
注意:
未设置PHAccessLevel
时,选择照片...
的权限为.authorized
,设置了readWrite
时权限为. limited
1.4 单选,使用系统API:UIImagePickerController
let imagePickerController = UIImagePickerController()
imagePickerController.modalPresentationStyle = .fullScreen
imagePickerController.delegate = self
// 是否允许编辑
imagePickerController.allowsEditing = true
需要遵守UINavigationControllerDelegate
和UIImagePickerControllerDelegate
协议
extension LITBImagePickerController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
// 取消
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
// 选择照片
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var fileImage: UIImage?
if picker.allowsEditing {
if let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
fileImage = image
}
} else {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
fileImage = image
}
}
if let img = fileImage {
systemHandler?(img)
}
picker.dismiss(animated: true, completion: nil)
}
}
1.5 多选,多选UI需要自己实现,相关API
1.5.1 获取缩略图
/// 获取缩略图
/// - Parameters:
/// - asset: PHAsset
/// - imageSize: 图片大小
/// - completion: 获取系统图片
func getThumbnailWithAsset(asset: PHAsset, imageSize: CGSize, completion: @escaping ((UIImage?) -> Void)) {
let requestOptions = PHImageRequestOptions()
requestOptions.resizeMode = .fast
let scale = UIScreen.main.scale
let size = CGSize(width: imageSize.width * scale, height: imageSize.height * scale)
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { image, info in
completion(image)
}
}
1.5.2 获取原图
/// 获取原图
/// - Parameters:
/// - asset: PHAsset
/// - isSync: 开启同步执行,默认是flase(异步执行)
/// - progressHandler: 加载图片进度回调
/// - completion: 获取图片回调
func getOriginalPhotoWithAsset(asset: PHAsset, isSync: Bool = false, progressHandler: ((Double?, Error?) -> Void)?, completion: @escaping ((UIImage?) -> Void)) {
let requestOptions = PHImageRequestOptions()
requestOptions.resizeMode = .fast
requestOptions.isSynchronous = isSync
requestOptions.progressHandler = { (progress, error, stop, info) in
DispatchQueue.main.async {
progressHandler?(progress, error)
}
}
// 该方法是异步执行,会回调多次,当isSynchronous设为true时,deliveryMode属性就会被忽略,并被当作.HighQualityFormat来处理。
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: requestOptions) { image, info in
/*
info?[PHImageResultIsDegradedKey]
PHImageResultIsDegradedKey 1表示低质量图片,0是高质量图片
设置isSynchronous为true时,阻塞线程,直到图片被准备好或发生错误时才会回调,回调一次
*/
completion(image)
}
}
1.5.3 获取相薄
/// 获取相簿列表
func getAlbumList() -> [AlbumItem] {
var items: [AlbumItem] = []
// 获取系统的相机相册
let smartOptions = PHFetchOptions()
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: smartOptions)
items.append(contentsOf: convertCollection(collection: smartAlbums))
// 获取用户创建的相册
let userCustomCollections = PHFetchOptions()
let userCustomAlbums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: userCustomCollections)
items.append(contentsOf: convertCollection(collection: userCustomAlbums))
return items
}
/// 转化处理获取到的相簿
private func convertCollection(collection: PHFetchResult<PHAssetCollection>) -> [AlbumItem] {
var items: [AlbumItem] = []
// 获取转化前相簿内容的图片
collection.enumerateObjects { object, index, stop in
let resultsOptions = PHFetchOptions()
// 按照创建时间,进行倒序
resultsOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
resultsOptions.predicate = NSPredicate(format: "mediaType=%d", PHAssetMediaType.image.rawValue)
let assetsFetchResult = PHAsset.fetchAssets(in: object, options: resultsOptions)
// 过滤隐藏相册,最近删除相册(1000000201)
if object.assetCollectionSubtype == .smartAlbumAllHidden || object.assetCollectionSubtype.rawValue > 1000000000 { return }
if assetsFetchResult.count > 0 {
let item = AlbumItem(title: object.localizedTitle ?? "Unnamed", fetchResult: assetsFetchResult)
// 最近项目显示在最前面
if object.assetCollectionSubtype == .smartAlbumUserLibrary {
items.insert(item, at: 0)
} else {
items.append(item)
}
}
}
return items
}
1.5.4 监听相册变化,PHAuthorizationStatus
为.limited
时需要监听
class LITBSelectImagesViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 开始监听
PHPhotoLibrary.shared().register(self)
}
deinit {
// 结束监听
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
}
extension LITBSelectImagesViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
// selectedFetchResult: PHFetchResult<PHAsset> 为当前选中的相薄
guard let selectedFetchResult = controller.selectedFetchResult else { return }
DispatchQueue.main.async {
guard let changes = changeInstance.changeDetails(for: selectedFetchResult) else { return }
// 获取更新的相册,更新UI
self.controller.getAllAssets(assets: changes.fetchResultAfterChanges) {
self.collectionView.reloadData()
}
}
}
}
2 使用PHPickerViewController
- 不需要设置相册访问权限
- 不能编辑照片
- 不支持查看大图
import UIKit
import PhotosUI
import Photos
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func onSingle(_ sender: Any) {
showPikcer(limitCount: 1)
}
@IBAction func onMultiple(_ sender: Any) {
showPikcer(limitCount: 9)
}
func showPikcer(limitCount: Int) {
var config = PHPickerConfiguration()
// 可选择的资源数量,0表示不设限制,默认为1
config.selectionLimit = limitCount
// 可选择的资源类型,只显示图片(注:images包含livePhotos)
config.filter = .images
// 显示livePhotos和视频(注:livePhotos不包含images)
// config.filter = .any(of: [.livePhotos, .videos])
// 如果要获取视频,最好设置该属性,避免系统对视频进行转码
config.preferredAssetRepresentationMode = .current
let pickerVC = PHPickerViewController(configuration: config)
pickerVC.delegate = self
present(pickerVC, animated: true)
}
}
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// 关闭页面
dismiss(animated: true)
// 取消选择也会触发代理方法,会返回空的results
guard !results.isEmpty else { return }
results.forEach { result in
// 判断类型是否为UIImage
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
// 获取图片
result.itemProvider.loadObject(ofClass: UIImage.self) { itemProvider, error in
// 回调结果在异步线程,展示时需要切换到主线程
if let image = itemProvider as? UIImage {
DispatchQueue.main.async {
print("---:", image)
}
}
}
}
}
}
}
更多PHPickerViewController
参考资料:https://juejin.cn/post/6881513652176814093