Introducción a AWS

Crear una aplicación iOS

Crear una aplicación iOS sencilla con AWS Amplify

Módulo 5: agregar la capacidad de almacenar imágenes

En este módulo, agregará almacenamiento y la capacidad de asociar una imagen con las notas en su aplicación.

Introducción

Ahora que tenemos una aplicación de notas en funcionamiento, agreguemos la capacidad de asociar una imagen a cada nota. En este módulo, usará la CLI y las bibliotecas de Amplify para crear un servicio de almacenamiento con Amazon S3. Por último, actualizaremos la aplicación iOS para permitir la carga, la búsqueda y el procesamiento de imágenes.

Lo que aprenderá

  • Crear un servicio de almacenamiento
  • Actualice su aplicación IOS: la lógica para cargar y descargar imágenes
  • Actualice su aplicación IOS: la interfaz de usuario

Conceptos clave

Servicio de almacenamiento: el almacenamiento y la consulta de archivos como imágenes y videos es un requisito común para la mayoría de las aplicaciones. Una opción para hacer esto es codificar el archivo con Base64 y enviarlo como cadena para guardarle en la base de datos. Esto viene con desventajas como que el archivo codificado es más grande que el archivo binario original, la operación es costosa en términos informáticos y la codificación y decodificación adecuadas agregan complejidad. Otra opción es crear y optimizar un servicio de almacenamiento específicamente para el almacenamiento de archivos. Los servicios de almacenamiento como Amazon S3 existen para aportar la mayor facilidad, eficiencia y rentabilidad posible.

 Tiempo de realización

10 minutos

 Servicios utilizados

Implementación

  • Para agregar la funcionalidad de almacenamiento de imágenes, usaremos la categoría de almacenamiento 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

    Luego de un momento, debería ver lo siguiente:

    Successfully added resource image locally
  • Para implementar el servicio de almacenamiento que acabamos de crear, diríjase a su terminal y ejecute el comando:

    amplify push

    Presione Y para confirmar y, luego de un momento, debería ver lo siguiente:

    ✔ Successfully pulled backend environment amplify from the cloud.
  • Antes de ir al código, agregue la biblioteca de almacenamiento de Amplify a las dependencias de su proyecto. Abra el archivo Podfile y agregue la línea con AmplifyPlugins/AWSS3StoragePlugin o copie/pegue el archivo completo a continuación.

    # 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

    En una terminal, ejecute el comando:

    pod install

    El comando tarda unos minutos en completarse. Debería ver esto (los números de versión reales pueden variar):

    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.
  • En Xcode, abra Backend.swift y agregue una línea en la secuencia de inicialización de Amplify en el método init() privado. El bloque de código completo debería verse así:

    // 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)")
    }
  • Abra Backend.swift. En cualquier lugar de la clase backend, agregue los siguientes métodos:

    // 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)")
                }
            }
        )
    }
  • Ahora que tenemos nuestras funciones de backend disponibles, carguemos las imágenes cuando regrese la llamada a la API. El lugar principal para agregar este comportamiento es cuando la aplicación crea una UI de Note a partir del NoteData devuelto por la API.

    Abra ContentView.swift y actualice el inicializador de Note (agregue las líneas 8 a 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
    }

    Cuando el nombre de una imagen está presente en la instancia de Note, el código llama a retrieveImage. Esta es una función asincrónica. Se necesita una función para llamar cuando se descargue la imagen. La función crea un objeto de UI de Image y lo asigna a la instancia de Note. Tenga en cuenta que esta asignación desencadena una actualización de la interfaz de usuario, por lo que ocurre en el hilo principal de la aplicación con DispatchQueue.main.async.

  • Primero, agregamos código genérico para admitir la captura de imágenes. Este código se puede reutilizar en muchas aplicaciones; muestra un selector de imágenes que permite al usuario elegir una imagen de su biblioteca de imágenes.

    En Xcode, cree el nuevo archivo Swift (⌘N, luego seleccione Swift). Nombre el archivo CaptureImageView.swift y agregue este código:

    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>) {
    
        }
    }
  • Invoquemos los métodos de almacenamiento de Backend cuando se crea una nota. Abra ContentView.swift y modifique AddNoteView para agregar un componente 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()
                }
            }
        }
    }

    Modifique la sección Create Note (Crear nota) para almacenar la imagen y la nota:

    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")
        }
    }
  • Para verificar que todo funcione como se espera, cree y ejecute el proyecto. Haga clic en el menú Product (Producto) y seleccione Run (Ejecutar) o presione ⌘R. No debería haber ningún error.

    La aplicación comienza en la lista con una nota, si asume que su sesión aún sigue abierta. Use el signo + nuevamente para crear una nota. Esta vez, agregue una imagen seleccionada de la tienda de imágenes local.

    Este es el flujo completo.

Conclusión

¡Ha creado una aplicación iOS con AWS Amplify! Ha agregado autenticación a su aplicación, lo que permite que los usuarios se registren, inicien sesión y administren su cuenta. La aplicación también cuenta con una API GraphQL escalable configurada con una base de datos Amazon DynamoDB que permite que los usuarios creen y eliminen notas. También ha agregado almacenamiento de archivos mediante Amazon S3, lo que permite que los usuarios carguen imágenes y las vean en su aplicación.

En la última sección, encontrará instrucciones para reutilizar o eliminar el backend que acabamos de crear.

  • Amplify facilita el intercambio de un solo backend entre diferentes aplicaciones de frontend.

    En una terminal, diríjase hasta el otro directorio de su proyecto y ejecute el siguiente comando:

    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.

    Luego de unos segundos, verá el siguiente mensaje:

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

    Puede ver los dos archivos de configuración que se han extraído. Cuando responda “Sí” a la pregunta “¿Planea modificar este backend?”, también verá un directorio de 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
  • Cuando crea un backend para una prueba o un prototipo, o simplemente con fines de aprendizaje, al igual que cuando sigue este tutorial, luego desea eliminar los recursos en la nube que se han creado.

    Aunque el uso de estos recursos en el contexto de este tutorial se incluye en la capa gratuita, es una buena práctica limpiar los recursos que no se utilizan en la nube.

    Para limpiar su proyecto de Amplify, en una terminal, ejecute el siguiente comando:

    amplify delete

    Luego de un momento, verá el siguiente mensaje que confirma que se han eliminado todos los recursos de la nube.

    ✔ Project deleted in the cloud
    Project deleted locally.

Gracias por haber seguido este tutorial hasta el final. Háganos saber sus comentarios con la siguiente herramienta o una solicitud de extracción en nuestro repositorio de Github.

¿Este módulo le resultó útil?

¡Felicitaciones!

¡Creó con éxito una aplicación Web en AWS! Como paso siguiente, profundice aún más en las tecnologías específicas de AWS y lleve su aplicación al siguiente nivel.