Diego Salazar


Animaciones con Swift 2 en iOS 9: BAFluidView

Hoy les moestraré como hacer una animación de fluidos como indicador de carga usando la última versión de Swift 2 y Xcode 7

Fluid Loader

Empecemos...

Abre Xcode 7 y crea un nuevo proyecto elijiendo la plantilla 'Single View Application'. Elije iPhone en dispositivos. Elije Swift como lenguaje principal.

Single view app

Luego se mostrará una ventana donde configuras propiedades como el "Nombre de Producto", "Lenguaje" (Que por su puesto es Swift), etc

Cuando des click en 'siguiente' se mostrará una ventana de dialogo para guardar el proyecto donde quieras. Donde sea que lo crees, una carpeta se creará con el nombre de la app, y dentro estará el proyecto de Xcode y una carpeta con cada target que tiene tu app (la misma app y una prueba o test en un servidor).

Después de dar click en el botón 'Crear', los archivos para tu proyecyo se generan y Voilá! probablemente estarás mirando el archivo AppDelegate.swift.

Configurando CocoaPods

Para empezar, lo primero que necesitamos es instalar Cocoapods. Cocoapods corre en Ruby, y es la única dependencia que tiene. Afortunadamente para nosotros, todas las versiones recientes de Mac OS X (desde OS X 10.7) incluyen Ruby en nuestra maquina, así que no hará falta instalarlo. Entonces todo lo que se necesita es actualizar RubyGems (solo para confirmar que estemos en la última versión).

Para hacer lo anterior, abre la Terminal y escribe el siguiente comando: sudo gem update --system

Ingresa tu contraseña cuando se pida. Esta actualización puede tomar un buen tiempo, así que relajense y vayan por un café por unos minutos mientras termina.

Lo siguiente es instalar CocoaPods. Escribe este comando en la Terminal: sudo gem install cocoapods

Por último, ingresa este comando en la Terminal para completar la configuración de CocoaPods pod setup

Este proceso puede tomar también algunos minutos mientras se clona los contenidos de CocoaPods en ~/.cocoapods/ en tu computador. Super, ahora ya tienes configurado CocoaPods!

Instalando la primera dependencia

Abre la Terminal y navega hasta el directorio que contiene el proyecto utilizando el comando 'cd':

cd ~/Ruta/A/la/Carpeta/Conteniendo/FluidLoader

Luego ingresa este comando:

pod init

Esto creará un Podfile por defecto para tu proyecto. Este Podfile será donde se definen nuestras dependencias que necesita el proyecto. Escribe este comando para abrir el Podfile usando Xcode y luego editarlo:

open -a Xcode Podfile

Abrirá el podfile con Xcode y luego tendrás que editar el archivo con lo siguiente:

Podfile

Aqui cambiamos la platarforma a: ios, ‘9.0’ en la linea 2, pod ‘BAFluidView’ en la linea 8. Después de hacer los cambios, guarda y cierra el podfile.

Tendrás que decirle a CocoaPods que instale las dependencias de tu proyecto. Escribe el siguiente comando en la Terminal (confirma que efectivamente estes en el directorio que contiene el proyecto y el Podfile):

pod install

Verás algo como lo siguiente:

Terminal

Cierra el proyecto de Xcode (si ya lo has abierto) y Abre el archivo .xworkspace (Adevertencia: De ahora en adelante, como la linea de comandos te mencionó, debes siempre abrir el worspace y no el proyecto!)

Felicitaciones, ya has agregado una dependencia usando CocoaPods. Como el proyecto que hemos importado está escrito en Objective-C necesitamos crear un archivo puente que apunte a los headers de Objetive-C. Crea un archivo Header para tu proyecto y agrega la siguiente linea de código:

#import "BAFluidView.h"

Esta linea importará todas las funciones de BAFluidView para nuestro proyecto.

También necesitamos importar algunas librerías estandard de los Frameworks de Apple como CoreGraphics, UIKit, Foundation. Ve al proyecto principal en el panel izquierdo baja hasta 'Linked Frameworks and Libraries ' > presiona el botón + y elije los Frameworks que te mencioné.

Frameworks

Configurando la Storyboard

Ahora configuraremos nuestra storyboard. Esta app descargará una imagen de un servidor y la mostrará en una UIImage. Hasta cuando termine de descargar, lo que haremos será mostrar una linda animación de fluidos. Esta app requiere de un botón para 'empezar' la animación y la descarga. Un 'Label' básico nos ayudará a mostrar el estado de la descarga y una UIImage para mostrar la imágen.

Abre Main.storyboard y verás un solo ViewController que es todo lo que necesitamos.

  1. Arrastra un UIView y ponle los constraints atados al tamaño de la ventana.
  2. Arrastra otro UIView y ubícalo sobre el anterior y ajusta los constraints al tamaño de la ventana.
  3. Agrega un UIButton llamado "Comenzar" y ubícalo en una posición central superior.
  4. Agrega un UILabel al rededor de 150px debajo del botón y centrado en el eje-X.
  5. Agrega una UIImage al rededor de 200px debajo del UILabel, de nuevo, centralo al eje-X. La jerarquía de elementos en el ViewController debe ser así:

Jerarquia ViewController

Ahora tendrás un layout como este en la storyboard:

Layout FluidLoader

Conecta el botón de "Empezar", Label de estado de Descarga, UIImageView y las dos UIViews en el archivo ViewController.swift. Abre el asistente, presiona ctrl + arrastrar y crea todos los outlets uno tras otro. También agrega la acción del botón "Empezar".

Conectando outlets

Como refencia, mira los nombres de los outlets que utilicé:

Nombre de outlets

(Nota: La UIView que está encima está nombrada como exampleContainerView)

A escribir código en el ViewController!

Abre el archivo ViewController.swift y agrega algunas lineas a la función viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    setUpBackground()
    startAnime.setTitleColor(UIColor.whiteColor(), forState: .Normal)
    titleLabels.text = ""
}

Lo que hacemos es que la funcion setUpBackground asigne una gradiente de color al fondo (background) que hemos ubicado en la storyboard. La siguiente linea cambia el color del botón "Empezar" a blanco. La tercera linea cambia el texto del UILabel a una cadéna de carácteres vacía.

Ahora veamos como luce la funcion setUpBackground:

var gradient = CAGradientLayer()

func setUpBackground() {

    if ((self.gradient) != nil) {
    self.gradient.removeFromSuperlayer()
    self.gradient = nil
    }

    var tempLayer: CAGradientLayer = CAGradientLayer()
    tempLayer.frame = self.view.bounds
    tempLayer.colors = [UIColor(netHex: 0x53cf84).CGColor, UIColor(netHex: 0x53cf84).CGColor, UIColor(netHex: 0x2aa581).CGColor, UIColor(netHex: 0x1b9680).CGColor]
    tempLayer.locations = [NSNumber(float: 0.0), NSNumber(float: 0.5), NSNumber(float: 0.8), NSNumber(float: 1.0)]
    tempLayer.startPoint = CGPointMake(0, 0)
    tempLayer.endPoint = CGPointMake(1, 1)

    self.gradient = tempLayer
    self.backgroundView.layer.insertSublayer(self.gradient, atIndex: 1)
    self.exampleContainerView.hidden = true
}

The code above adds a gradient color to the background for good looking UI.

Wait! you’re getting error for the code UIColor(netHex: 0xXXXXXX), don’t you worry about that, I’m using a custom extension to provide the UIColor in Hex format. The extension for it is given below: (Paste this code at the bottom of your ViewController.swift file,i.e, even after the closing brace of your ViewController class)

El código de arriba añade una linda gradiente para mejorar la UI de nuestra app.

Pero espera... te saldrá un error para el código UIColor(netHex: 0xXXXXXX), pero no te preocupes, vamos a usar una extension personalizada a UIColor que nos permitirá usar valores en formato hexadecimal. La extension está en el código de abajo:

(Nota: Pega este código al final del archivo ViewController.swift, después de cerrar con "}" la clase)

extension UIColor {
    convenience init(red: Int, green: Int, blue: Int) {
        assert(red >= 0 && red <= 255, "Invalid red component")
        assert(green >= 0 && green <= 255, "Invalid green component")
        assert(blue >= 0 && blue <= 255, "Invalid blue component")

        self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
    }

    convenience init(netHex:Int) {
        self.init(red:(netHex >> 16) & 0xff, green:(netHex >> 8) & 0xff, blue:netHex & 0xff)
    }
}

Now we will come to the main function,i.e, the Action of the Start Button that we linked earlier. This function is the most important part in this app. Have a look at it:

Ahora iremos a la función principal, por ejemplo, la Acción del Botón Empezar que enlazamos anteriormente. Esta función es la parte más importante de nuestra app.

Echemos un vistazo:

@IBAction func startAnimation(sender: AnyObject) {

    var myView:BAFluidView = BAFluidView(frame: self.view.frame, startElevation: 0.5)

    myView.strokeColor = UIColor.whiteColor()
    myView.fillColor = UIColor(netHex: 0x2e353d)
    myView.keepStationary()
    myView.startAnimation()
    titleLabels.textColor = UIColor.whiteColor()
    self.exampleContainerView.hidden = false
    myView.startAnimation()

    self.view.insertSubview(myView, aboveSubview: self.backgroundView)

    UIView.animateWithDuration(0.5, animations: {
            myView.alpha=1.0
        }, completion: { _ in
        self.titleLabels.text = "Downloading"
        self.startAnime.enabled = false
        self.exampleContainerView.removeFromSuperview()
        self.exampleContainerView = myView
    })

    if let imageUrl = NSURL(string: "http://swinggolfireland.com/wp-content/uploads/2014/09/OldHead_7PanB.jpg") {
        let imageRequest: NSURLRequest = NSURLRequest(URL: imageUrl)
        let queue: NSOperationQueue = NSOperationQueue.mainQueue()
        NSURLConnection.sendAsynchronousRequest(imageRequest, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in

        if data != nil {
            self.titleLabels.text = "Complete!"
            self.exampleContainerView.hidden = true
            self.startAnime.enabled = true
            println("done")
            self.myImage.image = UIImage(data: data)
            self.myImage.layer.borderWidth = 2.0
            self.myImage.layer.borderColor = UIColor.whiteColor().CGColor

    } else {
            self.titleLabels.text = "Error Downloading"
            self.exampleContainerView.hidden = true
            self.startAnime.enabled = true
            println("error")
        }
    })
    }

}

El código de arriba será ejecutado una vez el usuario haga tap sobre el botón "Empezar". Trataré de romper el código por partes para que lo entiendas mejor.

var myView:BAFluidView = BAFluidView(frame: self.view.frame, startElevation: 0.5)

myView.strokeColor = UIColor.whiteColor()
myView.fillColor = UIColor(netHex: 0x2e353d)
myView.keepStationary()
myView.startAnimation()
titleLabels.textColor = UIColor.whiteColor()
self.exampleContainerView.hidden = false
myView.startAnimation()

self.view.insertSubview(myView, aboveSubview: self.backgroundView)

UIView.animateWithDuration(0.5, animations: {
        myView.alpha=1.0
    }, completion: { _ in
    self.titleLabels.text = "Downloading"
    self.startAnime.enabled = false
    self.exampleContainerView.removeFromSuperview()
    self.exampleContainerView = myView
})

El código de arriba usa la clase BAFluidView que habíamos importado a nuestro proyecto usando CocoaPods. Agregamos las propiedades de la animación a myView que es un objeto BAFluidView. Luego agregamos propiedades como el color de borde, relleno, etc. y empezamos la animación. Luego usamos UIView.animateWithDuration y ponemos el exampleContainerView a myView. Esto enciende la animación del fluido y la verás en la mitad de la pantalla.

La siguiente parte del código es:

if let imageUrl = NSURL(string: "http://swinggolfireland.com/wp-content/uploads/2014/09/OldHead_7PanB.jpg") {
    let imageRequest: NSURLRequest = NSURLRequest(URL: imageUrl)
    let queue: NSOperationQueue = NSOperationQueue.mainQueue()
    NSURLConnection.sendAsynchronousRequest(imageRequest, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in

    if data != nil {
        self.titleLabels.text = "Complete!"
        self.exampleContainerView.hidden = true
        self.startAnime.enabled = true
        println("done")
        self.myImage.image = UIImage(data: data)
        self.myImage.layer.borderWidth = 2.0
        self.myImage.layer.borderColor = UIColor.whiteColor().CGColor

} else {
        self.titleLabels.text = "Error Downloading"
        self.exampleContainerView.hidden = true
        self.startAnime.enabled = true
        println("error")
    }
})

El código anterior descarga una imagen de un servidor remoto, durante el tiempo que el archivo se esté descargando, el indicador de carga será una hermosa anición de fluido, que se ocultará al momento de tener la imagen y mostrarse en el UIImage en la app. La descarga de la imagen se hace de manera asíncrona, por ejemplo, ejecutandose en un hilo de fondo evitando bloquear el hilo principal de la app.

¡Listo! Hemos completado exitosamente nuestra app. Eso es todo el tutorial. Si quieres editar la animación puede revisar la documentación de BAFluidView en github, solo juega con el código.

Ejecutando la App

Ahora puedes presionar el botón de Play en Xcode 7 y seleccionar un dispositivo para mostrar la app. Ahora en la última versión de Xcode también puedes ejecutar directamente en tu iPhone sin necesidad de pagar por una cuenta de Apple Developer, así que disfruta de esta animación en tu celular.

Si te gustó este post, y quieres más no olvides seguirme en twitter: @diegosalazarCo y compartir este post. Estaré posteando más tutoriales y noticias sobre Swift y lo útimo en iOS.