() 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 Identificador a ListCell, 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.



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.



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.



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.



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.



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.



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.