译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
为了完成这个项目,我们最后需要实现保存按钮:将滤镜处理后的照片保存到用户的相册,以便用户可以进一步编辑照片,或者分享照片。
我在前面解释过,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。
接下来是考虑如何利用 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及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~