开始使用 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。应该不会出现错误。

    假设您仍然处于登录状态,该应用程序在启动时会提供一个包含备注的列表。再次使用“+”符号创建备注。这一次,添加从本地图像存储中选择的图片。

    以下是完整流程。

    iOSTutorial-Module5-step1
    iOSTutorial-Module5-step2
    iOSTutorial-Module5-step3
    iOSTutorial-Module5-step4
    iOSTutorial-Module5-step5

结论

您已使用 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.

感谢您学完本教程。请使用以下工具或在我们的 Github 存储库中发出拉取请求,告知我们您的反馈。

此模块有帮助吗?

谢谢
请告知我们您喜欢什么。
关闭
很抱歉让您失望了
是否存在过时、令人困惑或不准确的内容? 请向我们提供反馈,帮助我们改进本教程。
关闭

恭喜!

您已在 AWS 上成功构建 Web 应用程序! 接下来,深入研究特定的 AWS 技术并将您的应用程序提升到下一个层次。