Démarrer avec AWS

Créer une application iOS

Créer une application iOS simple avec AWS Amplify

Module 5 : Ajouter la capacité de stocker des images

Dans ce module, vous apprendrez à augmenter la capacité de stockage et à associer une image avec les notes dans votre application.

Introduction

Maintenant que les notes fonctionnent dans l'application, apprenons à associer une image à chaque note. Dans ce module, vous allez utiliser l'interface de ligne de commande Amplify et les bibliothèques pour créer un service de stockage qui exploite Amazon S3. Et pour finir, vous mettrez à jour l'application iOS pour autoriser le téléchargement, la récupération et le rendu d'images.

Ce que vous apprendrez

  • Créer un service de stockage
  • Mettre à jour votre application iOS : la logique de chargement et de téléchargement des images
  • Mettre à jour votre application iOS : l'interface utilisateur

Concepts clés

Service de stockage :le stockage et la requête de fichiers, tels que des images et des vidéos, sont des exigences communes à la plupart des applications. L'une des solutions consiste à encoder le fichier en Base64 et à l'envoyer en tant que chaîne à sauvegarder dans la base de données. Cela présente toutefois des inconvénients, notamment le fait que le fichier codé est plus volumineux que le binaire d'origine, que l'opération est coûteuse en calculs et que le codage et le décodage sont plus complexes. Une autre option consiste à disposer d'un service de stockage spécialement conçu et optimisé pour le stockage de fichiers. Les services de stockage, comme Amazon S3, assurent ces opérations de manière facile, performante et aussi peu coûteuse que possible.

 Durée nécessaire

10 minutes

 Services utilisés

Mise en œuvre

  • Pour ajouter une fonctionnalité de stockage d'images, nous utiliserons la catégorie de stockage 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

    Après quelques instants, ce message devrait s'afficher :

    Successfully added resource image locally
  • Pour déployer le service de stockage que nous venons de créer, ouvrez votre terminal et exécutez cette commande :

    amplify push

    Appuyez sur la touche Y pour confirmer. Ce message s'affiche ensuite :

    ✔ Successfully pulled backend environment amplify from the cloud.
  • Avant de passer au code, vous ajoutez la bibliothèque de stockage Amplify aux dépendances de votre projet. Ouvrez le fichier Podfile et ajoutez la ligne avec AmplifyPlugins/AWSS3StoragePlugin ou copiez/collez le fichier entier ci-dessous.

    # 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

    Dans un terminal, exécutez la commande :

    pod install

    L'exécution de la commande prend quelques instants. Le résultat suivant doit être produit (les numéros de version réels peuvent varier) :

    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.
  • De retour dans Xcode, ouvrez Backend.swift et ajoutez une ligne dans la séquence d'initialisation d'Amplify en suivant la méthode init() privée. Le bloc de code complet doit ressembler à ceci :

    // 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)")
    }
  • Ouvrez Backend.swift. À un endroit quelconque dans la classe backend, ajoutez les méthodes suivantes :

    // 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)")
                }
            }
        )
    }
  • Maintenant que nos fonctions backend sont disponibles, chargeons les images lors du retour de l'appel d'API. L'emplacement central pour ajouter ce comportement est lorsque l'application crée une interface utilisateur Note à partir de l'objet NoteData renvoyé par l'API.

    Ouvrez ContentView.swift et mettez à jour l'initialiseur de Note (ajoutez les lignes 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
    }

    Lorsqu'un nom d'image se trouve dans l'instance de Note, le code appelle retrieveImage. C'est une fonction asynchrone. Il faut une fonction pour appeler lorsque l'image est téléchargée. La fonction crée un objet Image UI et l'affecte à l'instance de Note. Notez que cette attribution déclenche une mise à jour de l'interface utilisateur, qui se fait donc sur le thread principal de l'application avec DispatchQueue.main.async.

  • Tout d'abord, nous ajoutons du code générique pour prendre en charge la capture d'images. Ce code peut être réutilisé dans de nombreuses applications ; il présente un sélecteur d'images permettant à l'utilisateur de choisir une image dans sa bibliothèque d'images.

    Dans Xcode, créez le nouveau fichier swift (⌘N, puis sélectionnez Swift). Nommez le fichier CaptureImageView.swift et ajoutez ce code :

    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>) {
    
        }
    }
  • Invoquons les méthodes de stockage depuis le backend lorsqu'une note est créée. Ouvrez ContentView.swift et modifiez AddNoteView pour ajouter un composant 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()
                }
            }
        }
    }

    Modifiez la section Créer une note pour stocker l'image ainsi que la 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")
        }
    }
  • Pour vérifier que tout fonctionne comme prévu, générez et exécutez le projet. Cliquez sur le menu Produit et sélectionnez Exécuter ou tapez ⌘R. Il ne devrait pas y avoir d'erreur.

    Si vous êtes toujours connecté, l'application démarre à partir d'une liste avec une note. Utilisez le signe + à nouveau pour créer une note. Cette fois, ajoutez une image sélectionnée dans l'espace de stockage local.

    Voici la procédure complète.

Conclusion

Vous avez créé une application iOS avec AWS Amplify. Vous avez ajouté une authentification à votre application, qui permet aux utilisateurs de s'inscrire, de se connecter et de gérer leur compte. L'application possède également une API GraphQL évolutive, configurée avec une base de données Amazon DynamoDB, qui permet aux utilisateurs de créer et de supprimer des notes. Vous avez également ajouté le stockage de fichiers en utilisant Amazon S3 pour permettre aux utilisateurs de télécharger des images et de les visionner dans leur application.

La dernière section contient les instructions à suivre pour réutiliser ou supprimer le backend que nous venons de créer.

  • Amplify facilite le partage de backend entre plusieurs applications front-end.

    À partir d'un terminal, naviguez vers le répertoire de votre autre projet et exécutez la commande suivante :

    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.

    Ce message apparaît après quelques secondes :

    Added backend environment config object to your project.
    Run 'amplify pull' to sync upstream changes.

    On peut voir les deux fichiers de configuration qui ont été extraits. En répondant positivement à la question « Do you plan on modifying this backend? » (Comptez-vous modifier ce backend ?), on voit également un répertoire 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
  • Lorsque vous créez un backend dans le cadre d'un test, pour un prototype ou bien à des fins d'apprentissage, comme dans ce didacticiel, vous pourrez supprimer les ressources que vous avez créées.

    Dans le contexte de ce didacticiel, l'utilisation de ces ressources est comprise dans l'offre gratuite. Toutefois, une bonne pratique consiste à nettoyer les ressources inutilisées dans le cloud.

    Pour nettoyer votre projet Amplify, exécutez la commande suivante à partir d'un terminal :

    amplify delete

    Après quelques instants, le message ci-dessous apparaît, confirmant la suppression de l'ensemble des ressources cloud.

    ✔ Project deleted in the cloud
    Project deleted locally.

Merci d'avoir suivi ce tutoriel jusqu'à la fin. Vous pouvez nous envoyer vos commentaires en utilisant l'outil ci-dessous ou effectuer une demande d'extraction sur notre référentiel Github.

Ce module vous a-t-il été utile ?

Félicitations !

Vous avez créé une application Web dans AWS ! Prochaine grande étape : approfondissez la découverte de technologies AWS spécifiques pour faire évoluer votre application au niveau supérieur.