三.PhotoKit - 浏览和修改照片和相册

引言

(相关代码在Album文件中,项目运行起来通过个人页的头像点击进入。)

在本篇博客中我们将会探讨关于使用PhotoKit获取资源缩略图,然后将其展示为单张图片、视频或者实况照片。

还将介绍如何将用户的照片整理到用户创建的相册和其它内置的集合中,比如“最近添加”,“收藏夹”,以及创建,删除,修改相册和照片。

相册

显示

除了获取所有资源之外我们还可以获取到用户创建的所有相册以及内置的系统相册,并且当相册内容发生更改时,比如创建新的相册或者添加新的照片和视频我们都能获取到对应改变的通知。

首先我们将数据分成三个部分,所有照片,智能相册,和用户相册。

    /// 所有相册数据
    private var allAlbums:PHFetchResult<PHAsset> = PHFetchResult()
    /// 智能相册数据
    private var smartAlbums:PHFetchResult<PHAssetCollection> = PHFetchResult()
    /// 用户相册数据
    private var userCollections:PHFetchResult<PHAssetCollection> = PHFetchResult()

在进入该视图控制器时,开始加载这三个数据,再次之前记得需要在info.plist文件中添加对应的key和描述信息。

    private func reloadAlbum() {
        // 1.所有照片
        let allPhotos = PHFetchOptions()
        allPhotos.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
        allAlbums = PHAsset.fetchAssets(with: allPhotos)
        // 2.智能相册
        smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil)
        // 3.用户相册
        userCollections = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil)
    }

注册相册更新的监听,当相册或照片数据发生变化时及时更新列表

    /// 注册相册变化监听
    private func registerPhotoLibraryChangeObserver() {
        PHPhotoLibrary.shared().register(self)
    }
class PHAlbumViewController: PHBaseViewController,PHPhotoLibraryChangeObserver, UITableViewDelegate, UITableViewDataSource {
    ....
}
    //MARK: - PHPhotoLibraryChangeObserver
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        DispatchQueue.main.sync {
            // 更新所有相册数据
            if let changeDetails = changeInstance.changeDetails(for: allAlbums) {
                allAlbums = changeDetails.fetchResultAfterChanges
            }
            // 更新智能相册数据
            if let changeDetails = changeInstance.changeDetails(for: smartAlbums) {
                smartAlbums = changeDetails.fetchResultAfterChanges
                tableView.reloadSections(IndexSet(integer: 1), with: .automatic)
            }
            // 更新用户相册数据
            if let changeDetails = changeInstance.changeDetails(for: userCollections) {
                userCollections = changeDetails.fetchResultAfterChanges
                tableView.reloadSections(IndexSet(integer: 2), with: .automatic)
            }
        }
    }

效果如下:

创建

当用户点击视图右上角的添加按钮,将会弹出一个系统的带输入框的弹窗,输入相册名称之后点击确定按钮,我们将会调用PHPhotoLibrary的创建相册方法,创建一个我们自定义的相册。

    /// 创建相册
    /// - Parameter name: 相册名称
    /// - Returns: 相册
    private func createAlbum(name: String) {
        PHPhotoLibrary.shared().performChanges {
            PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
        } completionHandler: { (success, error) in
            if success {
                print("创建相册成功")
            } else {
                print("创建相册失败")
            }
        }
    }

例如我们创建一个名为MEME的相册,效果如下:

照片,视频,实况 缩略图

显示

创建一个名为PHAssetViewController的视图控制器,用来显示所有照片,视频和实况照片列表,我们采用UICollectionView网格的形式来加载照片,当我们从相册页面跳转到照片列表页面时会将所属相册,或者是当前的照片资源列表传递到PHAssetViewController中。

        let assetViewController = PHAssetViewController()
        if let fetchResult = fetchResult {
            assetViewController.fetchResult = fetchResult
        }
        if let collection = collection {
            assetViewController.assetCollection = collection
        }
        self.navigationController?.pushViewController(assetViewController, animated: true)

PHAssetViewController将会根据传递过来的数据来判断是否加载对应相册照

    override func viewDidLoad() {
        super.viewDidLoad()
        if let assetCollection = assetCollection {
            fetchResult = PHAsset.fetchAssets(in: assetCollection, options: nil)
        }
        ...
    }

获取到相册的所有资源之后我们就可以显示照片列表了,在UICollectionViewDelegate的方法中加载缩略图。

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! PHPhotoCell
        let asset = fetchResult.object(at: indexPath.item)
        if asset.mediaSubtypes.contains(.photoLive) {
            cell.livePhotoBadgeImage = PHLivePhotoView.livePhotoBadgeImage(options: .overContent)
        } else {
            cell.livePhotoBadgeImage = nil
        }
        cell.representedAssetIdentifier = asset.localIdentifier
        imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) { image, _ in
            if cell.representedAssetIdentifier == asset.localIdentifier {
                cell.image = image
            }
        }
        return cell
    }

在加载缩略图片时我们采用了PHCachingImageManager类,它适合在加载大量图片并且不断切换的场景,但是为此我们需要计算需要缓存的资源以及需要移除缓存的资源。

        // 更新缓存
        imageManager.startCachingImages(for: addedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)
        imageManager.stopCachingImages(for: removedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)

运行后的效果如下:

创建

我们还可以使用PHPhotoLibrary创建新的照片到指定相册

    /// 添加照片
    @objc private func addAlbum() {
        let image = createAsset()
        PHPhotoLibrary.shared().performChanges {
           let creationRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
            if let assetCollection = self.assetCollection {
                let collectionChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection)
                collectionChangeRequest?.addAssets([creationRequest.placeholderForCreatedAsset!] as NSArray)
            }
        } completionHandler: { success, error in
            if success {
                print("保存成功")
            } else {
                print("保存失败")
            }
        }
       
    }

效果如下:

当创建一个新的照片到相册之后,相册的内容立刻就刷新显示了出来,因为在这个控制中我们也注册了相册更新的监听,并实现了监听方法。

    //MARK: - PHPhotoLibraryChangeObserver
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        DispatchQueue.main.sync {
            if let changeDetails = changeInstance.changeDetails(for: fetchResult) {
                fetchResult = changeDetails.fetchResultAfterChanges
                collectionView.reloadData()
            }
        }
    }

显示单张照片,视频,实况照片

照片

定义合适的目标尺寸,使图片在显式时系统不需要进行额外的计算,来提升程序性能。

    /// 图片尺寸
    private var targetSize: CGSize {
        let scale = UIScreen.main.scale
        return CGSize(width: CS_SCREENWIDTH * scale, height: CS_SCREENHIGHT * scale)
    }

对于静态图片我们使用PHImageManager提供的requestImage方法来加载图片,通过options参数我们可以设置加载图片的质量,进度回调等等。

    /// 加载普通静态图
    private func loadNormalImage(asset:PHAsset) {
        let options = PHImageRequestOptions()
        options.deliveryMode = .highQualityFormat
        options.isNetworkAccessAllowed = true
        options.progressHandler = {[weak self] (progress, error, stop, info) in
            guard let self = self else { return }
            DispatchQueue.main.async {
                self.progressView.progress = progress
            }
        }
        PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options) { [weak self] (image, info) in
            guard let self = self else { return }
            self.imageView.image = image
            self.imageView.isHidden = false
            self.livePhotoView.isHidden = true
            self.progressView.isHidden = true
        }
    }

效果如下:

视频

对于视频资源预览图图片的展示与静态图片相册,但是当我们点击播放按钮使,则需要PHImageManager提供的requestPlayerItem方法直接获取可播放的AVPlayerItem。

    /// 播放按钮点击事件
    @objc private func playButtonAction() {
        guard let asset = asset else {
            return
        }
        PHImageManager.default().requestPlayerItem(forVideo: asset, options: nil) { [weak self] (playerItem, info) in
            guard let self = self else { return }
            DispatchQueue.main.async {
                self.playerLayer.player = AVPlayer(playerItem: playerItem)
                self.playerLayer.player?.play()
                self.playButton.isHidden = true
            }
        }
    }

效果如下:

实况

对于实况照片资源PHImageManager提供了单独的方法获取PHLivePhoto,获取到该对象之后我们可以让其自动进行播放。

    /// 加载实况
    private func loadLivePhoto(asset:PHAsset) {
        let options = PHLivePhotoRequestOptions()
        options.deliveryMode = .highQualityFormat
        options.isNetworkAccessAllowed = true
        options.progressHandler = {[weak self] (progress, error, stop, info) in
            guard let self = self else { return }
            DispatchQueue.main.async {
                print("progress: \(progress)")
                self.progressView.progress = progress
            }
        }
        PHImageManager.default().requestLivePhoto(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options) { [weak self] (livePhoto, info) in
            guard let self = self else { return }
            self.livePhotoView.livePhoto = livePhoto
            self.livePhotoView.isHidden = false
            self.imageView.isHidden = true
            self.progressView.isHidden = true
            if !self.isPlayingHint {
                self.livePhotoView.startPlayback(with: .full)
                self.isPlayingHint = true
            }
        }
    }

效果如下:

收藏照片

可以通过修改PHAsset的isFavorite参数来进行收藏和取消收藏媒体资源:

    /// 收藏
    @objc private func collectButtonAction() {
        guard let asset = asset else {
            return
        }
        self.collectButton.isSelected = !self.collectButton.isSelected
        PHPhotoLibrary.shared().performChanges({
            let request = PHAssetChangeRequest(for: asset)
            request.isFavorite = !asset.isFavorite
        }, completionHandler: { success, error in
            if success {
                print("Marked the asset as a Favorite")
                
            } else {
                DispatchQueue.main.sync {
                    self.collectButton.isSelected = !self.collectButton.isSelected
                }
                print("Can't mark the asset as a Favorite: \(String(describing: error))")
            }
        })
    }

效果如下:

删除照片

除了收藏以外使用PHAssetChangeRequest还可以删除一个指定的媒体资源,而使用PHAssetCollectionChangeRequest可以将一个指定的媒体资源从指定的相册移除。

if assetCollection != nil {
    // Remove the asset from the selected album.
    PHPhotoLibrary.shared().performChanges({
        let request = PHAssetCollectionChangeRequest(for: self.assetCollection)!
        request.removeAssets([self.asset as Any] as NSArray)
    }, completionHandler: completion)
} else {
    // Delete the asset from the photo library.
    PHPhotoLibrary.shared().performChanges({
        PHAssetChangeRequest.deleteAssets([self.asset as Any] as NSArray)
    }, completionHandler: completion)
}

效果如下:

结语

通过本文,我们深入探讨了 PhotoKit 提供的强大功能,涵盖了从浏览到修改照片和相册的各个方面。我们不仅了解了如何显示和创建相册、照片、视频及实况照片,还掌握了如何处理单张照片的显示、收藏和删除操作。这些功能为开发者提供了丰富的工具,以便在应用中实现丰富的照片管理和展示体验。

在实际开发中,PhotoKit 的灵活性和强大功能使得处理用户的媒体数据变得更加高效和便捷。希望本文对您在使用 PhotoKit 的过程中有所帮助。如果您有任何问题或需要进一步的指导,请随时联系我。感谢您的阅读,期待您在接下来的项目中能够充分利用 PhotoKit 提供的所有功能,创造出更加出色的应用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值