译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
用 UIImageWriteToSavedPhotosAlbum() 来保存滤镜后的图片
为了完成这个项目,我们最后需要实现保存按钮:将滤镜处理后的照片保存到用户的相册,以便用户可以进一步编辑照片,或者分享照片。
我在前面解释过,UIImageWriteToSavedPhotosAlbum() 函数可以实现我们要的功能,但需要一些跟 SwiftUI 不太兼容的代码:需要一个继承自 NSObject 的对象,提供以 @objc 标记的回调方法,以及用 #selector 编译器指令指向的方法。
我们要在一个单独的,可重用的类中实现这个目标。请创建一个名叫 ImageSaver.swift 的新 Swift 文件,把导入 Foundation 修改为导入 UIKit,然后提供以下代码:
class ImageSaver: NSObject {
func writeToPhotoAlbum(image: UIImage) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
}
@objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
// save complete
}
}
我们稍后会回到上面的代码做修改。现在需要先确保从用户那里请求到保存照片的权限:我们需要添加一个键到 Info.plist。
- 打开 Info.plist
- 在空白区域右键
- 选择 Add Row
- 选择 “Privacy - Photo Library Additions Usage Description” 作为键名
- 输入 “我们希望保存滤镜照片。” 作为值
接下来是考虑如何利用 ImageSaver 类来保存照片。眼下我们设置 image 属性的方式如下:
if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
let uiImage = UIImage(cgImage: cgimg)
image = Image(uiImage: uiImage)
}
实际上我们可以直接由 CGImage 生成 SwiftUI Image 视图,之前我也说过之所以借助 UIImage 中转是因为直接从 CGImage 生成需要更多的参数。这个理由当然是真实的,但更重要的原因是:我们需要用到一个 UIImage,以便传给 ImageSaver 类,而这里正是创建这个对象最好的地方。
把下面这个新属性添加到 ContentView,以存储中转的 UIImage:
@State private var processedImage: UIImage?
然后,我们要修改 applyProcessing() 方法,暂存 UIImage 以便后续使用:
if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
let uiImage = UIImage(cgImage: cgimg)
image = Image(uiImage: uiImage)
processedImage = uiImage
}
然后是保存按钮中的逻辑:
Button("Save") {
guard let processedImage = self.processedImage else { return }
let imageSaver = ImageSaver()
imageSaver.writeToPhotoAlbum(image: processedImage)
}
到这里其实我们已经可以收工了,但我们单独做了 ImageSaver 类的目的正是我们可以读取保存是否成功的结果。而这个方法是通过 ImageSaver 里下面这个方法来报告的:
@objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
// save complete
}
为了利用这个结果,我们需要将结果传递给 ContentView。但是,我不希望这个讨厌的 @objc 逃出这个类,所以我们要用闭包来报告成功或者失败 —— 对于 Swift 开发者来说这个解决方案更友好。
首先添加下面这两个属性到 ImageSaver 类,用以表示处理成功和失败的闭包:
var successHandler: (() -> Void)?
var errorHandler: ((Error) -> Void)?
然后,完善 didFinishSavingWithError 方法,根据成功和失败两种情况分别调用对应的闭包:
if let error = error {
errorHandler?(error)
} else {
successHandler?()
}
现在,我们就可以在使用 ImageSaver 时这样处理:
let imageSaver = ImageSaver()
imageSaver.successHandler = {
print("Success!")
}
imageSaver.errorHandler = {
print("Oops: \($0.localizedDescription)")
}
imageSaver.writeToPhotoAlbum(image: processedImage)
尽管代码不一样,但概念和原来在 ImagePicker 里的做法是一样的:我们以对 SwiftUI 更友好的方式封装 UIKit 功能以获取我们想要的行为。更棒的是,这种方式让我们可以获得能够在其他项目中复用的代码 —— 我们实际上构建了一个库!
到此我们已经完成了项目,可以从头尝试应用,导入照片,应用滤镜,保存到相册。干得漂亮!
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~
