Categories
Swift Development

GCD

Granth central dispatch (algo así como gran despacho central en español)

Si estas accediendo a datos remotos deberías usar un Hilo en segundo plano

Si estas ejecutando un código lento este también debería ir en el hilo de segundo plano

Si estas editando 100 fotos deben ir en multiples hilos en segundo plano

GCD nos ayuda a manejar multiples hilos GCD los crea en automático y los ejecuta en el modo mas eficiente que puede.

async()

async() es llamada dos veces primero cuando ponemos el código en un hilo en segundo plano y luego cuando lo regresamos al hilo principal

GCD funciona con un sistema de colas FIFO(First In, First Out)

GCD coloca tus tareas en esas colas dependiendo que tan importantes tu le indiques que son

Los bloques de código serán puestos en las colas en el orden que indiques pero como varios se ejecutan al mismo tiempo el orden de término no esta garantizado.

Que tan importante es cierto código depende de algo llamado calidad de servicio (“quality of service”, or QoS) que decide que nivel de servicio debe recibir el código.

En la cima esta the main queue que corre en el hilo principal, y debe ser usada para correr código que vaya actualizar la interfaz de usuario de inmediato incluso si esto bloquea el programa de hacer cualquier otra cosa. Hay otros 4 hilos que puedes usar cada una con su nivel de QoS

User Interactive el hilo en segundo plano mas importante, debe ser usado para trabajo que mantenga tu interfaz funcionado, la prioridad de este hilo pedirá al sistema que dedique todo el CPU disponible para tener el trabajo terminado lo más rápido posible.

User Initiated este debe ser usado para tareas solicitadas por el usuario que ahora este esperando para continuar usando la app (ejemplo el usuario presiona un botón), no tan importante como el anterior pero es muy importante por que mantiene al usuario en espera.

The Utility queue este debe ser usado para tareas largas de las cuales el usuario este consciente de ellas pero por las que no este necesariamente desesperado. Si el usuario requiere algo que puede feliz mente dejar corriendo mientras hace algo mas en tu app deberías correr utility.

The Background queue esta es para tareas largas que el usuario no esta activamente consciente de ellas o que no le interesa que se completen.

No es nunca bueno hacer trabajo de UI en el hilo secundario

Paul Hudson

Diferentes formas de mover el código a un hilo diferente

Usando DispatchQueue

main para la interfaz de usuario

DispatchQueue.main.async {
    self.tableView.reloadData()
}

global y userInitiated para peticiones del usuario por ejemplo el Json

DispatchQueue.global(qos: .userInitiated).async {
    if let url = URL(string: urlString) {
        if let data = try? Data(contentsOf: url) {
            self.parse(json: data)
            return
        }
    }

    self.showError()
}

Ahora recordemos que nunca es bueno modificar la UI en un hilo secundario por eso hay que mover lo que esta dentro de showError a el hilo principal

func showError() {
    DispatchQueue.main.async {
        let ac = UIAlertController(title: "Loading error", message: "There was a problem loading the feed; please check your connection and try again.", preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: "OK", style: .default))
        self.present(ac, animated: true)
    }
}

Aunque esto es muy sencillo hay otra forma de usar GCD de forma muy sencilla

performSelector(inBackground: #selector(fetchJSON), with: nil)

Llamamos al metodo con performSelector

y la recarga de la tabla y alerta en otro método para regresar al hilo principal

performSelector(onMainThread: #selector(showError), with: nil, waitUntilDone: false)
func parse(json: Data) {
        let decoder = JSONDecoder()
        
        if let jsonPetitions = try? decoder.decode(Petitions.self, from : json){
            petitions = jsonPetitions.results
            
            
            tableView.performSelector(onMainThread: #selector(UITableView.reloadData), with: nil, waitUntilDone: false)
          
            
        }else{
            
            performSelector(onMainThread: #selector(showError), with: nil, waitUntilDone: false)
         
        }
    }

Referencias

https://www.hackingwithswift.com/read/9/overview

Categories
Articulo personal Portafolio Swift Development

HandsUP LESCO

HandsUp, es una plataforma virtual para el aprendizaje de LESCO, creada por Esteban Campos Sanchez

Para este proyecto se me encomendó recrear una Aplicación ya existente de la cual no se tenia el código fuente.

Para este proyecto utilice SwiftUI

El resultado fue una App para iOS, iPadOS y macOS.

La App tiene dos modos un modo de aprendizaje y otro de juego

En el de aprendizaje puedes ver todos los videos de señas y sus nombres en español.

En el modo juego aparece un video y debes elegir el botón correcto.

Desarrollar la App en SwiftUI permitió que se pudiera adaptar fácilmente a iPadOS y macOS

Este proyecto aunque sencillo fue bastante divertido y llego al lugar 1 en Costa Rica

Categories
Swift Development

Maps on SwiftUI and macOS Big Sur

Im writing this little post like a reference for my self but if anyone else found it useful thats great suggestions are also Wellcome

So a little bit of context im rewriting my app from SwiftUI to SwiftUI 2 to use the new Multiplatform Apps I was using the UIViewRepresentable but I read a little about the new Map on Pauls post an want to give it a look sadly the example was very symple

So this is what I learned for the Apple Documentation an few hours of googling

Get user Location

To get the user location im gonna use this LocationFetcher class im learned from #100daysofswiftui but I had to do some twits to make it work on macOS

import CoreLocation

class LocationFetcher: NSObject, CLLocationManagerDelegate {
    let manager = CLLocationManager()
    var lastKnownLocation: CLLocationCoordinate2D?

    override init() {
        super.init()
        manager.delegate = self
    }

    func start() {
        //manager.requestWhenInUseAuthorization()
        manager.startUpdatingLocation()
    }
    func locationManager(_ manager: CLLocationManager,
                            didChangeAuthorization status: CLAuthorizationStatus) {
         
        let location = manager.location?.coordinate
        lastKnownLocation = location
    
    }
    
    func locationManager(_ manager: CLLocationManager,
                                didFailWithError error: Error) {
            print( "location manager failed with error \(error)" )
        }
}

So the first thing you can notice is manager.requestWhenInUseAuthorization() is commented this is because Macs apps make the request on startUpdatingLocation() but make it work you have to go to the Signing & Capabilities of you macOS target and on the AppSandBox check location

Making the MapView

Make the map its really easy just like this

@State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))

    var body: some View {
        Map(coordinateRegion: $region)
    }

Now you can add more configurations like show the user location userTrackingMode: $trackingMode with showsUserLocation: true and allow user interactions interactionModes:.all

@State private var trackingMode = MapUserTrackingMode.follow

Map(coordinateRegion: $region, interactionModes:.all, showsUserLocation: true, userTrackingMode: $trackingMode)

But the best for me is how easy you can show MapAnnotations

Show MapAnnotation

To do this just need a collection of data that conforms to Identifiable annotationItems: collection and have a coordinate property best of all I can use my CoreData model to do this

Just had to make it conforms to Identifiable

extension User: Identifiable { }

also I added the coordinate property

public var coordinate : CLLocationCoordinate2D {
        let latitude = CLLocationDegrees(wrappedMap_cor_lat) ?? 41.158906
        let longitude = CLLocationDegrees(wrappedMap_cor_log) ??  -74.255084
        return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }

you can call it the way you like it

Next its just crearte a view for your annotation can be anything just a SwiftUI view

MapAnnotation<myCuztomView Market> in

So now this is how the Map look in code

 Map(coordinateRegion: $region, interactionModes:.all, showsUserLocation: true, userTrackingMode: $trackingMode, annotationItems: users){ (user) -> MapAnnotation<myCuztomView Market> in
MapAnnotation(coordinate: user.coordinate, content: {
                    myCuztomView Market(user: user)
                })
}

For more info check this links

https://developer.apple.com/documentation/mapkit/map

https://www.hackingwithswift.com/quick-start/swiftui/how-to-show-a-map-view

https://www.hackingwithswift.com/100/swiftui/78

https://stackoverflow.com/a/58882383/7195689

Categories
Swift Development

100 Días de Swift Día 23

Estas son mis notas para el curso https://www.hackingwithswift.com

Entrada anterior

Puntos claves

Accesar archivos en el Bundle de la app

let items = try! fm.contentsOfDirectory(atPath: path)

fm se refiere a FileManager y path se refiere a la ruta desde el Bundle de la App el cual indicamos con una String

try! es necesario por si no se encuentran nuestros archivos

Crear celdas en una tabla

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return pictures.count
}

En este código especificamos el numero de celdas para nuestra tabla, nuestra tabla fue hecha con un ViewController pero luego cambiamos por un UITableViewController que hereda de viewController pero ademas tiene lo necesario para una tabla.

Enlace para abrir la vista a detalle

let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)

if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
}

Estas dos lineas de código se hace referencia a el storyboard con código usando un String como identificador.

Ahora en la segunda linea se usa as? para agregar las propiedades de la clase y por usarlo en nuestro código, si es que el storyboard contiene esa clase

Challenge

Mostrar una tabla con nombres de banderas, vista a detalle y botones para compartir

storyboard

Flags Model

import Foundation

struct ItemCard {
    var id = UUID()
    var name: String
    var description: String
    var capital : String
    var nombreCompleto : String
    
    static var example : [ItemCard] {
        return [...]
    }
}

View Controller

//
//  ViewController.swift
//  ChallengeOne
//
//  Created by Francisco Misael Landero Ychante on 27/08/20.
//  Copyright © 2020 Francisco Misael Landero Ychante. All rights reserved.
//

import UIKit

class ViewController: UITableViewController {

    let estados : [ItemCard] = ItemCard.example
    
    override func viewWillAppear(_ animated: Bool) {
        title = "Estados de 🇲🇽 México" 
      }
      
      //Numero de celdas
      override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
          return estados.count
      }
      
      //crear la celda
      override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
          //crear la celda, usamos el que asignmos en el storyvoard
          //reusamos la celda
          let cell = tableView.dequeueReusableCell(withIdentifier: "estadoCell", for: indexPath)
          
          //le damos un texto a nuestra celda con el nombre de nuestras imagenes
          cell.textLabel?.text = estados[indexPath.row].nombreCompleto
        
          let imageName = UIImage(named: estados[indexPath.row].name)
          cell.imageView?.image = imageName
          return cell
      }
    
    
   override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //buscamos en nuestro story board nuestro detail view
        if let vc = storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as? DetailViewController {
            //en el detailview asignamos el nombre de la imagen segun la celda seleccionada
            
            vc.bind(estados[indexPath.row])
            
            //en el navigation controller empujamos la nueva vista
            navigationController?.pushViewController(vc, animated: true)
        }
        
    }
    
}

DetailViewController

//
//  DetailViewController.swift
//  ChallengeOne
//
//  Created by Francisco Misael Landero Ychante on 27/08/20.
//  Copyright © 2020 Francisco Misael Landero Ychante. All rights reserved.
//

import UIKit

class DetailViewController : UIViewController {
    
    @IBOutlet var image: UIImageView!
    @IBOutlet var name: UILabel!
    @IBOutlet var labelDes: UILabel!
    
    var estado : ItemCard?
      
      
      override func viewDidLoad() {
        
        title = estado?.name
        self.navigationController?.navigationBar.prefersLargeTitles = false
        
        let imageFlag = UIImage(named: estado!.name)
        image.image = imageFlag
        
        name.text = estado?.nombreCompleto
        labelDes.text = estado?.description
        
        //Button en el la barra de navegacion
               navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareTapped))
        
        
      }
      
    func bind(_ model : ItemCard) {
        
        self.estado = model
        
    }
    
    @objc func shareTapped(){
           
           //Verificar si tenemos una imagen
           guard let image = image.image?.jpegData(compressionQuality: 0.8) else {
               print("No se encontro niguna imagen")
               return
           }
           
           guard let imageTitle = estado?.nombreCompleto else {
                      print("No se encontro niguna imagen")
                      return
                  }
            
           
           //Que vamos a compatir
           let vc = UIActivityViewController(activityItems: [image,imageTitle], applicationActivities: [])
           
           //permitir que la ventana semuestra en el ipad
           vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
           
           //mostrar la ventana de compartir
           present(vc,animated: true)
       }
       
    
}

Categories
Swift Development

100 Días de Swift Día 22

Estas son mis notas para el curso

https://www.hackingwithswift.com/100

Día anterior

Crear un botón en la barra de navegación.

 //Button en el la barra de navegacion
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareTapped))

Guardar imagen en el carrete

    @objc func shareTapped(){
        
        //Verificar si tenemos una imagen
        guard let image = imageView.image?.jpegData(compressionQuality: 0.8) else {
            print("No se encontro niguna imagen")
            return
        }
        
        //Que vamos a compatir
        let vc = UIActivityViewController(activityItems: [image], applicationActivities: [])
        
        //permitir que la ventana semuestra en el ipad
        vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
        
        //mostrar la ventana de compartir
        present(vc,animated: true)
    }

Nota importante: No olvidar agregar el permiso en el info.plist

Resumen

En este proyecto aprendí lo fácil que es Mostar el menu de compartir en iOS.

También a agregar botones en la barra de navegación

Bibliografía

https://www.hackingwithswift.com/100/22

Categories
Swift Development

100 Días de Swift Día 19 a 21

Día 19 a 21

Estas son mis notas para el curso https://www.hackingwithswift.com/100

“The world is a book, and those who do not travel read only a page.”

Saint Augustine

«El mundo es un libro, y aquellos que no viajan solo han leído una pagina»

Saint Augustine

Guess the Flag

Project 2, part one

Añadir un Navigation View Controller

En nuestro storyboard tocamos nuestro view controller y luego en Editor menu and choose Embed In > Navigation Controller.

Añadir Botones , desde la librería de objetos

Agregamos 3 botones y luego en el inspector de tamaño (Size Inspector) configuramos el tamaño en 200 x 100

y en la Y podemos ajustar la distancia entre los botones

Ahora para crear nuestras constraints esta ves debemos tocar un botón presionar ctrl y con el puntero seleccionar el area blanca de nuestro view controller

y así podemos seleccionar las constraints que queremos aplicar

Ahora importamos nuestras banderas al folder Assets

Ahora en el inspector de imágenes seleccionamos nuestro botón le quitamos el titulo y le asignamos una imagen.

Ahora repetimos nuestro proceso de constraints pero esta vez del segundo botón al primer y del tercero al segundo.

Ahora para que nuestras reglas se apliquen debemos ir al menu Editor > Resolve Autolayout Iusses > Update Frames

Ahora para conectar nuestros botones a nuestro código, tocamos un botón presionamos ctrl y lo arrastramos sobre nuestro ViewController

Making the basic game work: UIButton and CALayer

Crear un array con las banderas del juego y una variable para jugar el puntaje

class ViewController: UIViewController {
    @IBOutlet var button1: UIButton!
    @IBOutlet var button2: UIButton!
    @IBOutlet var button3: UIButton!
    
    //Aqui iran los nombres de las banderas
    var countries = [String]()
    
    //Aqui el puntaje
    var score = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //cargar los paises
        countries += ["estonia", "france", "germany", "ireland", "italy", "monaco", "nigeria", "poland", "russia", "spain", "uk", "us"]
    }


}

Ahora mostraremos 3 banderas de forma aleatoria

func askQuestion(){
        countries.shuffle()
        
        button1.setImage(UIImage(named: countries[0]), for: .normal)
        
        button2.setImage(UIImage(named: countries[1]), for: .normal)
        
        button3.setImage(UIImage(named: countries[2]), for: .normal)
    }

Ahora para mejorar la vista de nuestras banderas

        //añadir borde a los botones
        button1.layer.borderWidth = 1
        button2.layer.borderWidth = 1
        button3.layer.borderWidth = 1
        
        //añadir cambiar color de borde a los botones
        button1.layer.borderColor = UIColor.lightGray.cgColor
        button2.layer.borderColor = UIColor.lightGray.cgColor
        button3.layer.borderColor = UIColor.lightGray.cgColor

Ahora hay que crear una variable para establecer la respuesta correcta.

    //La respuesta correcta
    var correctAnswer = 0
...
   //elegir una bandera al azar
        correctAnswer = Int.random(in: 0...2)

y mostrar el nombre de esa bandera en el titulo

//Cambiar el titulo de nuestro barra de navegacion
        title = countries[correctAnswer].uppercased()

From outlets to actions: creating an IBAction

Ahora para poder hacer algo cuando se presiona el botón seleccionamos nuestros botones y lo arrastramos hasta un nuevo espacio en nuestro código, seleccionamos esta vez acción y en tipo UIButton

Para los otros botones solo arrastraremos sobre la misma función para que todos funcionen con la misma función

Ahora debemos ir al inspector de atributos y en la sección View ponerles un tag a cada botón, 0 para el primero , 1 para el segundo, 2 para el tercero.

Ahora tenemos que revisar si nuestra respuesta fue correcta y mostrar una alerta

var title: String
        //Determinar si la respuesta fue correcta
        if sender.tag == correctAnswer {
            title = "Correcto"
            score += 1
        } else {
            title = "Incorrecto"
            score -= 1
        }
        
        //Crear una alerta con el titulo de nuestra variable
        let alert = UIAlertController(title: title, message: "Tu puntaje es \(score)", preferredStyle: .alert)
        
        //añadir un boton a nuestra alerta
        alert.addAction(UIAlertAction(title: "Continuar", style: .default, handler: askQuestion))
        
        //finalmente mostramos la alerta
        present(alert, animated: true)
        
        

Hay que actualizar nuestra función askQuestion para que pueda ser utilizada en una alerta

//
//  ViewController.swift
//  Project2
//
//  Created by Francisco Misael Landero Ychante on 08/08/20.
//

import UIKit

class ViewController: UIViewController {
    @IBOutlet var button1: UIButton!
    @IBOutlet var button2: UIButton!
    @IBOutlet var button3: UIButton!
    
    //Aqui iran los nombres de las banderas
    var countries = [String]()
    
    //Aqui el puntaje
    var score = 0
    
    //La respuesta correcta
    var correctAnswer = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //cargar los paises
        countries += ["estonia", "france", "germany", "ireland", "italy", "monaco", "nigeria", "poland", "russia", "spain", "uk", "us"]
        
        //añadir borde a los botones
        button1.layer.borderWidth = 1
        button2.layer.borderWidth = 1
        button3.layer.borderWidth = 1
        
        //añadir cambiar color de borde a los botones
        button1.layer.borderColor = UIColor.lightGray.cgColor
        button2.layer.borderColor = UIColor.lightGray.cgColor
        button3.layer.borderColor = UIColor.lightGray.cgColor
        
        askQuestion(action: nil)
    }
    
    func askQuestion(action: UIAlertAction!){
        //mezclar las banderas
        countries.shuffle()
        
        //elejir una bandera al azar
        correctAnswer = Int.random(in: 0...2)
        
        
        
        button1.setImage(UIImage(named: countries[0]), for: .normal)
        
        button2.setImage(UIImage(named: countries[1]), for: .normal)
        
        button3.setImage(UIImage(named: countries[2]), for: .normal)
        
        
        //Cambiar el titulo de nuestro barra de navegacion
        title = countries[correctAnswer].uppercased()
    }
    
    @IBAction func buttonTap(_ sender: UIButton) {
        
        var title: String
        //Determinar si la respuesta fue correcta
        if sender.tag == correctAnswer {
            title = "Correcto"
            score += 1
        } else {
            title = "Incorrecto"
            score -= 1
        }
        
        //Crear una alerta con el titulo de nuestra variable
        let alert = UIAlertController(title: title, message: "Tu puntaje es \(score)", preferredStyle: .alert)
        
        //añadir un boton a nuestra alerta
        alert.addAction(UIAlertAction(title: "Continuar", style: .default, handler: askQuestion))
        
        //finalmente mostramos la alerta
        present(alert, animated: true)
        
        
    }
    
    
    

}

Así queda nuestra primera version

Referencias

https://www.hackingwithswift.com/100

https://www.hackingwithswift.com/100/19

https://www.hackingwithswift.com/100/20

https://www.hackingwithswift.com/100/21

Próximos Días

Categories
Swift Development

100 Días de Swift Días 13 a 15

Estas son mis notas del curso https://www.hackingwithswift.com/100

Consolidation 1

Estos son días para repasar lo aprendido

Variables and constants

Types of Data

Operators

String interpolation

Arrays

Dictionaries

Conditional statements

Loops

Switch case

Functions

Optionals

Optional chaining

Enumerations

Structs

Classes

Properties

Static properties and methods

Access control

Polymorphism and typecasting

Closures

Referencias

https://www.hackingwithswift.com/100/13

https://www.hackingwithswift.com/100/14

https://www.hackingwithswift.com/100/15

Próximos Días

Categories
Swift Development

100 Días de Swift Día 16 a 18

“Of all our inventions for mass communication, pictures still speak the most universally understood language.”

Walt Disney

«De todas nuestros inventos de comunicación masiva, las imágenes siguen siendo el lenguaje universal mas entendido»

Walt Disney

Storm View Project 1, part one

Listado archivos del main Bundle

Obtener archivos del main bundle (contenedor principal de nuestra aplicación )

let fm = FileManager.default
        let path = Bundle.main.resourcePath!
        let items = try! fm.contentsOfDirectory(atPath: path)
        
        for item in items {
            if item.hasPrefix("nssl"){
                // esta es una imagen
                pictures.append(item)
            }
        }
        print(pictures)

Creando una interfaz

Crear una tabla usando UITableViewController

Así que primero debemos modificar nuestro ViewController cambiando la clase

class ViewController: UITableViewController {

Después añadir un TableViewController en nuestro story board

Ahora debemos indicarle a Xcode que este TableViewController y el de nuestro ViewController son el mismo.

Así que en el inspector de identidad seleccionamos nuestro ViewController como la clase

Ahora debemos decirle a Xcode que muestre nuestro TableViewController al iniciar la app para eso ahora elegimos el inspector de atributos y en la sección View Controller seleccionamos is inital View Controler

Ahora en el Document Outline buscamos nuestra celda dentro de la tabla del TableViewController y en el inspector de atributos le cambiamos el nombre del Identifier por Picture y el estilo Basic

Ahora lo que hacemos es ir al menu de Editor y seleccionamos Embed in -> Navigation controller

Ahora en nuestro ViewController crearemos un método que le diga Swift cuantas celdas tiene nuestra tabla

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return pictures.count
    }

Ahora creamos nuestras celdas

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //crear la celda, usamos el que asignmos en el storyvoard
        //reusamos la celda
        let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)
        
        //le damos un texto a nuestra celda con el nombre de nuestras imagenes
        cell.textLabel?.text = pictures[indexPath.row]
        return cell
    }

Project 1, part two

Añadiendo una vista de detalles para nuestras imágenes

Para crear la vista a detalle creamos un nuevo archivo de tipo Cocoa Touch Class ademas del nombre hay que suministrar que subclase es, UIViewController en este caso

Ahora añadimos desde la biblioteca un nuevo View Controller lo ponemos a la derecha de nuestra tabla

Ahora hay que añadirle su identificacion en el inspector donde dice Storyboard ID

y en la clase como DetailViewController

Así es como nuestra interfaz de usuario queda conectada con nuestro código

Ahora añadimos UIImageView desde la biblioteca y la arrastramos a nuestro View Controller y la extendemos para que llene la pantalla

Ahora debemos añadir las constraints para que la imagen se vea bien en todos los dispositivos.

hay varias formas de hacerlo, una es en menu editor >  Resolve Auto Layout Issues > Reset To Suggested Constraints.

Ahora para conectar nuestra UIImageView a nuestro código en DetailViewController seleccionamos la UIImageView en el Storyboard y presionamos ctrl en el teclado.

Una line azul aparecerá así que ahora arrastramos hasta la linea donde queremos conectar nuestra imagen y nos aparecerá un globo

Ahora seleccionamos Strong y el nombre imageView

Creemos ahora una propiedad en DetailViewController para guardar el nombre de la imagen

var selectedImage : String?

Para que la vista de la imagen cambie al la imagen que la vamos a pasar usamos este código

if let imageToLoad = selectedImage {
            //cargar la imagen en el storyboard
            imageView.image = UIImage(named: imageToLoad)
        }

Ahora de vuelta en nuestro View Controller crearemos una función para cuando alguien toque una celda de la tabla

 //acciones al seleccionar la celda

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //buscamos en nuestro story board nuestro detail view
        if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
            //en el detailview asignamos el nombre de la imagen segun la celda seleccionada
            vc.selectedImage = pictures[indexPath.row]
            //en el navigation controller empujamos la nueva vista 
            navigationController?.pushViewController(vc, animated: true)
        }
        
    }

Básicamente lo que este código hace es obtener el index de la celda, buscar el nombre de la imagen en el array y pasarlo a DetailView, luego muestra la vista de DetailView y mostrarla

Afinando los detalles finales

Ocultar la barra de navegación

Primero para hacer que nuestra imagen rellene la pantalla podemos cambiar como se muestra, para vamos al storyboard seleccionamos la Image View y en inspector de atributos seleccionamos en la sección View cómo queremos que nuestra imagen rellena la vista.

Ahora regresando a DetailViewController también podemos añadir la opción de que la barra de navegación se oculte al tocar la imagen para que veamos mejor nuestra imagen.

 
    // ocultar barra de navegacion en toque
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.hidesBarsOnTap = true
    }
    
    // mostrar barra de navegacion en toque
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.hidesBarsOnTap = false
    }

Ahora para Mostar la flechita en nuestras celdas en el storyboard buscamos nuestro ViewController > TableViewController > Picture y en el inspector de atributos le añadimos un accesorio a la celda

Añadir un titulo

Añadir un titulo es tan fácilmente es como añadirlo bajo super.viewDidLoad()

 title = selectedImage

Ahora si queremos usar títulos grandes en nuestras apps debemos definirlo bajo el titulo en ViewController

 title = "Tormentas ⛈"
        navigationController?.navigationBar.prefersLargeTitles = true

Ahora como no queremos este estilo en nuestro DetailViewController también hay que especificarlo para evitar la herencia

title = selectedImage
navigationItem.largeTitleDisplayMode = .never

y bueno ahí esta no es gran cosa pero es trabajo Honesto

Referencias

https://www.hackingwithswift.com/100

https://www.hackingwithswift.com/100/16

https://www.hackingwithswift.com/100/17

https://www.hackingwithswift.com/100/18

Próximos Días

Categories
Swift Development

100 Días de Swift Día 12

Estas son mis notas para el curso https://www.hackingwithswift.com/100

Entrada anterior

“I call it my billion-dollar mistake ”

Tony Hoare (talking about the invention of optionals)

«Lo llamo mi error de un millón de dólares»

Tony Hoare (hablando de la invención de los opcionales )

Optionals

Null references cuando una variable no contiene nigua valor, pueden causar varios dolores de cabeza, Swift vida con ellas usando optionals.

Comencemos con un dato sencillo como la edad de una persona, un valor que podemos no conocer, así que podemos crear una variable de tipo Int vacía que contenga ninguna valor y al cual podemos asignarle uno después.

var edad: Int? = nil

Esta variable no guarda ningún valor pero podemos asignarle uno después

edad = 24

Unwrapping optionals

Ahora imaginemos que tenemos un variable de tipo String, una String norma tienen una propiedad count pero un variable vacía no.

var nombre: String? = nil

Por tanto Swift no nos permite usar esta propiedad en este tipo de variables o por lo menos no sin antes hacerles un unwrapping

 if let unwrapped = nombre {
     print("tiene \(unwrapped.count)  letras")
 } else {
     print("No hay un nombre.")
 }

Lo norma es usar la sintaxis if let que hace el unwrapping usando una condición.

Si nombre contienen un string ejecuta el código entre corchetes si no la condición falla y ejecuta el otro código.

Unwrapping with guard

De forma alternativa al unwrapping con if let podemos usar guard let, que espera que al encontrar un nil seas tu quién termine la función, loop o condición.

La mayor diferencia con if let es que tu valor unwrapped permanece usable después del código guard

A continuación un ejemplo

 var nombre: String? = nil

 func saludo(_ nombre: String?) {
     guard let unwrapped = nombre else {
         print("¡No suministraste un nombre!")
         return
     }

     print("Hola, ¡\(unwrapped)!")
 }

 saludo(nombre)

usar guard let nos permite lidiar con los problemas al inicio de nuestro código y lo que queda es código feliz.

Force unwrapping

En ocaciones estamos seguros que un valor opcional no es nil, por ejemplo si queremos convertir un String a Int

let str = "5"
let num = Int(str)

Esto convierte num a un Int opcional pues swift no sabe que el valor que tratas dé convertir es un “5” o un “cinco” . Pero nosotros sí lo sabemos por lo que es seguro forzar el unwrapping usando !

let num = Int(str)!

Como resultado swift convertirá tu opcional en un Int regular pero cuidada si tu valor contienen un nil tu código fallara y tu app se detendrá. Así que ten cuidado de solo forzar el unwrapping cuando estes seguro de que el valor no sera nil.

Implicitly unwrapped optionals

Los opcionales implícitos son similares a los opcionales normales, pero ya están unwrapped, los Implicitly unwrapped optionals son creados al añadir un signo de admiración después del tipo de valor.

let edad: Int! = nil

Este tipo de opcionales también harán fallar tu condigo si contienen un nil así que solo deben ser usados cuando necesitas una variable y luego estas seguro que le asignaras un valor antes de usarla.

Nil coalescing

El operador Nil coalescing permite hacer el unwrapping y si el valor es nil puedes proporcionar un valor por default el operador se represan con dos signos de interrogación ?? después de nuestra variable opcional y después debemos proporcionar un valor.

//Esta función regresa un String opcional 
func nombreDeUsuario(for id: Int) -> String? {
     if id == 1 {
         return "Taylor Swift"
     } else {
         return nil
     }
 }
  
//Aqui usamos el nil coalescing operador 
let usuario = nombreDeUsuario(for: 15) ?? "Anonymoux"

En el ejemplo de arriba, se usa el nil coalescing operator si el valor que nos regresa la función no es nil ese es el que se usa, pero si es nil se usa lo que esta después de operador

Optional chaining

Swift nos proveer un atajo cuando usamos opcionales, si quieres acceder a algo como a.b.c y b es opcional, puedes usar un ? para activar el optional chaining.

Cuando el código corre swift checara si b tiene un valor, y si es nil el resto de la línea sera ignorado y swift regresara nil, pero si tienen un valor sera unwrapped y seguirá ejecutando el código.

let nombres = ["John", "Paul", "George", "Ringo"]

let beatle = nombres.first?.uppercased()

Optional try

En dias pasados vimos como manejar errores en una función, cuando se suministra un dato erróneo

Writing throwing functions
enum errorDeContraseña: Error {
    case muyObvia
}

func revisarContraseña(_ contraseña: String) throws -> Bool {
    if contraseña == "contraseña" {
        throw errorDeContraseña.muyObvia
    }

    return true
}

do {
     try revisarContraseña("contraseña")
     print("¡Buena contraseña!")
 } catch {
     print("¿En serio 😐?")
 }

Para correr una throwing function usamos do, try y catch

Hay dos alternativas para try la primera es try? que cambia la throwing function por una función que regresa un nil como resultado


if let resultado = try? revisarContraseña("contraseña") {
    print("El resultado fue  \(resultado)")
} else {
   print("¿En serio 😐?")
}

La otra alternativa es try! que solo debes usar en caso que estes seguro que la función no fallara, pues si lo hace tu código fallara y se detendrá (crash)

try! revisarContraseña("Secreto")
print("ok")

Failable initializers

Cuando vimos force unwrapping convertimos un string a un integer opcional

let str = "5"
let num = Int(str)

Esto es se conoce como un failable initializer un initializer que podría o no funcionar.

Puedes escribir los propios usando init?() en lugar de init y responder nil si las cosas no van bien.

como en el siguiente ejemplo

struct Persona {
    var id: String

    init?(id: String) {
        if id.count == 9 {
            self.id = id
        } else {
            return nil
        }
    }
}

Typecasting

Swift siempre debe saber el tipo de valores de tus variables, pero a veces tenemos mas información que swift, tomemos el siguiente ejemplo.

class Animal { }
class Pez: Animal { }

class Perro: Animal {
    func hacerRuido() {
        print("¡Guau!")
    }
}
let mascotas = [Pez(), Perro(), Pez(), Perro()]

Swift puede ver que tanto Perro como Pez heredan de Animal así que infiere que el array es de Animal

Si queremos un loop sobre mascotas y le pida a los perros que hagan ruido, swift puede revisar que cada elemento de mascotas sea o no perro y entonces llamar a hacerRuido

Paro eso usamos as? lo cual responde con un opcional, nil si no es de la clase esperada.

for mascota in mascotas {
    if let perro = mascota as? Perro {
        perro.hacerRuido()
    }
}

Resumen

Los opciones nos dejan presentar la ausencia de un n valor de una forma limpia y no ambigua

Swift no nos deja usar opcionales sin hacerles primero un unwrapping, usando if let o guard let.

Puedes forzar un unwrapping con !, pero recuerda que si tu valor es nil tu código fallara y se detendrá.

Implicitly unwrapped optionals no requieren que hagas un unwrapping pero si no te aseguras de asignarles un valor tu código fallara y se detendrá.

Puedes usar nil coalescing (??) para hacer unwrap y proveer un valor por default.

Optional chaining es usado para manipular un opcional, si el opcional resulta ser nil la linea de código es solo ignorado.

Puedes usar try? para convertir una throwing function en un valor opcional, o usar try! para detener tu código si hay un error.

Puedes usar init?() para regresar nil si recibes un dato incorrecto.

Puedes usar typecasting (as?) para convertir un tipo de valor en otro.

Referencias

https://www.hackingwithswift.com/100/12

https://www.hackingwithswift.com/100

Categories
Swift Development

100 Días de Swift Día 11

Estas son mis notas para el curso https://www.hackingwithswift.com/100

Entrada anterior

“Inside every large program, there is a small program trying to get out.”

Tony Hoare

«Dentro de todo gran programa, hay un pequeño programa tratado de salir»

Tony Hoare

Protocols

Los protocolos son una forma de describir que propiedades y métodos algo debe tener

Por ejemplo quizás queramos crear una función que acepte algo con un ID y no nos interesa precisamente que tipo de dato se este usando.

Comenzamos creando nuestro propio protocolo, en el definimos que requerirá que los tipos que se adapten al protocolo tengan una variable ID que pueda ser escrita y leída («get» and «set» )

protocol Identificable {
    var id: String { get set }
}

No podemos crear instancias de este protocolo directamente pues su objetivo es describir simplemente los que queremos.

Por ejemplo podemos usarlo en un struct

struct Usuario: Identificable {
    var id: String
}

Esta struct tendrá que tener un id tipo string si quiere conformarse al protocolo que definimos.

De la misma forma podemos crear una función

func mostrarID (loQueSea : Identificable ){
    print("El id es \(loQueSea.id)")
}

Protocol inheritance

Al igual que las clases los protocolos pueden usar herencia, pero con la diferencia de que puedes heredar de multiples protocolos al mismo tiempo y luego poner tu propio protocolo sobre todos ellos.

Comencemos creando tres protocolos nuevos.

protocol SeLePuedePagar {
    func calcularSueldo() -> Int
}

protocol NecesitaCapacitacion {
    func capacitar()
}

protocol TieneVacaciones {
    func tomarVacaciones(dias: Int)
}

Ahora lo mas genial, podemos combinar estos tres protocolos en uno solo

protocol Empleado: SeLePuedePagar, NecesitaCapacitacion, TieneVacaciones {}

Ahora en lugar de hacer que nuestros datos se conformen a cada uno de estos tres protocolos, podemos hacer que se conformen solo al protocolo Empleado

Extensions

Las extensiones hacen precisamente lo que dicen sus nombres, nos permiten añadir funciones o métodos a tipos de datos ya existentes, por ejemplo podemos añadir un método para obtener el cuadrado de un Int

extension Int {
    func alCuadrado() -> Int {
        return self * self
    }
}

var numero =  1
numero.alCuadrado()

Swift no nos permite añadir propiedades almacenadas (stored properties) a una extensión pero si propiedades calculadas (computed properties) por ejemplo

extension Int {
    var esPar: Bool {
        return self % 2 == 0
    }
}

Protocol extensions

Los protocolos nos permiten describir que métodos algo debe tener, pero no provee el código dentro.

Las extensiones nos dejan tener nuestros propio código y métodos pero lo aplican solo a un tipo de datos.

Protocol extensions resuelven ambos problemas, son similares a las extensiones nórmales pero en lugar de aplicarse a un tipo de dato como Int se extienden a todo un protocolo así que todos los datos que se conformen a ese protocolo lo obtienen.

En este ejemplo tenemos un Array y un Set con algunos nombres

let pythons = ["Eric", "Graham", "John", "Michael", "Terry", "Terry"]
let beatles = Set(["John", "Paul", "George", "Ringo"])

Ambos tipos de datos se conforman con el protocolo Collection, así que podemos crear una extensión para ese protocolo.


let pythons = ["Eric", "Graham", "John", "Michael", "Terry", "Terry"]
let beatles = Set(["John", "Paul", "George", "Ringo"])

extension Collection {
    func recapitular() {
        print("Son \(count) de nosotros:")

        for name in self {
            print(name)
        }
    }
}

El nuevo método que hemos creado se añadió a todos los tipos de datos con ese protocolo.

pythons.recapitular()
beatles.recapitular()

Protocol-oriented programming

Protocol extensions nos permite añadir métodos por default a nuestros protocolos, eso hace mucho mas fácil que los tipos de datos se conformen a nuestros protocolos

Por ejemplo si creamos un protocolo llamado Identificable que requiere que se tenga un ID y un método llamado identificarse()

protocol Identificable {
    var id: String { get set }
    func identificarse()
}

En este caos todas los tipos de datos que quieran conformarse al protocolo Identificable necesitan implementar su propio método identificarse()

¿Pero qué tal si nosotros los proporcionamos por default con un Protocol extension?

extension Identificable {
    func identificarse() {
        print("Mi ID es \(id).")
    }
}

Ahora podemos crear una Struct que se conforme a nuestro protocolo, y que ademas de manera gratuita obtenga una implementación de identificarse()

struct Agente: Identificable {
    var id: String
}

let james = Agente(id: "007")
james.identificarse()

Resumen

Los protocolos describen que métodos y propiedades debe tener un tipo de dato que quiere conformarse a ellos. Pero no proveen la implementación de esos métodos.

Puedes construir protocolos sobre otros protocolos, igual que las clases.

Las extensiones nos permiten añadir métodos y propiedades computadas a tipos de datos ya existentes

Las extensiones de protocolos nos permiten añadir métodos y propiedades computadas a protocolos ya existentes.

Protocol-oriented programming consisten en basar la arquitectura de tu app en una serie de protocolos , y luego usar una serie de protocolos para proveer implementaciones de métodos por default.

//y ese fue el resumen de protocolos, sin duda una tema fascinante que te ayudará a reducir la cantidad de código que reescribes, ademas de proveer un nuevo tipo de paradigma de programación. 

Referencias

https://www.hackingwithswift.com/100/11

https://www.hackingwithswift.com/100

https://developer.apple.com/videos/play/wwdc2015/408/

Proximo dia