在完成本项目的技术之前,我们需要解决UIKit的最后一件事情:处理完用户的图像后,我们将获得UIImage
,但是我们需要一种方法来保存处理后的图像到用户的照片库。这使用了一个名为UIImageWriteToSavedPhotosAlbum()
的 UIKit 函数,该函数以最简单的形式使用并不容易,但是要使其发挥有用的作用,您需要重新使用 UIKit。至少它会让您真正欣赏 SwiftUI 有多好!
在编写任何代码之前,我需要您对项目的 Info.plist 文件进行一些小的更改。您会看到,写入照片库是一项受保护的操作,这意味着没有用户的明确许可我们就无法做到这一点。
iOS 将负责请求许可并检查响应,但是我们需要提供一个简短的字符串向用户解释为什么我们首先要编写图像。为此,请打开Info.plist,右键单击一些空白区域,然后选择“添加行”。您会看到一个下拉选项列表,供您选择,我希望您向下滚动并选择“Privacy - Photo Library Additions Usage Description”。对于其右侧的值,请输入文本“我们要保存使用滤镜后的照片”。
完成后,我们现在可以使用UIImageWriteToSavedPhotosAlbum()
方法写出图片。我们先前的工作中已经有了这个loadImage()
方法:
func loadImage() {
guard let inputImage = inputImage else { return }
image = Image(uiImage: inputImage)
}
我们可以对其进行修改,以便立即保存加载的图像,从而有效地创建副本。将此行添加到方法的末尾:
UIImageWriteToSavedPhotosAlbum(inputImage, nil, nil, nil)
就是这样,每次导入图像时,我们的应用程序都会将其保存回照片库。首次尝试时,iOS会自动提示用户写照片的权限,并显示我们添加到Info.plist文件中的字符串。
现在,您可能会看着它并认为“那很容易!”而且你会是对的。但之所以容易,是因为我们做了最少的工作:我们将要保存的图像作为第一个参数保存到UIImageWriteToSavedPhotosAlbum()
中,然后将nil
作为其他三个保存。
这些nil
参数很重要,或者至少前两个参数很重要:它们告诉Swift保存完成后应调用什么方法,这反过来将告诉我们保存操作成功还是失败。如果您不在乎,那就大功告成了——全部三个都设为nil
就可以了。但是请记住:用户可以拒绝访问他们的照片库,因此,如果您未发现保存错误,他们会想知道您的应用程序为何无法正常运行。
使用 UIKit 两个参数来知道要调用哪个函数的原因是因为此代码很旧——比 Swift 早,而且实际上已经很旧了,甚至早于Objective-C 的闭包。因此,它使用了一种完全不同的函数引用方式:代替第一个nil
,我们应该指向一个对象,代替第二个nil
,我们应当指向应该被调用的方法的名称。
如果听起来不好,恐怕您只了解一半。您会看到,这两个参数都有各自的复杂性:
- 我们提供的对象必须是一个类,并且必须继承自
NSObject
。这意味着我们无法指向SwiftUI视图结构体。 - 该方法是作为方法名称而不是实际方法提供的。Objective-C 使用此方法名称在运行时查找实际代码,然后可以运行该代码。该方法需要具有特定的签名(参数列表),否则我们的代码将无法使用。
但是,等等:还有更多!出于性能方面的考虑,Swift 倾向于不以 Objective-C 可以读取的方式生成代码——整个“在运行时查找方法''的内容确实很整洁,但是也很慢。因此,在编写方法名称时,我们需要做两件事:
- 使用称为
#selector
的特殊编译器指令标记该方法,该指令要求 Swift 确保方法名称存在于我们所说的位置。 - 在方法中添加一个名为
@objc
的属性,该属性指示 Swift 生成可由 Objective-C 读取的代码。
您知道,在切换到SwiftUI之前,我已经写了十多年的UIKit代码,并且已经写完所有这些说明,使这个旧 API 看起来像是危害人类罪。遗憾的是这就是它,同时我们需要一直坚持下去。
在向您展示代码之前,我想提到第四个参数。因此,第一个是要保存的图像,第二个是应通知有关保存结果的对象,第三个是应运行的对象的方法,然后是第四个。我们不会在这里使用它,但是您需要了解它的作用:我们可以在此处提供任何类型的数据,并且在调用我们的完成方法时会将其传递回给我们。
这就是UIKit所称的“上下文(context)”,它可以帮助您从一个图像保存操作中识别另一个图像保存操作。您可以在此处提供任何您想要的内容,因此 UIKit 使用了您可以想象的最自动的类型:Swift 不能保证的原始内存块。在 Swift 中,它具有自己的特殊类型名称:UnsafeRawPointer
。老实说,如果不是因为我们必须在这里使用它,我什至不会告诉您它的存在,因为它在您的应用程序开发生涯中目前还没有真正的用处。
无论如何,这已经足够了。在您决定放弃该项目并直接进行下一个项目之前,让我们结束并完成。
就像我说过的,要将图片写到照片库并读取响应,我们需要某种继承自NSObject
的类。在其中,我们需要一个带有精确签名的方法,该方法带有@objc
标记,然后可以从UIImageWriteToSavedPhotosAlbum()
中调用它。
将所有内容放在一起,请将此类添加到ContentView
之外的某个位置:
class ImageSaver: NSObject {
func writeToPhotoAlbum(image: UIImage) {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
}
@objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
print("Save finished!")
}
}
有了这个,我们现在可以从SwiftUI使用它,如下所示:
let imageSaver = ImageSaver()
imageSaver.writeToPhotoAlbum(image: inputImage)
如果您现在运行代码,则应该看到“Save finished!”选择图像后立即输出消息。是的,考虑到需要多少解释,这几乎是很少的代码,但是从好的方面来说,完成了该项目的概述,经过了很长一段时间(很长很长!),我们终于可以开始实际的实现了。请继续进行,将您的项目恢复为默认状态,以便我们可以轻松进行工作。
网友评论