AWS 入門

建置 iOS 應用程式

使用 AWS Amplify 建立簡單的 iOS 應用程式

第五單元︰新增儲存影像的功能

在本單元中,您將新增儲存體至應用程式,並讓影像能夠與應用程式中的筆記關聯。

簡介

現在,記事應用程式開始工作了,讓我們新增將影像與每個筆記關聯的功能。在本單元中,您將使用 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.
  • 將 Amplify 儲存程式庫新增至 Xcode 專案

    在前往程式碼之前,您需要將 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 Storage 外掛程式

    返回 Xcode,開啟 Backend.swift,然後在 private 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 建構 Note 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。這是非同步函數。下載影像時需要叫用函數。該函數將建立一個 Image 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()
                }
            }
        }
    }

    修改 Create Note (建立附註) 部分,以存放影像及附註:

    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")
        }
    }
  • 建立和測試

    若要驗證是否一切如預期,請建置並執行專案。按一下 Product (產品) 功能表,然後選取 Run (執行) 或輸入 ⌘R。應不會發生錯誤。

    假設您仍處於登入狀態,該應用程式將從一個附註開始。再次使用 + 號建立附註。這次,新增從本機影像存放區中選取的圖片。

    這就是完整的流程。

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

結論

您已使用 AWS Amplify 建置 iOS 應用程式! 您已將身份驗證新增到應用程式中,從而允許使用者註冊、登入和管理其帳戶。該應用程式還具有可擴展的 GraphQL API 以及 Amazon DynamoDB 資料庫,允許使用者建立和刪除筆記。您還使用 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 技術,並將您的應用程式提升到一個新的水平。