Advertisement
  1. Code
  2. CloudKit

Construcción de  una Aplicaciones de Lista de Compra Con CloudKit: Añadir Registros

Scroll to top
Read Time: 27 min
This post is part of a series called Building a Shopping List Application With CloudKit.
Building a Shopping List Application With CloudKit: Introduction
Building a Shopping List Application With CloudKit: Adding Relationships

() translation by (you can also view the original English article)

En el primer tutorial de esta serie, se analizó la infraestructura y marco CloudKit. También sentaron las bases para la aplicación de ejemplo que vamos a construir, una aplicación de lista de compras. En este tutorial, nos estamos centrando en añadir, editar, y eliminar compras listas.

Requisitos Previos

Como mencioné en el anterior tutorial, utilizaré Xcode 7 y Swift 2. Si está utilizando una versión anterior de Xcode, entonces tenga en cuenta que usted está usando una versión diferente del lenguaje de programación rápida.

En este tutorial, seguiremos trabajando con el proyecto que creamos en el primer tutorial. Se puede descargar desde GitHub.

1. Configuración del CocoaPods

La aplicación comercial de la lista hará uso de la SVProgressHUD library, una biblioteca popular creado por Sam Vermette que hace más fácil mostrar un indicador de progreso. Puede Agregar manualmente la biblioteca para su proyecto, pero recomiendo usar CocoaPods para la gestión de las dependencias. ¿Eres Nuevo en CocoaPods? He escrito un tutorial de introducción a la CocoaPods que le ayudarán a ponerse al día.

Paso 1: Crear un Podfile

Abrir Finder y navegar a la raíz de su proyecto de Xcode. Cree un nuevo archivo el nombre Podfile y añadir las siguientes líneas de Ruby a él.

1
platform :ios, '8.0'
2
use_frameworks!
3
4
target 'Lists' do
5
  pod 'SVProgressHUD', '~> 1.1'
6
end

La primera línea especifica la plataforma iOS y blanco de implementación del proyecto, iOS 8.0. La segunda línea es importante si estás usando Swift. SWIFT no es compatible con las bibliotecas estáticas y CocoaPods proporcionan la opción desde la versión 0.36 de usar marcos. A continuación especificamos las dependencias para el destino de la Lista del proyecto. Reemplazar las Listas con el nombre de su destino si tu objetivo es nombrado diferentemente.

Paso 2: Instalar las Dependencias

Abrimos el Terminal, la raíz de su proyecto de Xcode y ejecute pod install. Esto hará una serie de cosas para usted, como la instalación de las dependencias especificadas en Podfile y crear un espacio de trabajo de Xcode.

Después de completar la configuración de CocoaPods, cierre el proyecto y abrir el espacio de trabajo CocoaPods creado para usted. Este último es muy importante. Abrir el espacio de trabajo, no el proyecto. El espacio de trabajo incluye dos proyectos, el proyecto de Listas y un proyecto denominado Pods.

2. Listado de Listas de Compras

Paso 1: Limpieza

Estamos listos para enfocar en el marco de CloudKit. En primer lugar, sin embargo, tenemos que hacer alguna limpieza renombrando la clase ViewController a la clase ListsViewController.

Empezar por cambiar el nombre ViewController.swift a ListsViewController.swift. Abra ListsViewController.swift y cambie el nombre de la clase ViewController a la clase ListsViewController.

A continuación, abra Main.storyboard, ampliar Vista Controlador de Escena en el Esquema del Documento a la izquierda y seleccione View Controller. Abra el Inspector de Identidad a la derecha y cambiar la Clase a ListsViewController.

Paso 2: Agregar una Vista de Tabla

Cuando el usuario abre la aplicación, se presenta con sus listas de compras. Te muestra las listas de la compra en una vista de tabla. Vamos a empezar por configurar la interfaz de usuario. Seleccionar el Controlador de Vista de Listas en la Controlador de Vista de Listas de Escena y elija Incrustar en > Controlador de Navegación de menú del Editor de Xcode.

Agregar una vista de tabla a la vista del controlador de la vista y crear las restricciones de diseño necesarias para ello. Con la vista de la tabla seleccionada, abra el Inspector de Atributos y las Células Prototipo a 1. Seleccione la celda prototipo y establezca IdentificadorListCell, y Estilo a Básico.

Con la vista de la tabla seleccionada, abra el Inspector de Conexiones. Conectar dataSource de la tabla vista y delegate de comunicación con el Controlador de Vista Lista.

Paso 3: Estado de Vacío

A pesar de que sólo estamos creando una aplicación de ejemplo para ilustrar cómo funciona CloudKit, me gustaría mostrar un mensaje si algo sale mal o no listas de la compra se encuentra en iCloud. Añadir una etiqueta al vista controlador, hacerla tan grande como del controlador vista, crear las restricciones de diseño necesarias para él y centrar texto de la etiqueta.

Puesto que tratamos con peticiones de red, también quiero mostrar una vista de indicador de actividad siempre y cuando la aplicación está esperando una respuesta de iCloud. Agregar una vista de indicador de la actividad a la vista del controlador de la vista y centro en la vista de su padre. En el Inspector de Atributos, marque la casilla de verificación Ocultar Cuando Pare.

Lists View ControllerLists View ControllerLists View Controller

Paso 4: Conexión de Salidas

Abra ListsViewController.swift y declarar una salida para la etiqueta, la vista de tabla y la vista de indicador de actividad. Esto también es un buen momento para hacer la clase de ListsViewController se ajustan a los protocolos de UITableViewDataSource y UITableViewDelegate.

Tenga en cuenta que también he añadido una declaración de importación para el marco SVProgressHUD y que he declarado una constante estática para el identificador de la reutilización de la célula de prototipo que se creó en el guión gráfico.

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
6
    static let ListCell = "ListCell"
7
    
8
    @IBOutlet weak var messageLabel: UILabel!
9
    @IBOutlet weak var tableView: UITableView!
10
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
11
    
12
    ...
13
    
14
}

Regresar al guión gráfico y conectar las salidas con las correspondientes vistas en la Escena de Controlador de Vista Lista.

Paso 5: Preparación de la Vista de Tabla

Antes que buscar datos de iCloud, necesitamos para asegurarse de que la vista de tabla está lista para mostrar los datos. Primero necesitamos crear una propiedad, listas, para mantener los registros que vamos a buscar. Recuerde que los registros son instancias de la clase CKRecord. Esto significa que la propiedad que contendrá los datos de iCloud es del tipo [CKRecord], una matriz de instancias de CKRecord.

1
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
2
    static let ListCell = "ListCell"
3
    
4
    @IBOutlet weak var messageLabel: UILabel!
5
    @IBOutlet weak var tableView: UITableView!
6
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
7
    
8
    var lists = [CKRecord]()
9
    
10
    ...
11
    
12
}

Para empezar, tenemos que implementar los tres métodos del Protocolo de UITableViewDataSource:

  • numberOfSectionsInTableView(_:)
  • numberOfRowsInSection(_:)
  • cellForRowAtIndexPath(_:)

Si usted tiene alguna experiencia trabajando con vistas de tabla, la aplicación de cada uno de estos métodos es sencilla. Sin embargo, cellForRowAtIndexPath(_:) puede requerir alguna explicación. Recuerde que una instancia de CKRecord es un sobrealimentado Diccionario de pares clave-valor. Para tener acceso al valor de una clave particular, se invoca objectForKey(_:) en el objeto CKRecord. Es lo que hacemos en cellForRowAtIndexPath(_:). Buscar el registro que corresponde a la fila de la vista de tabla y pregunte por el valor de clave "name". Si no existe el par de clave y valor, disponemos de un guión para indicar que la lista aún no tiene un nombre.

1
// MARK: -

2
// MARK: Table View Data Source Methods

3
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
4
    return 1;
5
}
6
7
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
8
    return lists.count
9
}
10
11
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
12
    // Dequeue Reusable Cell

13
    let cell = tableView.dequeueReusableCellWithIdentifier(ListsViewController.ListCell, forIndexPath: indexPath)
14
    
15
    // Configure Cell

16
    cell.accessoryType = .DetailDisclosureButton
17
    
18
    // Fetch Record

19
    let list = lists[indexPath.row]
20
    
21
    if let listName = list.objectForKey("name") as? String {
22
        // Configure Cell

23
        cell.textLabel?.text = listName
24
        
25
    } else {
26
        cell.textLabel?.text = "-"
27
    }
28
    
29
    return cell
30
}

Paso 6: Preparación de la Interfaz de Usuario

Hay un paso más para que podamos tomar, preparar la interfaz de usuario. En viewDidLoad método del controlador vista, quitar la llamada al método de fetchUserRecordID e invocar a setupView, un método auxiliar.

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    setupView()
5
}

El método de setupView prepara la interfaz de usuario para obtener la lista de registros. Ocultar la etiqueta y la vista de tabla y Dile a la vista de indicador de actividad para empezar a animar.

1
// MARK: -

2
// MARK: View Methods

3
private func setupView() {
4
    tableView.hidden = true
5
    messageLabel.hidden = true
6
    activityIndicatorView.startAnimating()
7
}

Generar y ejecutar la aplicación en un dispositivo o en el simulador de iOS. Si usted ha seguido los pasos anteriores, debería ver una vista vacía con una vista de indicador de actividad de spinning en el centro.

Busy Pretending to Be Fetching DataBusy Pretending to Be Fetching DataBusy Pretending to Be Fetching Data

Paso 7: Crear un Tipo de Registro

Antes que buscar los registros, tenemos que crear un tipo de registro para una lista de compras en el tablero de CloudKit. El tablero de mandos de CloudKit es una aplicación web que permite a los desarrolladores manejar los datos almacenados en los servidores de iCloud de Apple.

Seleccione el Proyecto en el Navegador de proyecto y elegir el destino de las Listas de la lista de objetivos. Abra la ficha de Capacidades en la parte superior y ampliar la sección de iCloud. A continuación la lista de contenedores de iCloud, haga clic en el botón Panel de CloudKit.

Open CloudKit DashboardOpen CloudKit DashboardOpen CloudKit Dashboard

Inicia sesión con tu cuenta de desarrollador y asegúrese de que la aplicación de las Listas es seleccionada en la parte superior izquierda. A la izquierda, seleccione Tipos de Registro de la sección de Esquema. Cada aplicación tiene por defecto un tipo de registro Usuarios. Para crear un nuevo tipo de registro, haga clic en el botón más en la parte superior de la tercera columna. Seguiremos la Convención de nomenclatura de Apple y nombre de la Listas tipo de registro, no de Lista.

Adding a New Record TypeAdding a New Record TypeAdding a New Record Type

Tenga en cuenta que el primer campo se crea automáticamente para usted. Establezca el nombre del campo en nombre y verificar que Tipo de Campo se establece en Cadena. No olvide hacer clic en el botón Save en la parte inferior para crear el tipo de registro de las Listas. A revisar el tablero de CloudKit más adelante en esta serie.

Paso 8: Realizar una Consulta

Con el tipo de registro Listas creado, finalmente es hora de recuperar algunos registros desde iCloud. El marco de la CloudKit proporciona dos APIs para interactuar con iCloud, una API de conveniencia y un API basado en la clase NSOperation. Usaremos ambas APIs en esta serie, pero vamos a mantenerlo simple por ahora y utilizar la API de conveniencia.

En Xcode, abra ListsViewController.swift e invocar el método fetchLists en viewDidLoad. El método de fetchLists es otro método auxiliar. Echemos un vistazo a la implementación del método.

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    setupView()
5
    fetchLists()
6
}

Porque un registro comercial de la lista se almacena en bases de datos del usuario, en primer lugar obtenemos una referencia a bases de datos de contenedor predeterminado. Para obtener el usuario de compras listas, tenemos que realizar una consulta en la base de datos privada, utilizando la clase CKQuery.

1
// MARK: -

2
// MARK: Helper Methods

3
private func fetchLists() {
4
    // Fetch Private Database

5
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
6
    
7
    // Initialize Query

8
    let query = CKQuery(recordType: RecordTypeLists, predicate: NSPredicate(format: "TRUEPREDICATE"))
9
    
10
    // Configure Query

11
    query.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
12
    
13
    // Perform Query

14
    privateDatabase.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
15
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
16
            // Process Response on Main Thread

17
            self.processResponseForQuery(records, error: error)
18
        })
19
    }
20
}

Inicializamos una instancia de CKQuery invocando la init(recordType:predicate:) señalado a inicializador, pasando el tipo de registro y un objeto NSPredicate. Para evitar faltas de ortografía, he creado una constante para el tipo de registro. Se declara la constante, RecordTypeLists, en la parte superior de ListsViewController.swift.

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
let RecordTypeLists = "Lists"
6
7
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
8
...

Antes que ejecutar la consulta, hemos creado el sortDescriptors propiedad de la consulta. Creamos una matriz que contiene un objeto NSSortDescriptor con la clave "name" y ascendente conjunto en true.

Ejecución de la consulta es tan sencillo como llamar a performQuery(_:inZoneWithID:completionHandler:) en privateDatabase, pasando en la consulta como primer argumento. El segundo parámetro especifica el identificador de la zona de registro en el cual se realizará la consulta. Pasando a cero nil, la consulta se realiza en la zona predeterminada de la base de datos.

El tercer argumento es un controlador de terminación. El cierre acepta una matriz opcional de objetos de CKRecord y una instancia de NSError opcional. La documentación de CloudKit menciona explícitamente que el controlador de terminación puede invocarse desde cualquier subproceso. Por lo tanto que enviemos el procesamiento de la respuesta para el subproceso principal envolviendo la llamada al método processResponseForQuery(_:error:) en un encierro de dispatch_async. Eso es lo fácil que es realizar una consulta. Vamos a ver cómo manejamos la respuesta de la consulta.

Paso 9: Procesamiento de la Respuesta

La aplicación de processResponseForQuery(_:error:) no es difícil si usted está familiarizado con la lengua rápida. Inspeccione el contenido de los registros y parámetros de error y actualizar en consecuencia la variable message.

1
private func processResponseForQuery(records: [CKRecord]?, error: NSError?) {
2
    var message = ""
3
    
4
    if let error = error {
5
        print(error)
6
        message = "Error Fetching Records"
7
        
8
    } else if let records = records {
9
        lists = records
10
        
11
        if lists.count == 0 {
12
            message = "No Records Found"
13
        }
14
        
15
    } else {
16
        message = "No Records Found"
17
    }
18
    
19
    if message.isEmpty {
20
        tableView.reloadData()
21
    } else {
22
        messageLabel.text = message
23
    }
24
    
25
    updateView()
26
}

Al final del método, se invoca a updateView. En este método, la actualización de la interfaz de usuario basándose en el contenido de la propiedad de las listas.

1
private func updateView() {
2
    let hasRecords = lists.count > 0
3
    
4
    tableView.hidden = !hasRecords
5
    messageLabel.hidden = hasRecords
6
    activityIndicatorView.stopAnimating()
7
}

Generar y ejecutar la aplicación para probar lo que tenemos hasta ahora. Actualmente no disponemos de ningún registro, pero lo fijamos en la siguiente sección de este tutorial.

No Records FoundNo Records FoundNo Records Found

3. Añadir una Lista de Compras

Paso 1: Crear la clase AddListViewController

Porque agregar y editar una lista de compras son muy similares, vamos a aplicar a ambos al mismo tiempo. Crear un nuevo archivo y asígnele el nombre AddListViewController.swift. Abra el archivo recién creado y crear una subclase de UIViewController llamada AddListViewController. En la parte superior, agrega declaraciones de importación para el framework UIKit, CloudKit y SVProgressHUD. ¡Declarar dos salidas, una de tipo UITextField! y uno de tipo UIBarButtonItem!. Por último pero no menos importante, crear dos acciones, cancel(_:) y save(_:).

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
class AddListViewController: UIViewController {
6
    
7
    @IBOutlet weak var nameTextField: UITextField!
8
    @IBOutlet weak var saveButton: UIBarButtonItem!
9
    
10
    @IBAction func cancel(sender: AnyObject) {
11
        
12
    }
13
    
14
    @IBAction func save(sender: AnyObject) {
15
        
16
    }
17
    
18
}

Paso 2: Crear la Interfaz de Usuario

Abra Main.storyboard y agregue un controlador de vista en el storyboard. Con el controlador de la vista seleccionado, abra el Inspector de la Identidad a la derecha y defina Clase como AddListViewController.

El usuario podrá navegar al controlador de vista de lista Agregar pulsando un botón en el controlador de vista de las listas. Arrastre una barra de botón artículo de la Biblioteca de Objeto a la barra de navegación del controlador de vista de listas. Con la barra el botón item seleccionado, abra el Inspector de Atributos y establecer Sistema de Elemento a Añadir. Pulse Control y arrastre desde la barra de botón de elemento para el controlador de vista de lista de agregar y seleccione push el menú que aparece.

Es importante que usted elija el push de la sección de Acción Segue. Debe ser el primer elemento en el menú. Seleccione la transición que jus creado e Identificador de conjunto de a ListDetail en el Inspector de Atributos de la derecha.

Agregue dos objetos de botón de la barra a la barra de navegación del controlador de vista de lista de agregar, uno a la izquierda y otra a la derecha. Establecer Sistema Elemento de la izquierda la barra elemento botón Cancel y la de la derecha barra elemento de botón para Guardar. Por último, agregar un campo de texto para el controlador de vista de lista de agregar. Centro del campo de texto y establecer su Alineación al centro del Inspector de Atributos.

Add List View ControllerAdd List View ControllerAdd List View Controller

Es posible que, después de crear la transición desde el controlador de vista de listas para el controlador de vista de lista de add, ningún elemento de navegación se creó para Añadir Lista Vista Controlador de Escena. Creo que esto es un error en Xcode 7.

Para resolver este problema, seleccione la transición y, en el Inspector de Atributos,Configure Segues a Desaprobado Segues > Push. Esto agregará un elemento de navegación a la escena. A continuación, establece la Transición a Adaptación de Transición > Mostrar.

Por último, conecte las salidas y acciones que creó en AddListViewController.swift en los elementos de interfaz de usuario correspondiente en la escena.

Paso 3: Protocolo de AddListViewControllerDelegate

Antes que implementar la clase de AddListViewController, necesitamos declarar un protocolo que usaremos para comunicar desde el controlador de vista de lista de agregar el controlador de vista de listas. El protocolo define dos métodos, uno para agregar y otro para la actualización de una lista de compras. Esto es lo que parece el protocolo.

1
protocol AddListViewControllerDelegate {
2
    func controller(controller: AddListViewController, didAddList list: CKRecord)
3
    func controller(controller: AddListViewController, didUpdateList list: CKRecord)
4
}

También tenemos que declarar tres propiedades, una para el delegado, otro para la lista de compras que es creada o actualizada y una variable auxiliar que indica si estamos creando una nueva lista de la compra o editar un registro existente.

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
protocol AddListViewControllerDelegate {
6
    func controller(controller: AddListViewController, didAddList list: CKRecord)
7
    func controller(controller: AddListViewController, didUpdateList list: CKRecord)
8
}
9
10
class AddListViewController: UIViewController {
11
    
12
    @IBOutlet weak var nameTextField: UITextField!
13
    @IBOutlet weak var saveButton: UIBarButtonItem!
14
    
15
    var delegate: AddListViewControllerDelegate?
16
    var newList: Bool = true
17
    
18
    var list: CKRecord?
19
    
20
    @IBAction func cancel(sender: AnyObject) {
21
        
22
    }
23
    
24
    @IBAction func save(sender: AnyObject) {
25
        
26
    }
27
    
28
}

La implementación de la clase AddListViewController no es ninguna ciencia de cohetes. Los métodos relacionados con el ciclo de vida de la vista son cortos y fáciles de entender. En viewDidLoad, primero se invoca el método setupView. A implementar este método en un momento. Luego actualizamos el valor de la variable de ayudante newList basándose en el valor de la propiedad de list. Si la lista es igual a nil, entonces sabemos que estamos creando un nuevo registro. En viewDidLoad, también agregamos el controlador de vista como observador para las notificaciones UITextFieldTextDidChangeNotification.

1
// MARK: -

2
// MARK: View Life Cycle

3
override func viewDidLoad() {
4
    super.viewDidLoad()
5
    
6
    setupView()
7
    
8
    // Update Helper

9
    newList = list == nil
10
    
11
    // Add Observer

12
    let notificationCenter = NSNotificationCenter.defaultCenter()
13
    notificationCenter.addObserver(self, selector: "textFieldTextDidChange:", name: UITextFieldTextDidChangeNotification, object: nameTextField)
14
}
15
16
override func viewDidAppear(animated: Bool) {
17
    nameTextField.becomeFirstResponder()
18
}

En viewDidAppear(_:), que llamamos becomeFirstResponder en el campo de texto para presentar el teclado al usuario.

En setupView, invocamos dos métodos, updateNameTextField y updateSaveButton. En updateNameTextField, nos pueblan el campo de texto si la lista no es nil. En otras palabras, si estamos editando un registro existente, entonces nos pueblan el campo de texto con el nombre de ese disco.

El método updateSaveButton es la encargada de activar y desactivar la barra de artículo de botón en la parte superior derecha. Sólo permitimos el guardar botón si el nombre de la lista de compras no es una cadena vacía.

1
// MARK: -

2
// MARK: View Methods

3
private func setupView() {
4
    updateNameTextField()
5
    updateSaveButton()
6
}
7
8
// MARK: -

9
private func updateNameTextField() {
10
    if let name = list?.objectForKey("name") as? String {
11
        nameTextField.text = name
12
    }
13
}
14
15
// MARK: -

16
private func updateSaveButton() {
17
    let text = nameTextField.text
18
    
19
    if let name = text {
20
        saveButton.enabled = !name.isEmpty
21
    } else {
22
        saveButton.enabled = false
23
    }
24
}

Paso 4: Implementación de Acciones

La acción de cancel(_:) es tan simple como se pone. Nos aparecerá el controlador de la vista superior de la pila de navegación. La acción de save(_:) es más interesante. En este método, extracto de la entrada del usuario desde el campo de texto y obtener una referencia a bases de datos de contenedor predeterminado.

Si estamos agregando una nueva lista de compras, luego creamos una nueva iCKRecord nstancia de init(recordType:), en RecordTypeLists como el tipo de registro se invoca. Luego actualizamos el nombre de la lista de compras por el valor del registro para la clave "name".

Porque guardar un registro consiste en una solicitud de red y puede tomar una cantidad no trivial de tiempo, se muestra un indicador de progreso. Para guardar un nuevo registro o cambios en un registro existente, llamamos saveRecord(_:completionHandler:) a privateDatabase, pasando en el registro como primer argumento. El segundo argumento es otro controlador de terminación que se invoca al grabar un registro de termina, con éxito o sin éxito.

El controlador de terminación acepta dos argumentos, un CKRecord opcional y un NSError opcional. Como mencioné antes, el controlador de terminación puede ser invocado en cualquier subproceso, lo que significa que necesitamos código. Lo hacemos invocando explícitamente el método processResponse(_:error:) en el subproceso principal.

1
// MARK: -

2
// MARK: Actions

3
@IBAction func cancel(sender: AnyObject) {
4
    navigationController?.popViewControllerAnimated(true)
5
}
6
7
@IBAction func save(sender: AnyObject) {
8
    // Helpers

9
    let name = nameTextField.text
10
    
11
    // Fetch Private Database

12
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
13
    
14
    if list == nil {
15
        list = CKRecord(recordType: RecordTypeLists)
16
    }
17
    
18
    // Configure Record

19
    list?.setObject(name, forKey: "name")
20
    
21
    // Show Progress HUD

22
    SVProgressHUD.show()
23
    
24
    // Save Record

25
    privateDatabase.saveRecord(list!) { (record, error) -> Void in
26
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
27
            // Dismiss Progress HUD

28
            SVProgressHUD.dismiss()
29
            
30
            // Process Response

31
            self.processResponse(record, error: error)
32
        })
33
    }
34
}

En processResponse(_:error:), verificamos si se produce un error. Si nos problemas, disponemos de una alerta al usuario. Si todo ha ido sin problemas, notificar a lo delegado y pop el controlador de vista de la pila de navegación.

1
// MARK: -

2
// MARK: Helper Methods

3
private func processResponse(record: CKRecord?, error: NSError?) {
4
    var message = ""
5
    
6
    if let error = error {
7
        print(error)
8
        message = "We were not able to save your list."
9
        
10
    } else if record == nil {
11
        message = "We were not able to save your list."
12
    }
13
    
14
    if !message.isEmpty {
15
        // Initialize Alert Controller

16
        let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
17
        
18
        // Present Alert Controller

19
        presentViewController(alertController, animated: true, completion: nil)
20
        
21
    } else {
22
        // Notify Delegate

23
        if newList {
24
            delegate?.controller(self, didAddList: list!)
25
        } else {
26
            delegate?.controller(self, didUpdateList: list!)
27
        }
28
        
29
        // Pop View Controller

30
        navigationController?.popViewControllerAnimated(true)
31
    }
32
}

Por último pero no menos importante, cuando el controlador de vista recibe una notificación de UITextFieldTextDidChangeNotification, invoca a updateSaveButton para actualizar el guardar botón.

1
// MARK: -

2
// MARK: Notification Handling

3
func textFieldTextDidChange(notification: NSNotification) {
4
    updateSaveButton()
5
}

Paso 5: Atar Todo Junto

En la clase de ListsViewController, todavía tenemos que cuidar de algunas cosas. Vamos a comenzar por conforme la clase en el protocolo de AddListViewControllerDelegate.

1
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
2
...

Esto también significa que tenemos que implementar los métodos del protocolo AddListViewControllerDelegate. En el método controller(_:didAddList:), agregamos el nuevo registro en la matriz de objetos CKRecord. Luego ordenar el array de registros, la vista de tabla de recarga e invocar updateView en el controlador de vista.

1
// MARK: -

2
// MARK: Add List View Controller Delegate Methods

3
func controller(controller: AddListViewController, didAddList list: CKRecord) {
4
    // Add List to Lists

5
    lists.append(list)
6
    
7
    // Sort Lists

8
    sortLists()
9
    
10
    // Update Table View

11
    tableView.reloadData()
12
13
    // Update View

14
    updateView()
15
}

El método de sortLists es bastante básico. Llamamos sortInPlace en la matriz de registros, ordenar el arreglo de discos basado en nombre del registro.

1
private func sortLists() {
2
    lists.sortInPlace {
3
        var result = false
4
        let name0 = $0.objectForKey("name") as? String
5
        let name1 = $1.objectForKey("name") as? String
6
        
7
        if let listName0 = name0, listName1 = name1 {
8
            result = listName0.localizedCaseInsensitiveCompare(listName1) == .OrderedAscending
9
        }
10
        
11
        return result
12
    }
13
}

La aplicación del segundo método del Protocolo de AddListViewControllerDelegate, controller(_:didUpdateList:), aspecto casi idéntico. Porque no estamos agregando un registro, sólo necesitamos ordenar el array de registros y vuelva a cargar la vista de tabla. No hay necesidad para llamar a updateView en el controlador de vista ya que el conjunto de registros es, por definición, no vacío.

1
func controller(controller: AddListViewController, didUpdateList list: CKRecord) {
2
    // Sort Lists

3
    sortLists()
4
    
5
    // Update Table View

6
    tableView.reloadData()
7
}

Para editar un registro, el usuario necesita Pulse brevemente el botón accesorio de una fila de la vista de tabla. Esto significa que tenemos que implementar el método tableView(_:accessoryButtonTappedForRowWithIndexPath:) del protocolo UITableViewDelegate. Antes que implementar este método, declarar una propiedad helper, selección guardar selección del usuario.

1
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
2
    static let ListCell = "ListCell"
3
    
4
    @IBOutlet weak var messageLabel: UILabel!
5
    @IBOutlet weak var tableView: UITableView!
6
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
7
    
8
    var lists = [CKRecord]()
9
    
10
    var selection: Int?
11
    
12
    ...
13
    
14
}

En tableView(_:accessoryButtonTappedForRowWithIndexPath:), guardar selección del usuario en la selección y Dile el controlador de vista para llevar a cabo la transición que conduce al controlador de vista de lista de add. Si tienes curiosidad, he creado una constante en ListsViewController.swift para el identificador de la transición, SegueListDetail.

1
// MARK: -

2
// MARK: Table View Delegate Methods

3
func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
4
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
5
    
6
    // Save Selection

7
    selection = indexPath.row
8
    
9
    // Perform Segue

10
    performSegueWithIdentifier(SegueListDetail, sender: self)
11
}

Estamos casi allí. Cuando la transición con el identificador que listdetail se lleva a cabo, necesitamos configurar la instancia de AddListViewController que es empujada hacia la pila de navegación. Lo hacemos en prepareForSegue(_:sender:?).

La transición nos da una referencia para el controlador de vista de destino, la instancia de AddListViewController. Establece la propiedad de delegate, y si se actualiza una lista de compras, nos establezca propiedad de lista de vista controlador en el registro seleccionado.

1
// MARK: -

2
// MARK: Segue Life Cycle

3
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
4
    guard let identifier = segue.identifier else { return }
5
    
6
    switch identifier {
7
    case SegueListDetail:
8
        // Fetch Destination View Controller

9
        let addListViewController = segue.destinationViewController as! AddListViewController
10
        
11
        // Configure View Controller

12
        addListViewController.delegate = self
13
        
14
        if let selection = selection {
15
            // Fetch List

16
            let list = lists[selection]
17
            
18
            // Configure View Controller

19
            addListViewController.list = list
20
        }
21
    default:
22
        break
23
    }
24
}

Generar y ejecutar la aplicación para ver el resultado. Ahora podrá agregar una nueva lista de compras y editar el nombre de las listas de compras.

4. Eliminar las Listas de Compras

Añadir la capacidad de eliminar listas de la compra no es mucho trabajo extra. El usuario debe ser capaz de eliminar una lista de compras por pasar una fila de la vista de tabla de derecha a izquierda y presionar la tecla delete que se revela. Para hacer esto posible, tenemos que implementar dos métodos más para el protocolo de UITableViewDataSource:

  • tableView(_:canEditRowAtIndexPath:)
  • tableView(_:commitEditingStyle:forRowAtIndexPath:)

La aplicación de tableView(_:canEditRowAtIndexPath:) es trivial como se puede ver a continuación.

1
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
2
    return true
3
}

En tableView(_:commitEditingStyle:forRowAtIndexPath:), buscar el registro correcto de la matriz de registros e invocar deleteRecord(_:) en el controlador de la vista, pasando en el registro que debe ser eliminado.

1
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
2
    guard editingStyle == .Delete else { return }
3
    
4
    // Fetch Record

5
    let list = lists[indexPath.row]
6
    
7
    // Delete Record

8
    deleteRecord(list)
9
}

El método de deleteRecord(_:) debe buscar familiar por ahora. Mostrar un indicador de progreso y llamar a deleteRecordWithID(_:completionHandler:) en bases de datos de contenedor predeterminado. Tenga en cuenta que estamos pasando en el identificador del registro, no el propio registro. El controlador de terminación acepta dos argumentos, un CKRecordID opcional y un NSError opcional.

1
private func deleteRecord(list: CKRecord) {
2
    // Fetch Private Database

3
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
4
    
5
    // Show Progress HUD

6
    SVProgressHUD.show()
7
    
8
    // Delete List

9
    privateDatabase.deleteRecordWithID(list.recordID) { (recordID, error) -> Void in
10
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
11
            // Dismiss Progress HUD

12
            SVProgressHUD.dismiss()
13
            
14
            // Process Response

15
            self.processResponseForDeleteRequest(list, recordID: recordID, error: error)
16
        })
17
    }
18
}

En el controlador de terminación, despedir el indicador de progreso y processResponseForDeleteRequest(_:recordID:error:) en el subproceso principal de invocar. En este método, examinamos los valores de recordID y error que nos ha dado la API de CloudKit y actualizamos mensaje en consecuencia. Si la solicitud de cancelación fue exitosa, entonces actualizamos la interfaz de usuario y el conjunto de registros.

1
private func processResponseForDeleteRequest(record: CKRecord, recordID: CKRecordID?, error: NSError?) {
2
    var message = ""
3
    
4
    if let error = error {
5
        print(error)
6
        message = "We are unable to delete the list."
7
        
8
    } else if recordID == nil {
9
        message = "We are unable to delete the list."
10
    }
11
    
12
    if message.isEmpty {
13
        // Calculate Row Index

14
        let index = lists.indexOf(record)
15
        
16
        if let index = index {
17
            // Update Data Source

18
            lists.removeAtIndex(index)
19
            
20
            if lists.count > 0 {
21
                // Update Table View

22
                tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Right)
23
                
24
            } else {
25
                // Update Message Label

26
                messageLabel.text = "No Records Found"
27
                
28
                // Update View

29
                updateView()
30
            }
31
        }
32
        
33
    } else {
34
        // Initialize Alert Controller

35
        let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
36
        
37
        // Present Alert Controller

38
        presentViewController(alertController, animated: true, completion: nil)
39
    }
40
}

Eso es todo. Es hora de probar correctamente la aplicación con algunos datos. Ejecutar la aplicación en un dispositivo o en el iOS Simulator y añadir unas cuantas listas de compras. Podrá añadir, editar y Borrar listas de la compra.

Conclusión

Aunque este artículo es bastante largo, es bueno recordar que solamente brevemente interactuó con la API CloudKit. La conveniencia API del marco de CloudKit es ligero y fácil de usar.

Este tutorial, sin embargo, también ha ilustrado que su trabajo como desarrollador no se limita a interactuar con la API CloudKit. Es importante controlar los errores, mostrar al usuario cuando una solicitud está en curso, actualización de la interfaz de usuario y Dile al usuario lo que está sucediendo.

En el siguiente artículo de esta serie, tomamos un vistazo a las relaciones mediante la adición de la capacidad de llenar una lista de compras con artículos. Una lista de la compra vacía no es de mucho uso y sin duda no es divertido. Dejar cualquier pregunta que tiene en los comentarios abajo o acercarse a mí en Twitter.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.