Una aplicación para probar las librerías UIKit y AVFoundation con iOS

BaseViewController.swift

//
//  BaseViewController.swift
//  UIKit component handling
//

import UIKit

class BaseViewController: UIViewController {
    let componentName:String
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
    }
    
    init(_ componentName: String) {
        self.componentName = componentName
        super.init(nibName: nil, bundle: nil)
        self.navigationItem.prompt = componentName
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func appDelegate() -> AppDelegate? {
        guard let app = UIApplication.shared.delegate as? AppDelegate else { return nil }
        return app
    }
}

RootViewController.swift

//
//  ViewController.swift
//  UIKit component handling
//

import UIKit

class RootViewController: UITableViewController, UISearchBarDelegate {
    @IBOutlet var searchBar: UISearchBar!
    
    let componentList : [BaseViewController] = [
        LabelVC("UILabel"),
        ImageViewVC("UIImageView"),
        ToolBarVC("UIToolBar"),
        FirstNavigationBarVC("Navigation Bar"),
        PlayMusicalNotesVC("AVAudioPlayer"),
        WebServiceSchoolsVC("JSON web service"),
        ScrollViewVC("UIScrollView")
        /* ... */
    ]
    
    var filteredData : [BaseViewController]! = nil
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.filteredData = self.componentList
        
        self.searchBar.delegate = self
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.filteredData.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
        
        cell.textLabel?.text = self.filteredData[indexPath.row].componentName
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
        self.navigationController?.pushViewController(self.filteredData[indexPath.row], animated: true)
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        self.filteredData = searchText.isEmpty ? self.componentList : self.componentList.filter({$0.componentName.contains(searchText)})
        
        tableView.reloadData()
    }
}

AppDelegate.swift

//
//  AppDelegate.swift
//  UIKit component handling
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var effectView: UIView!
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }
    
    func applicationWillResignActive(_ application: UIApplication) {
        makeEffectView()
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
    }
    
    func applicationWillEnterForeground(_ application: UIApplication) {
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {
        removeEffectView()
    }
    
    func applicationWillTerminate(_ application: UIApplication) {
    }
}

extension AppDelegate {
    
    private func makeEffectView() {
        effectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffect.Style.light))
        effectView.frame = CGRect(x:0, y:0, width:UIScreen.main.bounds.size.width, height:UIScreen.main.bounds.size.height)
        self.window?.addSubview(effectView)
    }
    
    private func removeEffectView() {
        if effectView != nil {
            self.effectView.removeFromSuperview()
        }
    }
    
    // Add UIAlertController on UIViewController
    func showMessage(vc: UIViewController, title: String, message: String, actionTitle: String, actionStyle: UIAlertAction.Style) {
        // Create a UIAlertController.
        let alert: UIAlertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        
        // Add an Action of OK.
        alert.addAction(UIAlertAction(title: actionTitle, style: actionStyle, handler: { action in print("Action OK!") }))
        
        // Activate UIAlert.
        vc.present(alert, animated: true, completion: nil)
    }
}

LabelVC.swift

//
//  LabelVC.swift
//  UIKit component handling
//

import UIKit

class LabelVC: BaseViewController {

    lazy var label: UILabel = {
        // Define the size of the label.
        let width: CGFloat = 300
        let height: CGFloat = 100
        
        // Define coordinates to be placed.
        // (center of screen)
        let posX: CGFloat = self.view.bounds.width/2 - width/2
        let posY: CGFloat = self.view.bounds.height/2 - height/2
        
        // Label Create.
        let label: UILabel = UILabel(frame: CGRect(x: posX, y: posY, width: width, height: height))
        
        // Define background color.
        label.backgroundColor = .orange
        
        // Define text color.
        label.textColor = .white
        
        // Define text font.
        label.font = .systemFont(ofSize: 20, weight: .regular)
        
        // Define text of label.
        label.text = "Hello! :)"
        
        // Define count of line.
        // '0' is infinity
        label.numberOfLines = 0
        
        // Round UILabel frame.
        label.layer.masksToBounds = true
        
        // Radius of rounded corner.
        label.layer.cornerRadius = 20.0
        
        // Define shadow color.
        label.shadowColor = .gray
        
        // Define text Alignment.
        // options: .left, .right, .center, .justified, .natural
        label.textAlignment = .center
        
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add UILabel to view.
        self.view.addSubview(self.label)
    }
}

ImageViewVC.swift

//
//  ImageViewVC.swift
//  UIKit component handling
//

import UIKit

class ImageViewVC: BaseViewController {
    
    lazy var imageView: UIImageView = {
        // Set the size of UIImageView.
        let width: CGFloat = self.view.bounds.width/2
        let height: CGFloat = 150
        
        // Set x, y of UIImageView.
        let posX: CGFloat = (self.view.bounds.width - width)/2
        let posY: CGFloat = (self.view.bounds.height - height)/2
        
        // Create UIImageView.
        let imageView = UIImageView(frame: CGRect(x: posX, y: posY, width: width, height: height))
        
        // Create UIImage.
        let myImage = UIImage(named: "apple.png")!
        
        // Set the image to UIImageView.
        imageView.image = myImage
        
        return imageView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add UIImageView to view
        self.view.addSubview(self.imageView)
    }
}

ToolBarVC.swift

//
//  ToolBarVC.swift
//  UIKit component handling
//

import UIKit

class ToolBarVC: BaseViewController {

    private let toolBarHeight: CGFloat = 44
    
    lazy var toolBar: UIToolbar = {
        // Decide the size of the toolbar.
        let tb = UIToolbar(frame: CGRect(x: 0, y: self.view.bounds.size.height - toolBarHeight, width: self.view.bounds.size.width, height: 40.0))
        
        // Determine the position of the toolbar.
        tb.layer.position = CGPoint(x: self.view.bounds.width/2, y: self.view.bounds.height-20.0)
        
        // Decide the color of the toolbar.
        tb.barStyle = .blackTranslucent
        tb.tintColor = .white
        tb.backgroundColor = .black
        
        return tb
    }()
    
    lazy var barButtonGreen: UIBarButtonItem = {
        // Generate button 1.
        return UIBarButtonItem(title: "Green", style:.plain, target: self, action: #selector(onClickBarButton))
    }()
    
    lazy var barButtonBlue: UIBarButtonItem = {
        // Generate button 2.
        return UIBarButtonItem(title: "Blue", style:.plain, target: self, action: #selector(onClickBarButton))
    }()
    
    lazy var barButtonRed: UIBarButtonItem = {
        // Generate button 3.
        return UIBarButtonItem(title: "Red", style:.plain, target: self, action: #selector(onClickBarButton))
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Place the button in the tool bar.
        self.toolBar.items = [self.barButtonGreen, self.barButtonBlue, self.barButtonRed]
        
        // Add UIToolBar on view
        self.view.addSubview(self.toolBar)
    }
    
    // Called when UIBarButtonItem is pressed.
    @objc private func onClickBarButton(sender: UIBarButtonItem) {
        switch sender.title {
            case "Green": self.view.backgroundColor = .green
            case "Blue": self.view.backgroundColor = .blue
            case "Red": self.view.backgroundColor = .red
            default: return
        }
    }
}

Navigation Bar

FirstNavigationBarVC.swift

//
//  FirstNavigationBarVC.swift
//  UIKit component handling
//

import UIKit

class FirstNavigationBarVC: BaseViewController {
    
    lazy var button: UIButton = {
        // Create Button
        let b = UIButton(frame: CGRect(x: 0, y: 0, width: 250, height: 50))
        b.backgroundColor = .orange
        b.layer.masksToBounds = true
        b.setTitle("Go to second view", for: .normal)
        b.layer.cornerRadius = 20.0
        b.layer.position = CGPoint(x: self.view.bounds.width/2, y:200)
        b.addTarget(self, action: #selector(goToNextView), for: .touchDown)
        
        return b
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Define the background color of View.
        self.view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        
        // Add button on view
        self.view.addSubview(self.button)
        
        // Create a BarButtonItem.
        let barButton_1 = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(goToPreviousView))
        
        let barButton_2 = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(goToNextView))
        let barButton_3 = UIBarButtonItem(barButtonSystemItem: .fastForward, target: self, action: #selector(goToNextView))
        
        // Set the title.
        self.navigationItem.title = "First View"

        // Place it on the left side of Bar.
        self.navigationItem.setLeftBarButton(barButton_1, animated: true)
        
        // Place multiple on the right side of Bar.
        self.navigationItem.setRightBarButtonItems([barButton_2, barButton_3], animated: true)
    }
    
    // Called when the left bar button is pressed
    @objc private func goToPreviousView(sender: UIBarButtonItem) {
        self.navigationController?.popViewController(animated: true)
    }
    
    // Called when the right bar buttons and the button on the screen are pressed
    @objc private func goToNextView(sender: UIButton) {
        let secondViewController = SecondNavigationBarVC("Second view")
        self.navigationController?.pushViewController(secondViewController, animated: true)
    }
}

SecondNavigationBarVC.swift

//
//  SecondNavigationBarVC.swift
//  UIKit component handling
//

import UIKit

class SecondNavigationBarVC: BaseViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Define the background color of View.
        self.view.backgroundColor = UIColor.green
    }
}

PlayMusicalNotesVC.swift

//
//  PlaySoundVC.swift
//  UIKit component handling
//

import UIKit
import AVFoundation

class PlayMusicalNotesVC: BaseViewController {
    
    var audioPlayer : AVAudioPlayer!
    
    let sounds = ["do", "re", "mi", "fa", "sol", "la", "si", "do2"]
    
    lazy var stackView: UIStackView = {

        let stackView = UIStackView()
        
        stackView.frame = CGRect(x: 0, y: 98, width: self.view.bounds.width, height: self.view.bounds.height - 98)
        stackView.axis = .vertical
        stackView.distribution = .fillEqually
        
        for sound in sounds {
            let button = UIButton()
            
            // Set the background color of the button.
            button.backgroundColor = .white
            button.layer.borderWidth = 1
            button.layer.borderColor = UIColor.black.cgColor
            
            // Set the title (normal).
            button.setTitle(sound, for: .normal)
            button.setTitleColor(.black, for: .normal)
            
            // Set the title (highlighted).
            button.setTitle(sound, for: .highlighted)
            button.setTitleColor(.red, for: .highlighted)
            
            // Add an event.
            button.addTarget(self, action: #selector(onClick), for: .touchDown)
            
            stackView.addArrangedSubview(button)
        }
            
        return stackView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add UIStackView to view.
        self.view.addSubview(self.stackView)
    }
    
    // Button event.
    @objc private func onClick(_ sender: UIButton) {

        if let soundURL : URL = Bundle.main.url(forResource: sender.currentTitle, withExtension: "mp3") {
            print(soundURL)
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
                audioPlayer.play()
            } catch {
                print(error)
            }
        }
    }
}

WebServiceSchoolsVC

//
//  WebServiceSchoolsVC.swift
//  UIKit component handling
//

import UIKit

class Ciclo : Codable {
    var denominacion:String = ""
    var denominacion_val:String = ""
}

class WebServiceSchoolsVC: BaseViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
    
    // Define the array to use in the Table.
    var ciclos: [Ciclo] = []
    var ciclosFiltrados: [Ciclo] = []
    
    // Define the url to download the data
    let url = URL(string: "https://fernandoruizrico.com:8001/api/getListadoCiclos")
    
    lazy var searchBar: UISearchBar = {
        // Create a search bar.
        let searchBar = UISearchBar()
        searchBar.delegate = self
        searchBar.frame = CGRect(x: 0, y: 98, width: self.view.bounds.width, height: 100)
        
        // Add a shadow.
        searchBar.layer.shadowColor = UIColor.black.cgColor
        searchBar.layer.shadowOpacity = 0.5
        searchBar.layer.masksToBounds = false
        
        // Disable the bookmark button.
        searchBar.showsBookmarkButton = false
        
        // Set bar style to Default.
        searchBar.searchBarStyle = .default
        
        // Set descriptive text.
        searchBar.placeholder = "Buscar"
        
        // Set the color of the cursor and cancel button.
        searchBar.tintColor = .red
        
        // The search result display button is not displayed.
        searchBar.showsSearchResultsButton = false
        
        return searchBar
    }()
    
    lazy var tableView: UITableView = {

        let tableView = UITableView()
        
        tableView.frame = CGRect(x: 0, y: 200, width: self.view.bounds.width, height: self.view.bounds.height)
        
        // Register the Cell name.
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        
        // Set the DataSource.
        tableView.dataSource = self
        
        // Set Delegate.
        tableView.delegate = self
        
        return tableView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        URLSession.shared.dataTask(with: url!) { (data, response, error) in
            do {
                // Decode data
                self.ciclos = try JSONDecoder().decode([Ciclo].self, from: data!)
                self.ciclosFiltrados = self.ciclos
                
                DispatchQueue.main.async {
                    // Add UITableView on view
                    self.view.addSubview(self.searchBar)
                    self.view.addSubview(self.tableView)
                }
            } catch let error {
                print("Error: \(error)")
            }
        }.resume()
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return ciclosFiltrados.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        cell.textLabel?.text = self.ciclosFiltrados[indexPath.row].denominacion
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
        // Create a UIAlertController.
        let alert: UIAlertController = UIAlertController(title: "Ciclo", message: self.ciclosFiltrados[indexPath.row].denominacion+"\n"+self.ciclosFiltrados[indexPath.row].denominacion_val, preferredStyle: .alert)
        
        // Add an Action of OK.
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        
        present(alert, animated: true, completion: nil)
    }
    
    // Called whenever text is changed
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        self.ciclosFiltrados = searchText.isEmpty ? self.ciclos : self.ciclos.filter({$0.denominacion.contains(searchText)})
        self.tableView.reloadData()
    }
}

ScrollViewVC.swift

//
//  ScrollViewVC.swift
//  UIKit component handling
//

import UIKit

class ScrollViewVC: BaseViewController {
    
    lazy var scrollView: UIScrollView = {
        let scrollView: UIScrollView = UIScrollView()
        
        // Generate ScrollView.
        scrollView.frame = self.view.frame
        
        // Disable ScrollView bounces
        scrollView.bounces = false
        
        // Set the image in UIImage.
        let image = UIImage(named: "vertical.jpeg")!
        
        // Create a UIImageView.
        let imageView = UIImageView()
        
        // Set myImage to the image of imageView.
        imageView.image = image
        
        // Set the value of frame size
        imageView.frame.size = image.size
        
        // Set the aspect ratio of the image.
        imageView.contentMode = .scaleAspectFill
        
        // Add imageView to ScrollView.
        scrollView.addSubview(imageView)
        
        // Set contentSize to ScrollView.
        scrollView.contentSize = imageView.frame.size
        
        return scrollView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add UIScrollView on view
        self.view.addSubview(self.scrollView)
    }
}

Swift – Ejercicios propuestos (V)

Diseñar un encriptador en modo consola utilizando una clase que tenga una propiedad para guardar la equivalencia entre las letras sin encriptar y las letras encriptadas. Para ello se deberá utilizar un array de estructuras. Además, dicha clase deberá contener una función para encriptar y otra para desencriptar. Se deberá diseñar una clase para probarlo. Las letras a encriptar y su equivalente se podrán añadir directamente desde código, y la cadena de texto a encriptar se deberá pedir por teclado, mostrando el resultado por pantalla.

...

Diseñar un encriptador en modo gráfico utilizando una clase que permita añadir las letras a encriptar y su equivalente, guardando dicho listado de manera persistente. Además, la aplicación deberá tener dos pantallas, de forma que en la primera se puedan encriptar o desencriptar expresiones según la equivalencia que se encuentre guardada en la aplicación. En la segunda pantalla se deberá poder modificar el listado de las letras que se deben encriptar, permitiendo añadir o quitar letras, volviendo a guardar en cada modificación dicho listado para que se mantenga entre las distintas ejecuciones de la aplicación. Para ello se puede utilizar un array de estructuras que se guardará utilizando la librería ‘Disk’ que se puede instalar utilizando ‘CocoaPods’.

...

Desarrollar una aplicación para mostrar el uso y la personalización de los diversos componentes de la interfaz gráfica de la librería UIKit. Se deberán consultar las diferentes opciones que nos proporciona la librería y escribir el código swift necesario para cambiar el valor de las propiedades más significativas de cada uno de los componentes y visualizar los resultados:

UILabel, UIButton, UIBarButtonItem, UITextField (isSecureTextEntry), UITextView, UIFont, UIAlertController (UIAlertAction, alerts, action sheets), UIPickerView, UIImageView (UIImage, transform, CGAffineTransform, scale, rotate, reverse), UIScrollView, UITableView (UITableViewCell, cells, sections), UIPageControl, UISlider, UISwitch, UIDatePicker, UIActivityIndicatorView, UISearchBar, UIWindow, UIToolBar, UIProgressView, UISegmentedControl, UIStepper, UNUserNotificationCenter, WKWebView, UIStackView, etc.

Añadir a la aplicación anterior un teclado sencillo para reproducir las notas musicales básicas utilizando el componente UIStackView y el reproductor AVAudioPlayer de la librería AVFoundation. Tenéis un ejemplo completo en el último post, y los archivos de audio con las notas musicales en Google Drive:

...

Añadir a la aplicación anterior un apartado para reproducir diversos sonidos similares a los de la aplicación Instant Buttons. Se podrán utilizar una serie de botones distribuidos en forma de rejilla utilizando el componente UIStackView:

...

Añadir a la aplicación anterior un apartado para obtener de un servicio web un listado con todos los ciclos formativos que se imparten en la Comunidad Valenciana. Dicho listado se deberá mostrar utilizando un componente UITableView, y se deberán poder realizar búsquedas utilizando un componente UISearchBar. Además, se deberá proporcionar un mecanismo para poder mostrar los nombres de los ciclos formativos en castellano o valenciano (se puede utilizar por ejemplo un «action sheet» o también se pueden añadir botones a la barra de navegación, o se puede colocar una barra con dos botones en la parte inferior de la pantalla, etc.). Podéis encontrar un ejemplo completo sobre cómo personalizar la barra de navegación aquí, o cómo crear una barra de botones aquí.


Añadir a la aplicación anterior un apartado para obtener de un servicio web un listado con todos los institutos públicos o concertados de la Comunidad Valenciana donde se imparten ciclos formativos. Dicho listado se deberá mostrar utilizando un componente UITableView, y se deberán poder realizar búsquedas utilizando un componente UISearchBar. Además, al pulsar sobre un instituto en particular, se deberá mostrar un alert con toda la información relativa a dicho instituto:


Realizar una modificación en el apartado anterior para que se muestren todos los ciclos del centro educativo que se seleccione, pasando a una vista nueva (se puede consultar este enlace para ver un ejemplo de cómo añadir y navegar a una nueva vista). Para ello en la nueva vista se deberá realizar una consulta diferente utilizando el «id» del instituto seleccionado. Por ejemplo, para obtener los ciclos formativos que se imparten en el instituto IES San Vicente:

... {"id":47,"denominacion":"IES San Vicente","codigo_centro":"03008423","provincia":"Alacant","localidad":"Sant Vicent Del Raspeig","tipo_via":"Cl","domicilio":"Lillo Juan","numero":"128","cp":"3690","telefono":"965936505","fax":"965936506","email":"[email protected]","director":"Joaquín Pastor Pina","firmaLocalidad":"S. Vicent Raspeig","gestion_id":10,"gestion":{"id":10,"descripcion":"Público"}} ...

https://fernandoruizrico.com:8001/api/getListadoCiclos/47
...

Añadir a la búsqueda de centros educativos un componente «UIPickerView» para poder elegir qué tipo de búsqueda se pretende realizar (por nombre del instituto, por provincia, por localidad, etc.). Se deberá utilizar una enumeración para establecer los campos por los que se puede filtrar:

...

Añadir a la aplicación anterior un apartado para obtener datos sobre el tiempo atmosférico de una ciudad concreta que se introducirá por teclado. Se pueden realizar consultas a la api «http://api.openweathermap.org». A continuación se muestra una llamada y el resultado que se obtiene en formato JSON. Para obtener dicho resultado sólo es necesario proporcionar un identificador de usuario (appid) que se obtiene gratuitamente en el siguiente enlace «https://openweathermap.org/appid«:

https://api.openweathermap.org/data/2.5/weather?q=Alicante&appid=xxxxxxxx&lang=es


{"coord":{"lon":-74.23,"lat":10.65},"weather":[{"id":800,"main":"Clear","description":"cielo claro","icon":"01n"}],"base":"stations","main":{"temp":300.82,"pressure":1009,"humidity":83,"temp_min":300.15,"temp_max":301.15},"visibility":10000,"wind":{"speed":3.6,"deg":10},"clouds":{"all":0},"dt":1562913434,"sys":{"type":1,"id":8584,"message":0.0066,"country":"CO","sunrise":1562928074,"sunset":1562973803},"timezone":-18000,"id":3682292,"name":"Alicante","cod":200}

Añadir a la aplicación anterior un apartado para crear una lista de tareas utilizando un componente UITableView y la librería Disk para guardar el listado en el móvil, permitiendo por lo menos añadir y borrar tareas:


Swift – Ejercicios propuestos (IV)

Utiliza el método filter para obtener todos los nombre de personas que se encuentran en un array llamado nombres y que comiencen por la letra «A»:

let nombres = ["Ana", "Juan", "Pepe", "Anastasio", "Ángel"]
let nombresConA = nombres.filter { $0.first == "A" || $0.first == "Á" }
print("Los siguientes nombres comienzan por 'A': \(nombresConA)")

Utilizando el método reduce, calcula la suma de todos los enteros de un array:

let numeros = [1, 2, 3, 4, 6, 8, 9, 3, 12, 11]
let suma = numeros.reduce(0, {$0 + $1})
print("La suma de los números \(numeros) es \(suma)")

Utilizando el método map calcula el número real que se obtiene al dividir el numerador entre el denominador de diversas fracciones que se encuentran almacenadas en un array de tuplas, e imprime el resultado:

let fracciones = [(1,2), (2, 3), (5, 1), (4, 7)]
let reales = fracciones.map {Double($0.0) / Double($0.1)}
print("De las fracciones \(fracciones) se obtienen los números reales \(reales)")

Utilizando el método sorted, ordena ascendentemente todos los enteros de un array:

let numeros = [1, 6, 3, 34, 6, 8, 9, 3, 12, 11]
let numerosOrdenados = numeros.sorted(by: <)
print(numerosOrdenados)

Definir una clase libro que tenga dos propiedades: titulo y paginas. A continuación crear un array de libros con diferentes títulos y páginas, y ordenar los libros por el título de manera ascendente utilizando el método sorted. Finalmente se deberá mostrar el resultado por pantalla:

class Libro {
    var titulo = ""
    var paginas = 0
    init(_ titulo:String, _ paginas:Int) {
        self.titulo = titulo
        self.paginas = paginas
    }
}

var libros = [Libro("Juego de tronos", 250), Libro("El principito", 100), Libro("Blancanieves", 130)]
let librosOrdenados = libros.sorted(by: {$0.titulo < $1.titulo})
print("Libros ordenados por titulo:")
for libro in librosOrdenados {
    print(libro.titulo)
}

Utiliza el método filter para crear un array llamado multiplos que contenga todos los múltiplos de 3 de un array de enteros que se llame numeros e imprímelo para comprobar el resultado:

...

Utilizando el método reduce, encuentra el número más grande de entre los enteros de un array que se llame numeros e imprímelo:

...

Utilizando el método reduce, une todas las cadenas de un array en una sola añadiendo espacios entre cada una de ellas, e imprime el resultado:

...

Utilizando el método filter sobre un array que contiene los nombres de varias personas, muestra sólo aquellos cuyo nombre tiene más de 5 letras:

... 

Utilizando el método reduce, calcula la suma de todos los enteros de un array, y a continuación calcula la media e imprime el resultado:

...

Utilizando el método map convierte a minúsculas todas las cadenas almacenadas en un array, e imprime el resultado:

...

Utilizando el método map obtén un array con el número de letras que tienen cada una de las cadenas de otro array, e imprime el resultado:

...

Utilizando el método sorted, ordena descendentemente todos los enteros de un array:

...

Utilizando el método sorted, ordena ascendentemente todos los nombres de personas que se encuentran almacenados en un array:

...

Utilizando el código de ejercicios anteriores, crear un array de libros con diferentes autores, títulos y páginas, y ordenar los libros por el número de páginas de manera ascendente y descendente utilizando el método sorted, mostrando finalmente el resultado:

...

Utilizando el código de ejercicios anteriores, crear un array de álbumes de fotos, y ordenarlos de manera ascendente por número de páginas:

...

Utilizando el código de ejercicios anteriores, crear un array de vehículos y ordenarlos de manera ascendente según su velocidad:

...

Swift – Closures

Introducción

Los closures son bloques de código que pueden ser pasados por parámetro a otras funciones a lo largo del código. Una de las ventajas que proveen es que son capaces de capturar y guardar referencias de las constantes y variables del contexto en que son definidos.

Las funciones, vistas en una entrada anterior, son casos especiales de closures, que tienen la característica de estar definidos bajo un nombre. Sin embargo, en algunos casos es necesario escribir algoritmos más cortos y específicos que no van a ser reutilizados o invocados varias veces sino que solo tienen sentido en un ámbito en particular. El caso más claro es el de funciones que aceptan funciones por parámetro, y que al momento de invocarlos se les pasa closures, sin nombre ni una declaración completa, sino simplemente el código de su implementación.

Las expresiones de cerraduras proveen muchas optimizaciones en su sintaxis permitiendo simplificarlas sin perder el sentido y la claridad.

Para hacer una analogía con otros lenguajes, una cerradura es similar a una expresión lambda o bloques de Objective C.

Sintaxis

La forma general de una cerradura es la siguiente:

{ (parametros) -> tipo de retorno in
    sentencias
}

Como se puede observar, su sintaxis es la de una función con la diferencia de que no posee un nombre que la defina y que agrega la palabra in para separar su declaración de su implementación. Los parámetros pueden ser in-out pero no pueden tener valores por defecto.

Supongamos que tenemos la siguiente función:

func calcular(a:Int, b:Int, operacion:(Int,Int) -> Int) {
    print(“El resultado es \(operacion(a,b))”)
}

Para poder invocarla, necesitamos pasar dos números enteros y una función que reciba dos enteros y devuelva otro. Una posibilidad de hacerlo es definir una función que cumpla con el tipo del parámetro operación y pasarla por parámetro:

func sumar(a:Int, b:Int) -> Int {
    return a+b
}

calcular(a: 6, b: 10, operacion: sumar)

//Devuelve:
//El resultado es 16

De esta forma, podemos ejecutar una función u otra simplemente especificando el nombre de la función como último parámetro:

func sumar(a:Int, b:Int) -> Int {
    return a+b
}

func multiplicar(a:Int, b:Int) -> Int {
    return a*b
}

func calcular(a:Int, b:Int, operacion:(Int,Int) -> Int) {
    print("El resultado es \(operacion(a,b))")
}

calcular(a: 6, b: 10, operacion: sumar)
calcular(a: 6, b: 10, operacion: multiplicar)

// El resultado es 16
// El resultado es 60

Sin embargo, hacerlo de esta manera implica generar código que posiblemente no se vuelva a reutilizar y requiere de varias líneas para simplemente realizar una operación aritmética entre dos números.

En estos casos, es donde los closures vienen a ayudarnos a definir código mas específico y, al mismo tiempo, nos permite ahorrarnos algunas líneas. Una forma de hacer lo mismo usando closures podría ser:

func calcular(a:Int, b:Int, operacion:(Int, Int) -> Int) {
    print("El resultado es \(operacion(a,b))")
}

calcular(a: 6, b: 10, operacion: {(numero1:Int, numero2:Int) -> Int in return numero1 + numero2 })

calcular(a: 6, b: 10, operacion: {(numero1:Int, numero2:Int) -> Int in return numero1 * numero2 })

Simplificando la sintaxis

Lo primero que se puede simplificar al momento de usar un closure en la invocación de una función son los tipos de dato de sus parámetros y valor de retorno. En este sentido, Swift es capaz de inferir que el closure que estamos pasando debe cumplir con la forma (Int,Int) -> Int, por lo tanto no es necesario dejar explícitos los tipos de dato ni la flecha:

calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
    return numero1 + numero2
})

calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
    return numero1 * numero2
})

Además, los closures de una sola línea pueden omitir la palabra return ya que se sabe que la operación que se va a realizar dentro de ella se va a usar para devolver el dato que se espera:

calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
    numero1 + numero2
})

calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
    numero1 * numero2
})

Se dice que los closures son de una línea, o single-expression, cuando contienen una única operación independientemente de que hagamos cortes de línea por un tema de claridad al momento de leer el código.

Otra opción que podemos usar son los nombres de argumento corto que provee Swift para identificar a los argumentos, usando los nombres $0, $1, $2, etc, para hacer referencia al primer argumento, segundo, tercero, etc. Al mismo tiempo, se elimina la necesidad de usar la palabra reservada in ya que no se requiere separar la zona de argumentos de la de implementación:

calcular(a: 6, b: 10, operacion: { $0 + $1 })

calcular(a: 6, b: 10, operacion: { $0 * $1 })

Existe una forma aún más corta de escribir el closure anterior y es haciendo uso de los métodos de operador. Toda clase o struct puede definir su propia implementación de los operadores existentes haciendo uso de una técnica llamada sobrecarga. En el caso del tipo de dato Int (que internamente es un struct) tiene definido al operador + como una función del tipo (Int, Int) -> Int que es justamente lo que espera el parámetro calcular. Por lo tanto, la invocación anterior puede hacerse de la siguiente manera:

calcular(a: 6, b: 10, operacion: +)

calcular(a: 6, b: 10, operacion: *)

Trailing closures

Cada vez que escribimos una función que recibe un closure como parámetro, es recomendable dejar ese parámetro último en la lista para poder hacer uso de los trailing closures. El concepto de trailing hace alusión a que el closure puede escribirse por fuera del listado de parámetros, fuera de los paréntesis y a continuación de estos, para poder escribir un código más claro y sin la necesidad de especificar el nombre del argumento. En el caso de que la función no reciba otro parámetro adicional más que el closure, se pueden omitir los paréntesis:

func funcionCualquiera(closure: () -> Void) {

}

//llamando a la función sin usar trailing closure
funcionCualquiera(closure: {
    //Codigo
})

//llamando a la función con trailing closure
funcionCualquiera() {
    //Codigo
}

//llamando a la función con closure sin los paréntesis
funcionCualquiera {
    //Codigo
}

Volviendo al ejemplo anterior, usando lo aprendido se debería escribir así:

calcular(a: 6, b: 10) { $0 + $1 }

calcular(a: 6, b: 10) { $0 * $1 }

Capturar valores usando closures

Como dijimos al principio, los closures pueden capturar valores del ámbito donde están definidos y hacer uso de esos valores incluso cuando este contexto ya no existe.

El ejemplo más claro es el caso de las funciones anidadas, donde la función interior captura las variables que pertenecen a la función que la engloba para realizar sus cálculos, incluso cuando solo se hace uso de la función interna.

Supongamos que tenemos una función que posee un atributo y una función anidada, y que al llamarla la misma devuelve a dicha función, de manera de poder ser utilizada fuera de su ámbito.

func hacerIncremento(en cantidad:Int) -> () -> Int {
    var total = 0
    func incrementar() -> Int {
        total += cantidad
        return total
    }
    return incrementar
}

Como se puede observar, al invocar a hacerIncremento(en:) lo que obtenemos es una referencia a la función incrementar(_:), en lugar de un simple entero, la cual aumenta el valor del atributo total que pertenece a la función que la engloba. Cabe destacar que los atributos total y cantidad, si bien son usados dentro de la función incrementar(_:) no pertecen a ella, sino que ésta captura sus valores al momento de ejecutarse.

let incrementarPorDiez = hacerIncremento(en:10)

print(incrementarPorDiez()) //Devuelve 10
print(incrementarPorDiez()) //Devuelve 20
print(incrementarPorDiez()) //Devuelve 30

Aquí hay un caso curioso. Como podemos ver, incrementarPorDiez es una constante con lo cual no debería poder cambiar su valor y sin embargo estamos observando que cada vez que lo ejecutamos aumenta en 10 su valor de retorno. Esto es posible porque los closures son tipos por referencia. Esto implica que cada vez que asignamos una constante o variable a un closure, en realidad lo que estamos haciendo es asignar la dirección de memoria donde ese closure existe a esa variable o constante, en lugar de hacer una copia fiel del mismo.

En otras palabras, cuando ejecutamos esta línea:

let incrementarPorDiez = hacerIncremento(en:10)

Swift determina un espacio en memoria para que exista la función hacerIncremento(en:) y devuelve la dirección de memoria que es alojada en incrementarPorDiez. Si hiciéramos otra constante apuntando a otra versión distinta de hacerIncremento(en:) obtendríamos lo siguiente:

let incrementarPorCuatro = hacerIncremento(en:4)

print(incrementarPorCuatro()) //Devuelve 4
print(incrementarPorCuatro()) //Devuelve 8
print(incrementarPorCuatro()) //Devuelve 12

Como es un incrementador nuevo, la variable total es nueva también y arranca desde el principio, con lo cual, a estas alturas tenemos 2 incrementadores distintos en memoria. Ahora bien, si queremos asignar a una constante el primer incrementador, sucede lo siguiente:

let otroIncrementadorPorDiez = incrementarPorDiez

print(otroIncrementadorPorDiez()) //Devuelve 40

Como vemos, no arranca de 0 sino de 30, que es donde había quedado el incrementador por 10. Esto sucede porque en la constante otroIncrementadorPorDiez lo que se está almacenando es la misma dirección de memoria a la que apunta incrementarPorDiez, con lo cual el atributo total al que están incrementando es el mismo.

Ejemplos completos

func calcular(a:Int, b:Int, operacion:(Int,Int)->Int){
    print("El resultado es \(operacion(a,b))")
}

func sumar(a:Int, b:Int) -> Int{
    return a+b
}

calcular(a: 6, b: 10, operacion: sumar)

//Devuelve:
//El resultado es 16

calcular(a: 6, b: 10, operacion: {(numero1:Int, numero2: Int) -> Int in
                                    return numero1 + numero2
                                })

calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
    return numero1 + numero2
})


calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
    numero1 + numero2
})

calcular(a: 6, b: 10, operacion: { $0 + $1 })

calcular(a: 4, b: 5, operacion: +)



// Trailing Closures
func funcionCualquiera(closure: () -> Void) {
    
}

//llamando a la función sin usar trailing Closure
funcionCualquiera(closure: {
    //Codigo
})

//llamando a la función con trailing Closure
funcionCualquiera() {
    //Codigo
}

calcular(a: 6, b: 10) { $0 + $1 }

func hacerIncremento(en cantidad:Int) -> () -> Int {
    var total = 0
    func incrementar() -> Int {
        total += cantidad
        return total
    }
    return incrementar
}

let incrementarPorDiez = hacerIncremento(en:10)
incrementarPorDiez()
//Devuelve 10
incrementarPorDiez()
//Devuelve 20
incrementarPorDiez()
//Devuelve 30

let incrementarPorCuatro = hacerIncremento(en: 4)
incrementarPorCuatro()
//Devuelve 4
incrementarPorCuatro()
//Devuelve 8
incrementarPorCuatro()
//Devuelve 12

let otroIncrementadorPorDiez = incrementarPorDiez
otroIncrementadorPorDiez()
//Devuelve 40

Swift – Ejercicios propuestos (III)

Crea una clase llamada «Obstaculos» para crear los obstáculos de un videojuego. Debe tener un inicializador con los parámetros de anchura y altura de los obstáculos, que serán las propiedades de la clase. Deberá tener además un método «mostrar» que deberá listar esos datos por pantalla.

Crea un array de 10 obstáculos cuyas alturas y anchuras serán asignadas de forma aleatoria. La altura debe estar entre 40 y 100 píxeles y la anchura entre 90 y 120 píxeles y muestra todos los datos al final.

...

Crea una clase «Punto3D» para representar un punto en un espacio de 3 dimensiones que guarde las coordenadas x, y, z de dicho punto. Debe implementar los siguientes métodos:

  • Un inicializador para establecer los valores x, y, z.
  • Un método para mover dicho punto a otras coordenadas.
  • Un método para calcular la distancia entre el punto que represente el objeto, y el de otro objeto que reciba como parámetro. Para ello se deberá calcular la raiz cuadrada (se puede utilizar el método «squareRoot()» del tipo «Double») de la siguiente expresión: (xb – xa)2 + (yb – ya)2 + (zb – za)2
  • Un método «toString» que devolverá un «String» de la siguiente forma: (x, y, z)
  • Los get y los set para actualizar cada una de las coordenadas de forma individual.

Se deberá desarrollar además una clase de prueba que cree un array de 5 puntos, pidiendo los valores de las coordenadas y calcule y enseñe por pantalla la distancia desde el primer punto al resto.

...

Crear un array de libros utilizando el código de ejercicios anteriores. Se deberá crear un menú de consola con las opciones de añadir nuevo y libro y ver los datos de los libros existentes.

...

Crear un array de documentos utilizando el código de ejercicios anteriores. Se deberá crear un menú con las opciones de añadir nuevo documento o libro, pidiendo desde consola los datos correspondientes según se trate de un tipo u otro. También deberemos desarrollar la opción para ver los datos de los libros existentes.

...

Crear una clase «Puerta» para desarrollar un videojuego con un alto y un ancho y un método para mostrar dichos valores. Crear además una clase «Casa» para el mismo videojuego. Dicha clase contendrá 3 puertas, y tendrá otro método para mostrar por pantalla la palabra «Casa» y los datos de las tres puertas.

...

Crear una clase llamada «FotoAlbum» con un atributo que indique el número de páginas. Deberá tener un inicializador que reciba el número de páginas, y otro al que no se le pase ningún parámetro. Este último deberá crear el foto álbum con 16 páginas por defecto.

Se deberá desarrollar un método para mostrar por pantalla la siguiente expresión: «Soy un foto álbum de N páginas»

Además, se deberá desarrollar otra clase que herede de FotoAlbum y que se llame GranAlbum. Esta clase tendrá un inicializador sin parámetros que se utilice el de la clase base, generando un álbum de 64 páginas por defecto.

Por último, se deberá desarrollar un método para mostrar la siguiente expresión: «Soy un foto álbum de N páginas y soy grande», utilizando la función mostrar de la clase base para mostrar la primera parte del mensaje.

...

Crea una clase que se llame «Encriptador» con dos métodos estáticos para encriptar y desencriptar. Recibirán un «String» por parámetro devolviendo dicha cadena encriptada o desencriptada según el caso. La encriptación se llevará a cabo cambiando las vocales por los siguientes caracteres:

  • a -> |
  • e -> /
  • i -> @
  • o -> $
  • u -> &

Desarrollar una clase para probar dicho encriptador con varios ejemplos, teniendo en cuenta que se deben llamar a los métodos para encriptar o desencriptar sin crear un objeto, simplemente accediendo a la función de la clase con el «.» y pasando la cadena como parámetro de la función.

...

Swift – Ejercicios propuestos (II)

Desarrollar una clase para comprobar si un año es bisiesto:

class Bisiesto {
    var anyo:Int

    init(_ anyo: Int?) {
        self.anyo = Int(anyo ?? 0)
    }

    func esBisiesto() -> Bool {
        return (anyo % 4 == 0 && anyo % 100 != 0) || anyo % 400 == 0
    }
}

var anyo = Bisiesto(2300)

print(anyo.esBisiesto())

Crea una clase llamada Persona. Esta clase deberá tener un atributo «nombre», de tipo String. También deberá tener un método «setNombre», con un parámetro String, que permita cambiar el valor del nombre. Finalmente, también tendrá un método «saludar», que escribirá en pantalla «Hola, soy » seguido de su nombre. Crea también una clase llamada PruebaPersona. Esta clase deberá contener sólo la función main, que creará dos objetos de tipo Persona, les asignará un nombre a cada uno y les pedirá que saluden.

class Persona {
    var nombre:String = ""
    func setNombre(_ nombre:String) {
        self.nombre = nombre
    }
    func saludar() {
        print("Hola soy \(nombre)")
    }
}

class PruebaPersona {
    func main() {
        let persona1 = Persona()
        let persona2 = Persona()
        
        persona1.setNombre("Pepe")
        persona2.setNombre("Juan")
        
        persona1.saludar()
        persona2.saludar()
    }
}

var prueba = PruebaPersona()
prueba.main()

Para guardar información sobre libros, vamos a comenzar por crear una clase «Libro», que contendrá atributos «autor», «titulo», «ubicacion» (todos ellos Strings) y métodos get y set adecuados para leer su valor y cambiarlo (getAutor, getTitulo, …). Crea también una clase llamada «PruebaLibro». Esta clase deberá contener sólo la función «main», que cree un objeto de la clase «Libro», dé valores a sus tres atributos y luego los muestre.

...

Crea una clase «Coche», con atributos «marca» (texto), «modelo» (texto), «cilindrada» (número entero), potencia (número real) y métodos get y set adecuados para leer su valor y cambiarlo (getMarca, getModelo, …). Crea también una clase llamada «PruebaCoche». Esta clase deberá contener sólo la función «main», que cree un objeto de la clase «Coche», dé valores a sus tres atributos y luego los muestre.

...

Se debe crear una nueva clase «PersonaInglesa». Esta clase deberá heredar las características de la clase «Persona», y añadir un método «tomarTe», que escribirá en pantalla «Estoy tomando té». Crea también una clase llamada PruebaPersona2. Esta clase deberá contener sólo la función main, que creará dos objetos de tipo «Persona» y uno de tipo PersonaInglesa, les asignará un nombre, les pedirá que saluden y pedirá a la persona inglesa que tome té.

...

Crea una clase «Documento», de la que «Libro» heredará todos sus atributos y métodos. Ahora la clase «Libro» contendrá sólo un atributo «paginas», número entero, con sus correspondientes «getPaginas» y «setPaginas». Será la clase «Documento» la que tenga ahora las propiedades «autor», «titulo» y «ubicación».

...

Crea una clase «Vehiculo», de la que heredarán «Coche» y una nueva clase «Moto». La clase «Vehiculo» contendrá todos los atributos y métodos que antes estaban en «Coche», y tanto «Coche» como «Moto» heredarán de ella. Crea otra clase para probar el código.

...

Modificar la clase «PersonaInglesa» para que redefina el método «Saludar», para que escriba en pantalla «Hi, I am » seguido de su nombre; se creará una nueva clase «PersonaItaliana», que deberá heredar las características de la clase «Persona», pero se deberá redefinir el método «Saludar», para que escriba en pantalla «Ciao «; crea también una clase llamada PruebaPersona3, que deberá contener sólo la función «main» y creará un objeto de tipo «Persona», uno de tipo «PersonaInglesa», y uno de tipo «PersonaItaliana», les asignará un nombre, les pedirá que saluden y pedirá a la persona inglesa que tome té.

...

Amplia las clases del tipo persona, para que todas ellas contengan inicializadores. Los inicializadores de casi todas las clases estarán vacíos, excepto el de «PersonaInglesa», que prefijará su nombre a «John». Crea también un inicializador alternativo para esta clase que permita escoger cualquier otro nombre.

				

Ampliar la clase «Libro» para que tenga un inicializador que permita dar valores al autor, al título y la ubicación.

...

Mejora la clase «Coche» para añadir un atributo «cantidadDeRuedas» a la clase «Vehiculo», junto con sus get y set. El inicializador de la clase «Coche» le dará el valor 4 y el inicializador de la clase «Moto» le dará el valor 2.

...

Añade a la clase «Persona» un nuevo método «saludar», que reciba un parámetro, que será el texto que debe decir esa persona cuando salude.

...

Ampliar la clase «Libro» para que tenga un segundo inicializador que permita dar valores al autor y al título, pero no a la ubicación, que tomará el valor por defecto «No detallada».

...

Crear dos nuevos métodos en la clase «Vehiculo»: uno llamado «circular», que fijará su «velocidad» (un nuevo atributo) a 50, y otro «circular(v)», que fijará su velocidad al valor que se indique como parámetro.

...

Escribe una clase llamada «Mensaje» que guarde un «remite», «destino» y «contenido» de tipo String y una fecha de tipo entero (aaaammdd). El remite, destino y contenido deberán tener funciones para consultar y actualizar sus valores, pero las funciones «getFecha» y «setFecha» deberán manipular el valor de la fecha antes de guardarlo y antes de mostrarlo. Se deberá desarrollar un método «mostrar» para imprimir el mensaje de la siguiente forma:

Fecha: dd/mm/aaaa
Remite: xxxxxxxxxx, Destino: xxxxxxxxxx
Mensaje: xxxxxxxxxx

Se deberá crear una clase adicional «PruebaMensaje» donde se pidan los datos del mensaje y se muestren por pantalla. La fecha se deberá pedir en formato «dd/mm/aaaa», y se deberá convertir a un entero con el formato mencionado (se puede utilizar el método «split» del tipo String para separar el día, el mes y el año).

...

Crea una clase llamada «Tablero» que tendrá un atributo llamado «color» y otro llamado «material». Se deberá crear otra clase llamada «Mesa» que tendrá un atributo para guardar el tipo de tablero que tiene. En la mesa además se deberán guardar otros atributos como «altura» y «anchura». El tablero deberá tener un inicializador que asigne los valores por defecto de color «marrón» y material «madera». Se deberán desarrollar los métodos correspondientes para actualizar y consultar las propiedades, y un método mostrar en la clase «Mesa» que muestre todos sus datos, incluyendo los del tablero con el que está hecha. Además, se deberá probar dicho código con una clase adicional.

...

Un centro cultural se dedica al préstamo de dos tipos de materiales de préstamo: discos y libros. Para los dos se debe guardar información general, como su código identificativo, el título y el autor. En el caso de los libros, almacenaremos también su número de páginas, y para los discos, el nombre de la discográfica. Al centro cultural acuden una serie de clientes, de los que se guarda su DNI y nombre, que realizan una serie de peticiones de discos o libros, como mucho hasta 5 peticiones. Para cada petición se guarda la fecha de inicio y fin del préstamo. Crea todas las clases necesarias y los métodos para gestionar toda esta información, junto con una clase adicional para probarlo. Se necesitarán métodos del estilo «agregaPeticion» que añadirá una petición nueva dentro del cliente, comprobando que no llegue al máximo de préstamos.

...

Traductor español-inglés muy sencillo con Swift

Modo consola

Introduce una palabra: coche
Traducción: car

Introduce una palabra: car
Traducción: coche

Introduce una palabra: casa
Traducción: house

Introduce una palabra: dog
Traducción: perro

Introduce una palabra: aaaa
Traducción: palabra no encontrada

Introduce una palabra:
...
var palabras = [("coche", "car"), 
                ("gato",  "cat"),
                ("perro", "dog"),
                ("casa", "house")]

func traduce(_ entrada:String) -> String {
    for palabra in palabras {
        if palabra.0 == entrada { return palabra.1 }
        else if palabra.1 == entrada { return palabra.0 }
    }
    return "palabra no encontrada"
}

repeat {
    print("Introduce una palabra: ", terminator:"")
    let entrada = readLine()!
    let salida = traduce(entrada)
    print("Traducción: \(salida)\n")
}
while true

Aplicación iOS

Traducir con el evento de cambio de texto

//
//  ViewController.swift
//  Traductor
//

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    var palabras = [("coche", "car"),
                    ("gato",  "cat"),
                    ("perro", "dog"),
                    ("casa", "house")]

    @IBOutlet var traduccion: UILabel!
    
    func traduce(_ entrada:String) -> String {
        for palabra in palabras {
            if palabra.0 == entrada { return palabra.1 }
            else if palabra.1 == entrada { return palabra.0 }
        }
        return "palabra no encontrada"
    }
    
    @IBAction func traduce(_ entrada: UITextField) {
        traduccion.text = traduce(entrada.text!)
    }
}

Traducir al pulsar un botón

//
//  ViewController.swift
//  Traductor
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet var entrada: UITextField!
    @IBOutlet var salida: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    var palabras = [("coche", "car"),
                    ("gato",  "cat"),
                    ("perro", "dog"),
                    ("casa", "house")]
    
    func traduce(_ entrada:String) -> String {
        for palabra in palabras {
            if palabra.0 == entrada { return palabra.1 }
            else if palabra.1 == entrada { return palabra.0 }
        }
        return "palabra no encontrada"
    }

    @IBAction func boton(_ sender: UIButton) {
        salida.text = traduce(entrada.text!)
    }
}

Utilizando una clase y una estructura para añadir nuevas palabras y guardar el diccionario

Instalación de La librería «Disk»

Vamos a proceder a instalar la librería «Disk» utilizando el gestor de librerías «CocoaPods«:

  • Crear un proyecto de Xcode del tipo «TabbedApp»
  • Abre un terminal y ves al directorio del proyecto utilizando el comando $ cd .
  • Crear el fichero Podfile utilizando el comando $ pod init.
  • Abre el fichero «Podfile». La primera línea debe indicar la plataforma y la versión de iOS:  platform :ios, '12.2'.
  • Añade la línea pod 'Disk' dentro de la sección target.
  • Guarda el fichero.
  • Instala la librería con la instrucción $ pod install .
  • Abre el fichero .xcworkspace que se ha creado. A partir de ahora deberemos abrir ese fichero en vez del .xcodeproj .

El contenido final del archivo «Podfile» sería el siguiente:

platform :ios, '12.2'

target 'traductor' do
  use_frameworks!

  # Pods for Traductor
  pod 'Disk'
end

Diccionario.swift

//
//  Diccionario.swift
//  Traductor
//

import Disk

struct Palabra : Codable {
    let es : String
    let en : String
}

class Diccionario {
    var palabras : [Palabra] = []
    
    func add(_ palabra:Palabra) {
        self.palabras += [palabra]
    }
    
    func traduce(_ entrada:String) -> String {
        for palabra in self.palabras {
            if palabra.es == entrada { return palabra.en }
            else if palabra.en == entrada { return palabra.es }
        }
        return "palabra no encontrada"
    }
    
    func guarda() {
        do {
            try Disk.save(self.palabras, to: .applicationSupport, as: "diccionario.json")
        }
        catch {
            print("Error guardando")
        }
    }
    
    func lee() {
        do {
            try self.palabras = Disk.retrieve("diccionario.json", from: .applicationSupport, as: [Palabra].self)
        }
        catch {
            print("Error leyendo")
        }
    }
}

AppDelegate.swift

//
//  AppDelegate.swift
//  Traductor
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var diccionario = Diccionario()
    
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        self.diccionario.lee()
        
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
    }

    func applicationWillTerminate(_ application: UIApplication) {
    }
}

FirstViewController.swift

//
//  FirstViewController.swift
//  Traductor
//

import UIKit

class FirstViewController: UIViewController {

    var diccionario : Diccionario! = nil
    
    @IBOutlet var traduccion: UILabel!
    @IBOutlet var entrada: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        
        self.diccionario = appDelegate.diccionario
        
        print(self.diccionario.traduce("dog"))
    }

    @IBAction func traducir(_ sender: UITextField) {
        traduccion.text! = self.diccionario.traduce(sender.text!)
    }
}

SecondViewController.swift

//
//  SecondViewController.swift
//  Traductor
//

import UIKit

class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet var palabras: UITableView!

    var diccionario : Diccionario! = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.palabras.dataSource = self
        self.palabras.delegate = self

        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        
        self.diccionario = appDelegate.diccionario
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return diccionario.palabras.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
        cell.textLabel?.text = diccionario.palabras[indexPath.row].es + ", " + diccionario.palabras[indexPath.row].en
        return cell
    }
    
    @IBAction func addPalabra(_ sender: UIButton) {
        let alert = UIAlertController(title: "Añadir palabra", message: "", preferredStyle: .alert)
        
        alert.addTextField { (UITextField) in
            UITextField.placeholder = "Palabra en español"
        }

        alert.addTextField { (UITextField) in
            UITextField.placeholder = "Palabra en inglés"
        }
        
        alert.addAction(UIAlertAction(title: "Añadir", style: .default, handler: { (UIAlertAction) in
            let es = alert.textFields![0] as UITextField
            let en = alert.textFields![1] as UITextField
            self.diccionario.add(Palabra(es:es.text!, en:en.text!))
            self.diccionario.guarda()
            self.palabras.reloadData()
        }))
        
        alert.addAction(UIAlertAction(title: "Cancelar", style: .destructive, handler: nil))
        
        self.present(alert, animated: true, completion: nil)
    }
}

Cómo borrar palabras del diccionario

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
{
    if editingStyle == UITableViewCell.EditingStyle.delete {
        self.diccionario.palabras.remove(at: indexPath.row)
        self.diccionario.guarda()
        self.palabras.reloadData()
    }
}

Calculadora con Swift

Modo consola

Primer número: 1
Operación: +
Segundo número: 4
Resultado: 5

Primer número: r
Error

Primer número: 4
Operación: m
Error

Primer número: 1
Operación: /
Segundo número: 0
No se puede dividir por cero

Primer número: 4
Operación: -
Segundo número: 6
Resultado: -2

Primer número: 
...
enum OperacionCalculadora {
    case sumar, restar, multiplicar, dividir
    
    init?(_ boton:String) {
        switch boton {
            case "+": self = .sumar
            case "-": self = .restar
            case "*": self = .multiplicar
            case "/": self = .dividir
            default: return nil
        }
    }
    
    func calcular(_ n1: Int, _ n2: Int) -> Int {
        switch self {
            case .sumar: return n1 + n2
            case .restar: return n1 - n2
            case .multiplicar: return n1 * n2
            case .dividir: return n1 / n2
        }
    }
}

var n1:Int? = 0, n2:Int? = 0
var operacion:OperacionCalculadora? = nil
var pantalla:String = "0"

func actualizaNumero(_ boton:String) -> Int? {
    n1 = Int(pantalla)
    n2 = Int(boton)
    if n2 != nil {
        pantalla = boton
    }
    return n2
}

func actualizaOperacion(_ boton:String) -> OperacionCalculadora? {
    operacion = OperacionCalculadora(boton)
    return operacion
}

func realizaCalculo() -> Bool {
    if n2 == 0 && operacion == .dividir {
        print("No se puede dividir por cero\n")
        return false
    }
    pantalla = String(operacion!.calcular(n1!, n2!))
    return true
}

repeat {
    print("Primer número: ", terminator:"")
    if actualizaNumero(readLine()!) == nil {
        print("Error\n")
        continue 
    }
    print("Operación: ", terminator:"")
    if actualizaOperacion(readLine()!) == nil {
        print("Error\n")
        continue
    }
    print("Segundo número: ", terminator:"")
    if actualizaNumero(readLine()!) == nil {
        print("Error\n")
        continue 
    }
    
    if realizaCalculo() == true {
        print("Resultado: " + pantalla + "\n")
    }
} while true

Aplicación iOS

//
//  ViewController.swift
//  Calculadora
//

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet var pantalla: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    enum OperacionCalculadora {
        case sumar, restar, multiplicar, dividir
        
        init?(_ boton:String) {
            switch boton {
                case "+": self = .sumar
                case "-": self = .restar
                case "*": self = .multiplicar
                case "/": self = .dividir
                default: return nil
            }
        }
        
        func calcular(_ n1: Int, _ n2: Int) -> Int {
            switch self {
                case .sumar: return n1 + n2
                case .restar: return n1 - n2
                case .multiplicar: return n1 * n2
                case .dividir: return n1 / n2
            }
        }
    }
    
    var n1:Int? = 0, n2:Int? = 0, borraNumero = false
    var operacion:OperacionCalculadora? = nil
    
    // Ejecutar al pulsar los botones del 0 al 9
    @IBAction func actualizaNumero(_ boton:UIButton) {
        if pantalla.text == "0" || borraNumero == true {
            pantalla.text = ""
            borraNumero = false
        }
        pantalla.text = pantalla.text! + boton.currentTitle!
    }
    
    // Ejecutar al pulsar los botones de +, -, *, /
    @IBAction func actualizaOperacion(_ boton:UIButton) {
        operacion = OperacionCalculadora(boton.currentTitle!)
        n1 = Int(pantalla.text!)!
        borraNumero = true
    }
    
    // Ejecutar al pulsar el botón de =
    @IBAction func realizaCalculo() {
        n2 = Int(pantalla.text!)!
        if n2 == 0 && operacion == .dividir {
            muestraError("No se puede dividir por cero")
        }
        else {
            pantalla.text = String(operacion!.calcular(n1!, n2!))
        }
        borraNumero = true
    }

    func muestraError(_ mensaje:String) {
        let alert = UIAlertController(title:"Error", message:mensaje, preferredStyle:.alert)
        
        let action = UIAlertAction(title:"OK", style:.default, handler:nil)
        
        alert.addAction(action)
        
        show(alert, sender:nil)
    }
}

Swift – Ejercicios propuestos (I)

Pedirle un número al usuario y decir si es par usando «if»

var numero:Int;

print("Dime un número: ", terminator:"")
numero = Int(readLine()!)!

// Comprobamos si es par
if numero % 2 == 0 {
	print("\(numero) es par")
}
else {
    print("\(numero) no es par")
}

Pedirle dos números al usuario y ver cuál es el mayor de ellos


Pedirle al usuario un número. Si es múltiplo de 10, pedirle un segundo número y comprobar si también es múltiplo de 10

var numero1 = 0
var numero2 = 0

print("Dime el primer numero: ", terminator:"")
numero1 = Int(readLine()!) ?? 0 

if numero1 % 10 == 0 {
    print("\(numero1) es multiplo de 10, dame otro numero: ", terminator:"")
    numero2 = Int(readLine()!) ?? 0 
    
    if numero2 % 10 == 0 {
        print("El número \(numero2) también es múltiplo de 10\n")
    }
    else {
        print("El número \(numero2) no es múltiplo de 10\n")
    }
}
else {
    print("El número \(numero1) no es múltiplo de 10\n")
}

Multiplicar dos números si el primero no es cero (si es cero, avisar de que el resultado será cero)

var numero1 = 0
var numero2 = 0

print("Dime el primer numero: ", terminator:"")
numero1 = Int(readLine()!) ?? 0 

print("Dime el segundo numero: ", terminator:"")
numero2 = Int(readLine()!) ?? 0 

if numero1 == 0 || numero2 == 0 {
    print("Te va a dar cero\n")
}
else {
    print("La multiplicación de \(numero2) y \(numero1) es \(numero1*numero2)\n")
}

Pedirle al usuario un número e indicar si es múltiplo de 2 o de 3 (de cualquiera de ellos, sin importar cuál)

print("Dime un numero: ", terminator:"")
var numero = Int(readLine()!) ?? 0 

if numero % 2 == 0 || numero % 3 == 0 {
    print("El \(numero) es multiplo de 2 o de 3")
}
else {
    print("El \(numero) no es multiplo ni de 2 ni de 3")    
}

Pedirle al usuario un número e indicarle si es múltiplo de 2 y de 3 (a la vez)

print("Dime un numero: ", terminator:"")
var numero = Int(readLine()!) ?? 0 

if numero % 2 == 0 && numero % 3 == 0 {
    print("El \(numero) es multiplo de 2 y de 3")
}
else {
    print("El \(numero) no es múltiplo de 2 y 3 al mismo tiempo")    
}

Pedirle al usuario un número e indicarle si es múltiplo de 2 pero no de 3


Pedirle al usuario un número e indicarle si no es múltiplo de 2 ni de 3


Pedirle al usuario dos números e indicarle si ambos son pares


Pedirle al usuario dos números e indicarle si al menos uno es par


Pedirle al usuario dos números e indicarle si uno y solo uno es par


Pedirle al usuario que escriba dos números e indicarle uno de estos casos: Uno de ellos (pero no los dos) es positivo, los dos son positivos, o ninguno es positivo


Pedirle al usuario que escriba tres números e indicarle cuál es el mayor


Pedirle al usuario que escriba dos números e indicarle si son iguales o cuál es el mayor

var numero1 = 0
var numero2 = 0

print("Dime el primer numero: ", terminator:"")
numero1 = Int(readLine()!) ?? 0 

print("Dime el segundo numero: ", terminator:"")
numero2 = Int(readLine()!) ?? 0 

if numero1 == numero2 {
    print("Los dos números son iguales\n")
}
else if numero1 > numero2 {
    print("El numero \(numero1) es mayor que \(numero2)\n")
}
else {
    print("El numero \(numero2) es mayor que \(numero1)\n")
}

Usar el operador condicional ?: para mostrar el valor absoluto de un número


Usar el operador condicional ?: para detectar el menor de dos números


Utilizar un switch para pedirle al usuario un número del 1 al 10 y escribirlo en letra («uno», «dos», etc.)


Utilizar switch para pedirle al usuario un carácter e indicar el tipo: cifra numérica, signo de puntuación y otro carácter


Utilizar un switch para pedirle al usuario una letra y ver si ha escrito una vocal, un dígito o una consonante


Pedirle al usuario su contraseña numérica mientras no escriba 1111


Pedir un número por teclado e imprimir el cuadrado de dicho número


Pedirle al usuario dos números continuamente y ver cuál es múltiplo de cuál, o si no son múltiplos


Contador descendente de números pares del 26 al 10, usando while


Calcular cuántas cifras tiene un número entero positivo


Utilizando un bucle de tipo repeat while, sumar números positivos hasta que introduzcamos un 0 o un número negativo

var numero:Int, total = 0

repeat {
    print("Dime un numero: ", terminator:"")
    numero = Int(readLine()!) ?? 0 
    total += numero
} while numero > 0

print("Total sumado: \(total)")

Utilizando un bucle de tipo repeat while, pedirle al usuario su identificador y contraseña hasta que no escriba 1234 y 1111 respectivamente


Utilizando un bucle repeat while, pedirle al usuario su identificador y contraseña hasta que no escriba Pedro y Peter respectivamente


Mostrar los números del 10 al 20 ambos incluidos


Contar del 1 al 50 usando for y decir cuales son múltiplos de 3


Mostrar los números divisibles entre 7 y 3 que estén entre el 100 y el 200, usando un bucle for in


Imprimir la tabla de multiplicar del 9 usando for in


Mostrar los 8 primeros números pares


Mostrar del 15 al 5 descendiendo


Bucle infinito para mostrar «Hola» sin parar en pantalla


Bucle infinito para mostrar los enteros positivos a partir del 1


Utilizando bucles anidados, mostrar si los números estre el 10 y el 20 son divisibles entre 5, 6 o 7


Utilizando bucles anidados, mostrar 10 veces los números del 1 al 5, en 10 líneas separadas


Dibujar un cuadrado de asteriscos del ancho y alto que diga el usuario


Imprimir un rectángulo de asteriscos del ancho y alto especificado por el usuario


Dibujar un triángulo de asteriscos creciente, de tantas alturas como diga el usuario


Dibujar un triángulo de asteriscos decreciente, de tantas alturas como diga el usuario


Imprimir los divisores comunes de dos números


Imprimir el máximo común divisor de dos números


Imprimir el máximo común divisor de dos números utilizando un break para terminar cuando se haya encontrado


Imprimir el mínimo común múltiplo de dos números


Imprimir el mínimo común múltiplo de dos números utilizando un break para terminar cuando se haya encontrado


Imprimir una cuenta atrás desde el número introducido por teclado (entre el 1 y el 10)


Adivinar un número prefijado en el programa


Adivinar un número del 1 al 100 (prefijado) en 6 intentos como máximo


Averiguar si un número dado por el usuario es primo


Imprimir los factores primos de un número dado por el usuario


Calcular un número elevado a otro usando multiplicaciones sucesivas


Dibujar un rectángulo hueco, utilizando la altura y la anchura proporcionadas por el usuario


Devolver el cambio de una compra con los billetes y monedas más grandes posibles


Pedirle al usuario edad y año de nacimiento y dar aviso de error en el momento en que introduzca un dato incorrecto (por ejemplo, que ponga «Hola» al pedirle la edad)


Hallar el volumen de una esfera dado su radio. Volumen = pi * radio al cubo * 4/3


Mostrar el valor de y para los primeros 20 valores de x: y = x2 – 1


Calcula el perímetro, área y diagonal de un rectángulo dados su ancho y alto


Pedir al usuario su nombre y su contraseña dos veces, hasta que las dos contraseñas introducidas coincidan


Utilizando funciones

Crear una función que reciba como parámetro la longitud del lado de un cuadrado y lo dibuje utilizando asteriscos


Crear una función que reciba como parámetros la base y la altura de un rectángulo y lo dibuje utilizando asteriscos


Crear una función que reciba como parámetros un carácter y un número y repita ese carácter tantas veces como indique el número


Crear un función que devuelva el cubo de un número real pasado por parámetro


Crear una función para pedirle un número al usuario. Dicha función debe recibir como parámetro el texto a mostrar al usuario y el límite inferior y superior del entero que éste debe introducir, y repetirle que lo introduzca hasta que ponga un número correcto


Crear una función que reciba un entero y devuelva un booleano indicando si el número es primo o no


Swift – Opcionales

Introducción

Como hemos comentado previamente, una de las ventajas que nos da Swift es la posibilidad de escribir código seguro. Esto implica que swift está preparado para que las aplicaciones no se encuentren con situaciones indeseadas al momento de ejecutar el código. Los opcionales son un mecanismo muy importante para conseguir este objetivo.

Los opcionales permiten indicar que un valor puede estar ausente. Esto significa que una variable opcional puede tener un valor y por lo tanto se puede acceder al mismo o que esa variable esta “vacía”.

La potencia de este enfoque es que si se sabe que una variable puede llegar a no tener valor en algún momento de su ciclo de vida, podemos escribir código adicional preguntando si ese valor existe o no, estando seguros en todo momento sobre qué hacer en cada caso.

Si no existieran los opcionales, puede darse el caso de que no contemplemos un escenario en donde el valor no exista y por lo tanto se generen comportamientos extraños en momentos de ejecución. Veamos un ejemplo:

let colores = ["azul", "negro", "verde", "rojo", "amarillo"]

let primerColor = colores.first

//primerColor es es String? en lugar de String

Primero definimos un array de String donde guardamos un listado de colores y luego, creamos una constante para obtener el primero color del arreglo usando la propiedad first.

Como puede darse el caso de que el array esté vacío, first no siempre va a devolver un valor; en algunos casos tomará el primer elemento pero en otros indicará que no se encontró un valor. Esto hace que el resultado sea un opcional.

nil

Volviendo al ejemplo anterior, para indicar la ausencia de un valor se usa un valor especial llamado nil.

Por lo tanto, la constante primerColor no se trata de un tipo String sino de un opcional de String, indicado con un signo de pregunta (?) String?.

Si quisiéramos definir una variable como opcional usamos la siguiente forma:

var segundoColor: String? = "negro"
print("\(segundoColor)")

//Devuelve:
//Optional(“negro”)

segundoColor = nil
print("\(segundoColor)")

//Devuelve:
//nil

Como vemos, definimos la variable indicando el tipo de dato y agregando un signo de pregunta para indicar que se trata de un opcional.

La asignación de un valor en ese momento es optativo, ya que de no hacerlo Swift infiere que su valor inicial es nil.

Si imprimimos esa variable usando print vemos que su valor es Optional(“negro”) en lugar de “negro” a secas.

Una variable opcional puede volver a valer nil si es necesario.

Recordemos que las variables no opcionales deben tener un valor al momento de su definición obligatoriamente.

Forced unwrapping

Si sabemos y estamos seguros que en un determinado momento una variable opcional tiene un valor en su interior, podemos usar la técnica de forced unwrapping (desenvoltura forzada) mediante el uso de un signo de exclamación al final del nombre de la variable (!):

var otroColor:String? = "celeste"
print(“\(otroColor!)”)
//Devuelve
//celeste

Como sabemos que la variable otroColor contiene un valor, usamos la desenvoltura forzada y accedemos al dato que el mismo encierra. Ya no estamos obteniendo un Optional(“celeste”) sino que directamente obtenemos “celeste”.

Si quisiéramos preguntar primero si esa variable vale nil o no, una posibilidad es usar una sentencia if:

if otroColor != nil {
    print("\(otroColor!)")
}

//Devuelve
//celeste

En este caso, primero nos aseguramos si la variable no vale nil (o sea, si tiene un valor) y luego hacemos el forced unwrapping, debido a que dentro del ifestamos seguros que esa variable tiene un valor.

Optional Binding

Si bien el enfoque anterior es perfectamente válido, no termina siendo muy cómodo si dentro del if se necesita usar muchas veces el valor dentro del opcional, ya que tendríamos que recordar de usar el ! en todos los casos.

Otra manera de hacer lo mismo pero un poco más eficiente es usando el optional binding (enlace opcional), en el cual se pregunta si un opcional contiene un valor y en caso afirmativo, se crea una constante o variable temporal para ser usada dentro del if o while como si se tratara de una versión no opcional. Una vez fuera de ese if o while, la misma deja de existir.

if let colorElegido = otroColor {
    print("El color elegido es \(colorElegido)")
}

//Devuelve:
//El color elegido es celeste

Como vemos, creamos una constante temporal llamada colorElegido que contiene el valor del opcional otroColor. Dentro del if usamos ese valor y ya fuera de él la constante ya no existe más.

Asimismo, podemos anidar varios enlaces opcionales e incluso usar otras comparaciones convencionales que devuelven true o false. Como resultado, sólo se ejecutarán las instrucciones dentro del if cuando todos los opcionales tengan un valor y todas las condiciones devuelvan true. Si alguno de estos casos no se da, el resultado final será false y no se ingresará al if.

let numeroPar = 30

if let colorElegido = otroColor, let numero = Int("45"), numeroPar < 50 {
    print("El color elegido es \(colorElegido) y el \(numeroPar) es menor a 50")
    print("\(numero)")
}

//Devuelve:
//El color elegido es celeste y el 30 es menor a 50
//45

Implicit unwrapped optionals

En muchas situaciones contamos con variables opcionales que en un momento determinado del programa son inicializados con un valor y que luego no van a volver a valer nil nunca más. Por tal motivo, es conveniente no tener que preguntar a cada instante si esa variable contiene un valor y hacer la desenvoltura correspondiente, ya que se asume que siempre se va a contener un valor.

Un ejemplo muy claro de este escenario se da cuando se programa para un sistema operativo como iOS, en donde los botones que vemos en pantalla al inicio de su ciclo de vida se crean en nil pero luego mientras se inicializan determinados componentes, las variables que hacen referencia a esos botones contienen valor, hasta tanto el usuario cambia de pantalla y se pierden esas referencias. Como en un momento de su ciclo de vida necesitó valer nil, es obligatorio usar un opcional, pero como luego siempre va a tener un valor, no es cómodo tener que usar un if let en cada uso. Para este tipo de casos, los implicity unwrapped optionals (opcionales desenvueltos en forma implícita) son ideales.

Para hacer uso de ellos, en lugar de declarar al opcional usando un signo de pregunta (?) se utiliza un signo de exclamación (!):

let posibleValor:String? = "Estamos aprendiendo Swift! Yeahh!"
let valorSeguro:String! = posibleValor!

print(posibleValor)
print(valorSeguro)

//Devuelve:
//Optional("Estamos aprendiendo Swift! Yeahh!")
//Estamos aprendiendo Swift! Yeahh!

Podríamos decir que este tipo de opcionales tienen realizan la desenvoltura de su valor en forma automática, sin necesidad de hacerlo explícito. Tengamos en cuenta que si una variable o constante vale nil e intentamos usar su valor, vamos a tener un error en tiempos de ejecución. Por lo tanto, hay que ser muy cuidadoso y pensar bien antes de usar este tipo de opcionales.

En el caso de que exista alguna posibilidad de que una variable se vuelva nil en algún momento del código, entonces no debemos usar este opcional implícito.

Por otro lado, si necesitamos preguntar si ese opcional implícito tiene un valor en su interior o si quisiéramos usar el optional binding, podemos hacerlo:

if valorSeguro != nil {
    print(valorSeguro)
}

if let dato = valorSeguro {
    print(dato)
}

nil-coalescing operator

Este operador, que podríamos traducirlo como operador que viene junto a nil, devuelve el valor que contiene un opcional haciendo una desenvoltura (a) y en caso de encontrar nil, devuelve otro valor (b). Su sintaxis es a ?? b.

Un punto a tener en cuenta es que tanto a como b deben ser del mismo tipo.

var animal:String? = "Perro"

let mascota = animal ?? "Gato"

print("\(mascota)")

//Devuelve Perro

En el ejemplo, definimos una variable opcional animal con el valor Perro. Al ser opcional, podría no contener un valor (ser nil). Luego se declara una constante mascota que va a valer Perro siempre que la variable animal tenga ese valor. En caso de ser nil, se incluye un valor adicional Gato.

Este operador es muy útil cuando queremos dar un valor por defecto en caso de que una variable o contante no tengan valor.