86、照片框架开发全解析

照片框架开发全解析

1. 填充新创建的相册

若要将用户“最近添加”智能相册中的第一个资源添加到新创建的相册中,可按以下步骤操作:
1. 获取“最近添加”相册的引用。
2. 获取该相册中第一个资源的引用。
3. 获取新创建相册的引用。

以下是实现代码:

// find Recently Added smart album
let result = PHAssetCollection.fetchAssetCollections(
    with: .smartAlbum, subtype: .smartAlbumRecentlyAdded, options: nil)
guard let rec = result.firstObject else { return }
// find its first asset
let result2 = PHAsset.fetchAssets(in:rec, options: nil)
guard let asset1 = result2.firstObject else { return }
// find our newly created album by its local id
let result3 = PHAssetCollection.fetchAssetCollections(
    withLocalIdentifiers: [self.newAlbumId], options: nil)
guard let alb2 = result3.firstObject else { return }
// ready to perform the change request
PHPhotoLibrary.shared().performChanges({
    typealias Req = PHAssetCollectionChangeRequest
    let cr = Req(for: alb2)
    cr?.addAssets([asset1] as NSArray)
})
2. PHObjectPlaceholder 的用途

当在一个批量请求中创建资产集合并将其添加到某个集合(如 PHCollectionList)时,由于请求创建资产集合返回的是 PHAssetCollectionChangeRequest 实例,且请求的 PHAssetCollection 尚未创建,无法直接添加到集合中。此时可使用 PHObjectPlaceholder,因其是 PHObject,可作为变更请求方法(如 addChildCollections(_:))的参数。

3. 监听相册变化

当相册被修改时,应用中已收集的相册信息可能会过时。为应对这种情况,可在应用生命周期早期注册一个变更观察者:

PHPhotoLibrary.shared().register(self)

当相册发生变化时,观察者的 photoLibraryDidChange(_:) 方法会被调用,并传入一个封装了变更描述的 PHChange 对象。观察者可通过调用 changeDetails(for:) 方法探查该对象。参数可以是以下两种类型:
- PHObject :参数为感兴趣的单个 PHAsset、PHAssetCollection 或 PHCollectionList,结果是一个 PHObjectChangeDetails 对象,包含 objectBeforeChanges、objectAfterChanges 和 objectWasDeleted 等属性。
- PHFetchResult :结果是一个 PHFetchResultChangeDetails 对象,包含 fetchResultBeforeChanges、fetchResultAfterChanges、removedObjects、insertedObjects 等属性。

以下是一个更新相册列表的示例:

func photoLibraryDidChange(_ changeInfo: PHChange) {
    if self.albums !== nil {
        let details = changeInfo.changeDetails(for:self.albums)
        if details !== nil {
            self.albums = details!.fetchResultAfterChanges
            // ... and adjust interface if needed ...
        }
    }
}
4. 提取图像

获取相册中的实际照片或视频进行显示是一个比较复杂的过程,因为图像数据可能很大且可能存储在云端,耗时较长。可通过以下步骤获取图像:
1. 获取图像管理器:

let imageManager = PHImageManager.default()
  1. 调用以 request 开头的方法,并提供一个完成函数:
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: options) { (image, info) in
    // 处理图像
}

请求 UIImage 时,图像信息可能会随着时间变得更准确和详细,完成函数可能会被多次调用。若只想接收一个版本的图像,可通过 PHImageRequestOptions 对象指定。

请求 UIImage 时需提供以下参数:
| 参数 | 说明 |
| ---- | ---- |
| targetSize | 所需图像的大小,传入 PHImageManagerMaximumSize 可获取最大尺寸。 |
| contentMode | PHImageContentMode,可选 .aspectFit 或 .aspectFill。 |
| options | PHImageRequestOptions 对象,可进行更多设置,如是否要原始图像、是否精确匹配目标尺寸、是否自定义裁剪等。 |

以下是一个简单的示例:

func setUpInterface() {
    guard let asset = self.asset else { return }
    let opts = PHImageRequestOptions()
    opts.resizeMode = .exact
    PHImageManager.default().requestImage(for: asset,
        targetSize: CGSize(300,300), contentMode: .aspectFit,
        options: opts) { im, info in
            if let im = im {
                self.iv.image = im
            }
    }
}

图像请求的完成函数的第二个参数是一个字典,其中一些键可能有用:
- PHImageResultRequestIDKey :唯一标识单个图像请求,可用于取消请求。
- PHImageCancelledKey :报告取消图像请求成功。
- PHImageResultIsInCloudKey :警告图像在云端,需重新提交请求并将 PHImageRequestOptions 的 isNetworkAccessAllowed 属性设置为 true。

对于表格视图或集合视图,图像提取的异步和耗时特性很重要。用户滚动时,可在单元格进入视图时请求图像,离开视图时取消请求。此外,PHCachingImageManager 子类可帮助预取图像,提高响应时间。

若 PHAsset 表示实时照片,可调用 PHImageManager 的 requestLivePhoto 方法,完成函数中会得到一个 PHLivePhoto。提取视频资源相对简单,以下是一个示例:

func fetchMovie() {
    let opts = PHFetchOptions()
    opts.fetchLimit = 1
    let result = PHAsset.fetchAssets(with: .video, options: opts)
    guard let asset = result.firstObject else {return}
    PHImageManager.default().requestPlayerItem(
        forVideo: asset, options: nil) { item, info in
        if let item = item {
            DispatchQueue.main.async {
                self.display(item:item)
            }
        }
    }
}

func display(item:AVPlayerItem) {
    let player = AVPlayer(playerItem: item)
    let vc = AVPlayerViewController()
    vc.player = player
    vc.view.frame = self.v.bounds
    self.addChild(vc)
    self.v.addSubview(vc.view)
    vc.didMove(toParent: self)
}

还可通过 PHAssetResourceManager 类直接访问资产的各种数据,例如分别检索图像的 RAW 和 JPEG 数据。

5. 编辑图像

PhotoKit 允许修改用户相册中的图像,原因如下:
- 用户每次应用提议修改相册中的照片时都需给予许可,并会看到提议的修改。
- 相册照片的修改可撤销,原始图像会与修改后的图像一起保留在数据库中,用户可随时恢复到原始图像。

修改照片图像的步骤如下:
1. 向 PHAsset 发送 requestContentEditingInput(with:completionHandler:) 消息,完成函数会返回一个 PHContentEditingInput 对象,包含可显示给用户的图像数据和指向真实图像数据文件的指针。
2. 创建 PHContentEditingOutput 对象,其 renderedContentURL 属性表示文件 URL,需将编辑后的照片图像数据写入该 URL。
3. 通知相册提取编辑后的照片版本,调用 performChanges(_:completionHandler:) 方法,在变更函数中创建 PHAssetChangeRequest 并设置其 contentEditingOutput 属性为 PHContentEditingOutput 对象。

处理调整数据的步骤如下:
1. 调用 requestContentEditingInput 时,第一个参数应是 PHContentEditingInputRequestOptions 对象,设置其 canHandleAdjustmentData 属性为一个函数,根据是否识别照片的 PHAdjustmentData 来返回 Bool 值,这决定了接收到 PHContentEditingInput 对象时将得到的图像。
2. 完成函数被调用并接收到 PHContentEditingInput 对象时,其 adjustmentData 属性可能包含之前编辑时存储的数据,可提取并用于重建图像的编辑状态。
3. 编辑图像完成后,为 PHContentEditingOutput 对象设置新的 PHAdjustmentData 对象,其数据总结了照片的新编辑状态。

以下是一个具体示例:

// 编辑前
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { adjustmentData in
    return adjustmentData.formatIdentifier == self.myidentifier
}
var id : PHContentEditingInputRequestID = 0
id = self.asset.requestContentEditingInput(with: options) { input, info in
    guard let input = input else {
        self.asset.cancelContentEditingInputRequest(id)
        return
    }
    self.input = input
    let im = input.displaySizeImage!
    if let adj = input.adjustmentData,
       adj.formatIdentifier == self.myidentifier {
        if let vigNumber = try? NSKeyedUnarchiver.unarchivedObject(
            ofClass: NSNumber.self, from: adj.data),
           let vigAmount = vigNumber as? Double {
            // ... store vigAmount ...
        }
    }
    // ... present editing interface, passing it the vigAmount ...
}

// 编辑后
let inurl = self.input.fullSizeImageURL!
let output = PHContentEditingOutput(contentEditingInput:self.input)
let outurl = output.renderedContentURL
var ci = CIImage(contentsOf: inurl, options: [.applyOrientationProperty:true])!
let space = ci.colorSpace!
if vignette >= 0.0 {
    let vig = MyVignetteFilter()
    vig.setValue(ci, forKey: "inputImage")
    vig.setValue(vignette, forKey: "inputPercentage")
    ci = vig.outputImage!
}
try! CIContext().writeJPEGRepresentation(
    of: ci, to: outurl, colorSpace: space)

let data = try! NSKeyedArchiver.archivedData(
    withRootObject: vignette, requiringSecureCoding: true)
output.adjustmentData = PHAdjustmentData(
    formatIdentifier: self.myidentifier, formatVersion: "1.0", data: data)

PHPhotoLibrary.shared().performChanges({
    typealias Req = PHAssetChangeRequest
    let req = Req(for: self.asset)
    req.contentEditingOutput = output // triggers alert
}) { ok, err in
    if ok {
        // ...
    }
}

综上所述,通过以上方法可以在应用中对相册进行资源添加、图像提取和编辑等操作,同时处理好相册变化的监听和调整数据的管理。

照片框架开发全解析(续)

6. 实际案例分析

为了更好地理解上述内容,下面通过一个完整的例子来展示如何使用这些技术实现一个简单的照片浏览和编辑应用。

6.1 照片浏览界面

假设我们有一个 UIPageViewController 用于逐张浏览照片。首先,我们需要获取相册中的照片资源:

let fetchOptions = PHFetchOptions()
let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)

然后,在 UIPageViewController 的数据源方法中,为每个页面提供对应的 DataViewController ,并在 DataViewController 中设置界面显示照片:

class DataViewController: UIViewController {
    var asset: PHAsset?
    @IBOutlet weak var iv: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setUpInterface()
    }

    func setUpInterface() {
        guard let asset = self.asset else { return }
        let opts = PHImageRequestOptions()
        opts.resizeMode = .exact
        PHImageManager.default().requestImage(for: asset,
            targetSize: CGSize(300,300), contentMode: .aspectFit,
            options: opts) { im, info in
                if let im = im {
                    self.iv.image = im
                }
        }
    }
}
6.2 照片编辑功能

当用户选择编辑某张照片时,我们按照前面介绍的步骤进行操作。以下是一个简化的编辑流程:

graph TD;
    A[开始编辑] --> B[获取PHContentEditingInput];
    B --> C[显示编辑界面];
    C --> D[进行编辑操作];
    D --> E[创建PHContentEditingOutput];
    E --> F[写入编辑后的图像数据];
    F --> G[设置调整数据];
    G --> H[通知相册更新];
    H --> I[完成编辑];

具体代码如下:

class EditViewController: UIViewController {
    var asset: PHAsset?
    var input: PHContentEditingInput?
    let myidentifier = "com.neuburg.matt.PhotoKit-Images.vignette"

    func startEditing() {
        let options = PHContentEditingInputRequestOptions()
        options.canHandleAdjustmentData = { adjustmentData in
            return adjustmentData.formatIdentifier == self.myidentifier
        }
        var id : PHContentEditingInputRequestID = 0
        id = self.asset?.requestContentEditingInput(with: options) { input, info in
            guard let input = input else {
                self.asset?.cancelContentEditingInputRequest(id)
                return
            }
            self.input = input
            let im = input.displaySizeImage!
            if let adj = input.adjustmentData,
               adj.formatIdentifier == self.myidentifier {
                if let vigNumber = try? NSKeyedUnarchiver.unarchivedObject(
                    ofClass: NSNumber.self, from: adj.data),
                   let vigAmount = vigNumber as? Double {
                    // ... store vigAmount ...
                }
            }
            // 显示编辑界面
            self.showEditingInterface()
        }
    }

    func showEditingInterface() {
        // 显示编辑界面的代码
    }

    func finishEditing(vignette: Double) {
        guard let input = self.input else { return }
        let inurl = input.fullSizeImageURL!
        let output = PHContentEditingOutput(contentEditingInput: input)
        let outurl = output.renderedContentURL
        var ci = CIImage(contentsOf: inurl, options: [.applyOrientationProperty:true])!
        let space = ci.colorSpace!
        if vignette >= 0.0 {
            let vig = MyVignetteFilter()
            vig.setValue(ci, forKey: "inputImage")
            vig.setValue(vignette, forKey: "inputPercentage")
            ci = vig.outputImage!
        }
        try! CIContext().writeJPEGRepresentation(
            of: ci, to: outurl, colorSpace: space)

        let data = try! NSKeyedArchiver.archivedData(
            withRootObject: vignette, requiringSecureCoding: true)
        output.adjustmentData = PHAdjustmentData(
            formatIdentifier: self.myidentifier, formatVersion: "1.0", data: data)

        PHPhotoLibrary.shared().performChanges({
            typealias Req = PHAssetChangeRequest
            let req = Req(for: self.asset!)
            req.contentEditingOutput = output // triggers alert
        }) { ok, err in
            if ok {
                // 更新界面显示编辑后的照片
            }
        }
    }
}
7. 性能优化

在处理照片资源时,性能是一个需要考虑的重要因素。以下是一些性能优化的建议:
- 图像尺寸 :根据实际显示需求,合理设置 targetSize ,避免请求过大的图像,减少内存占用和加载时间。
- 缓存机制 :使用 PHCachingImageManager 预取图像,提高用户浏览照片时的响应速度。

let cachingImageManager = PHCachingImageManager()
let targetSize = CGSize(300, 300)
let contentMode = PHImageContentMode.aspectFit
let options = PHImageRequestOptions()
cachingImageManager.startCachingImages(for: assets, targetSize: targetSize, contentMode: contentMode, options: options)
  • 异步处理 :将耗时的操作(如图像加载和编辑)放在后台线程中进行,避免阻塞主线程,保证界面的流畅性。例如,在编辑图像时:
DispatchQueue.global().async {
    // 进行耗时的图像编辑操作
    DispatchQueue.main.async {
        // 更新界面
    }
}
8. 总结

通过本文的介绍,我们学习了如何使用照片框架进行相册资源的管理、图像的提取和编辑等操作。主要内容总结如下:
- 填充相册 :通过基本的获取请求,将资源添加到新创建的相册中。
- 监听变化 :注册变更观察者,及时处理相册变化,更新界面显示。
- 提取图像 :使用图像管理器和完成函数异步获取图像,根据需求设置参数。
- 编辑图像 :遵循特定的步骤进行照片编辑,并处理好调整数据。
- 性能优化 :通过合理设置图像尺寸、使用缓存机制和异步处理等方法提高性能。

在实际开发中,我们可以根据具体需求灵活运用这些技术,开发出功能丰富、性能优良的照片应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值