开始使用 AWS
构建 iOS 应用程序
使用 AWS Amplify 创建简单的 iOS 应用程序
模块 5:添加图像存储功能
在本模块中,您将添加存储以及将图像与您的应用程序中的备注关联的功能。
简介
现在,我们的备注应用程序已在运行,接下来添加将图像与每个备注关联的功能。在本模块中,您将使用 Amplify CLI 和库来创建利用 Amazon S3 的存储服务。最后,您将更新 iOS 应用程序以启用图像上传、获取和渲染。
您将学到的内容
- 创建存储服务
- 更新您的 IOS 应用程序 – 上传和下载图像的逻辑
- 更新您的 IOS 应用程序 – 用户界面
重要概念
存储服务 - 存储和查询图像和视频之类的文件是大多数应用程序的常见要求。进行此操作的一个选项是对文件进行 Base64 编码并将其作为字符串发送,以保存在数据库中。这样做也有一些缺点,比如被编码的文件比原始二进制文件大,操作的计算开销大,以及正确编码和解码的复杂性增加了。另一个选项是利用专为文件存储构建和优化存储服务。Amazon S3 之类的存储服务使这一过程尽可能简单、高效且廉价。
完成时间
10 分钟
使用的服务
实施
-
创建存储服务
要添加图像存储功能,我们将使用 Amplify 存储类别:
amplify add storage ? Please select from one of the below mentioned services: accept the default Content (Images, audio, video, etc.) and press enter ? Please provide a friendly name for your resource that will be used to label this category in the project: type image and press enter ? Please provide bucket name: accept the default and press enter ? Who should have access: accept the default Auth users only and press enter ? What kind of access do you want for Authenticated users? select all three options create/update, read, delete using the space and arrows keys, then press enter ? Do you want to add a Lambda Trigger for your S3 Bucket? accept the default No and press enter
一段时间后,您将看到:
Successfully added resource image locally
-
部署存储服务
要部署我们刚刚创建的存储服务,请转至您的终端,然后执行以下命令:
amplify push
按 Y 确认,一段时间后,您将看到:
✔ Successfully pulled backend environment amplify from the cloud.
-
向 Xcode 项目添加 Amplify 存储库
在转至代码之前,请将 Amplify 存储库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSS3StoragePlugin 添加行,或者复制并粘贴以下整个文件。
# you need at least version 13.0 for this tutorial, more recent versions are valid too platform :ios, '13.0' target 'getting started' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for getting started pod 'Amplify', '~> 1.0' # required amplify dependency pod 'Amplify/Tools', '~> 1.0' # allows to call amplify CLI from within Xcode pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~> 1.0' # support for Cognito user authentication pod 'AmplifyPlugins/AWSAPIPlugin', '~> 1.0' # support for GraphQL API pod 'AmplifyPlugins/AWSS3StoragePlugin', '~> 1.0' # support for Amazon S3 storage end
在终端执行以下命令:
pod install
该命令需要一些时间才能完成。您将看到(实际版本号可能有所不同):
Analyzing dependencies Downloading dependencies Installing AWSS3 (2.14.2) Installing AmplifyPlugins 1.0.4 Generating Pods project Integrating client project Pod installation complete! There are 5 dependencies from the Podfile and 12 total pods installed.
-
在运行时初始化 Amplify 存储插件
返回 Xcode,打开 Backend.swift,然后在私有 init() 方法的 Amplify 初始化序列中添加一行。完整代码块应如下所示:
// initialize amplify do { try Amplify.add(plugin: AWSCognitoAuthPlugin()) try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) try Amplify.add(plugin: AWSS3StoragePlugin()) try Amplify.configure() print("Initialized Amplify"); } catch { print("Could not initialize Amplify: \(error)") }
-
将 Image CRUD 方法添加到后端类
打开 Backend.Swift。在后端类中的任意位置,添加以下方法:
// MARK: - Image Storage func storeImage(name: String, image: Data) { // let options = StorageUploadDataRequest.Options(accessLevel: .private) let _ = Amplify.Storage.uploadData(key: name, data: image,// options: options, progressListener: { progress in // optionlly update a progress bar here }, resultListener: { event in switch event { case .success(let data): print("Image upload completed: \(data)") case .failure(let storageError): print("Image upload failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)") } }) } func retrieveImage(name: String, completed: @escaping (Data) -> Void) { let _ = Amplify.Storage.downloadData(key: name, progressListener: { progress in // in case you want to monitor progress }, resultListener: { (event) in switch event { case let .success(data): print("Image \(name) loaded") completed(data) case let .failure(storageError): print("Can not download image: \(storageError.errorDescription). \(storageError.recoverySuggestion)") } } ) } func deleteImage(name: String) { let _ = Amplify.Storage.remove(key: name, resultListener: { (event) in switch event { case let .success(data): print("Image \(data) deleted") case let .failure(storageError): print("Can not delete image: \(storageError.errorDescription). \(storageError.recoverySuggestion)") } } ) }
-
在从 API 检索数据时加载图像
现在,后端函数已经可用,我们接下来在 API 调用返回结果时加载图像。添加此行为的中心位置是,应用程序通过 API 返回的 NoteData 构造备注 UI 时。
打开 ContentView.swift 并更新备注的初始化程序(添加第 8 行至第 17 行):
// add a publishable's object property @Published var image : Image? // update init's code convenience init(from data: NoteData) { self.init(id: data.id, name: data.name, description: data.description, image: data.image) if let name = self.imageName { // asynchronously download the image Backend.shared.retrieveImage(name: name) { (data) in // update the UI on the main thread DispatchQueue.main.async() { let uim = UIImage(data: data) self.image = Image(uiImage: uim!) } } } // store API object for easy retrieval later self._data = data }
当备注实例中存在图像名称时,代码将调用 retrieveImage。这是异步函数。下载图像时需要调用一个函数。该函数创建一个图像 UI 对象并将其分配给备注实例。请注意,此分配会触发用户界面更新,因此会在应用程序的主线程中使用 DispatchQueue.main.async 进行更新。
-
添加 UI 代码以捕获图像
首先,我们添加通用代码以支持图像捕获。此代码可在许多应用程序中重复使用;它显示了一个图像选择器,允许用户从其图像库中选择图像。
在 Xcode 中,创建新的 Swift 文件(⌘N,然后选择 Swift)。命名 CaptureImageView.swift 文件,然后添加此代码:
import Foundation import UIKit import SwiftUI struct CaptureImageView { /// MARK: - Properties @Binding var isShown: Bool @Binding var image: UIImage? func makeCoordinator() -> Coordinator { return Coordinator(isShown: $isShown, image: $image) } } class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { @Binding var isCoordinatorShown: Bool @Binding var imageInCoordinator: UIImage? init(isShown: Binding<Bool>, image: Binding<UIImage?>) { _isCoordinatorShown = isShown _imageInCoordinator = image } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let unwrapImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return } imageInCoordinator = unwrapImage isCoordinatorShown = false } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { isCoordinatorShown = false } } extension CaptureImageView: UIViewControllerRepresentable { func makeUIViewController(context: UIViewControllerRepresentableContext<CaptureImageView>) -> UIImagePickerController { let picker = UIImagePickerController() picker.delegate = context.coordinator // picker.sourceType = .camera // on real devices, you can capture image from the camera // see https://medium.com/better-programming/how-to-pick-an-image-from-camera-or-photo-library-in-swiftui-a596a0a2ece return picker } func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<CaptureImageView>) { } }
-
创建备注时存储图像
创建备注时,我们从后端调用存储方法。打开 ContentView.swift 并修改 AddNoteView 以添加 ImagePicker 组件:
// at the start of the Content View struct @State var image : UIImage? // replace the previous declaration of image @State var showCaptureImageView = false // in the view, replace the existing PICTURE section Section(header: Text("PICTURE")) { VStack { Button(action: { self.showCaptureImageView.toggle() }) { Text("Choose photo") }.sheet(isPresented: $showCaptureImageView) { CaptureImageView(isShown: self.$showCaptureImageView, image: self.$image) } if (image != nil ) { HStack { Spacer() Image(uiImage: image!) .resizable() .frame(width: 250, height: 200) .clipShape(Circle()) .overlay(Circle().stroke(Color.white, lineWidth: 4)) .shadow(radius: 10) Spacer() } } } }
修改“创建备注”部分以存储图像和备注:
Section { Button(action: { self.isPresented = false let note = Note(id : UUID().uuidString, name: self.$name.wrappedValue, description: self.$description.wrappedValue) if let i = self.image { note.imageName = UUID().uuidString note.image = Image(uiImage: i) // asynchronously store the image (and assume it will work) Backend.shared.storeImage(name: note.imageName!, image: (i.pngData())!) } // asynchronously store the note (and assume it will succeed) Backend.shared.createNote(note: note) // add the new note in our userdata, this will refresh UI withAnimation { self.userData.notes.append(note) } }) { Text("Create this note") } }
-
构建和测试
要验证一切是否都按预期运行,请构建并运行项目。单击产品菜单,然后选择运行或键入 ⌘R。应该不会出现错误。
假设您仍然处于登录状态,该应用程序在启动时会提供一个包含备注的列表。再次使用“+”符号创建备注。这一次,添加从本地图像存储中选择的图片。
以下是完整流程。
结论
您已使用 AWS Amplify 构建了 iOS 应用程序! 您已经在应用程序中添加了身份验证,使用户可以注册、登录和管理其账户。该应用程序还使用 Amazon DynamoDB 数据库配置了一个可扩展的 GraphQL API,使用户可以创建和删除备注。您还使用 Amazon S3 添加了文件存储,从而使用户可以上传图像并在其应用程序中查看它们。
在最后一节中,您将找到有关重用或删除刚创建的后端的说明。
-
在多个项目之间共享后端
利用 Amplify,您可以轻松地在多个前端应用程序之间共享单个后端。
在终端中,导航到您的其他项目目录,然后执行以下命令:
mkdir other-project cd other-project amplify pull ? Do you want to use an AWS profile? accept the default Yes and press enter ? Please choose the profile you want to use select the profile you want to use and press enter ? Which app are you working on? select the backend you want to share and press enter ? Choose your default editor: select you prefered text editor and press enter ? Choose the type of app that you're building select the operating system for your new project and press enter ? Do you plan on modifying this backend? most of the time, select No and press enter. All backend modifications can be done from the original iOS project.
几秒钟后,您将看到以下消息:
Added backend environment config object to your project. Run 'amplify pull' to sync upstream changes.
您可以看到已拉出的两个配置文件。当您针对问题“您是否计划修改此后端?”回答“是”时,您还会看到一个 Amplify 目录
➜ other-project git:(master) ✗ ls -al total 24 drwxr-xr-x 5 stormacq admin 160 Jul 10 10:28 . drwxr-xr-x 19 stormacq admin 608 Jul 10 10:27 .. -rw-r--r-- 1 stormacq admin 315 Jul 10 10:28 .gitignore -rw-r--r-- 1 stormacq admin 3421 Jul 10 10:28 amplifyconfiguration.json -rw-r--r-- 1 stormacq admin 1897 Jul 10 10:28 awsconfiguration.json
-
删除后端
当为测试或原型创建后端,或仅出于学习目的创建后端时,就像按照本教程操作一样,您需要删除已创建的云资源。
尽管本教程中此资源的使用属于免费套餐范畴,但最佳实践是清理云中未使用的资源。
要清理 Amplify 项目,请在终端中执行以下命令:
amplify delete
稍后,您将看到以下消息,确认所有云资源已删除。
✔ Project deleted in the cloud Project deleted locally.
恭喜!
您已在 AWS 上成功构建 Web 应用程序! 接下来,深入研究特定的 AWS 技术并将您的应用程序提升到下一个层次。