Realm – Uma alternativa ao Core Data

Realm – Uma alternativa ao Core Data

Um dos tópicos mais comentados pelos desenvolvedores iOS é o Core Data. O Core Data possui muitos fãs por sua integração com o UITableView e pelo gerenciamento de memória. Também possui muitos “haters” por causa dos erros ligados a multithreading. A biblioteca Realm vem como uma alternativa a persistência de dados no iOS tendo versões em Swift e em Objective-C. Neste artigo vamos exemplificar a utilização do Realm com um aplicativo para mostrar os candidatos a prefeito de Belo Horizonte – MG. Os dados dos candidatos foram retirados do site do TRE no dia 25/08/2016.

A biblioteca

A biblioteca Realm é escrita em C++ (core) e possui versões Java, Objective-C, React Native, Swift e Xamarin. Ela possui todas as funcionalidades esperadas de uma biblioteca de persistência de dados tais como: encripitação, notificações, migração, suporte a threading, etc.

O banco de dados criado pelo Realm pode ser visualizado e ter os dados editados no Mac com o aplicativo Realm Browser.

Realm Browser

Para descobrir o caminho do banco no disco ao se utilizar o simulador digite o comando abaixo no console. Mais informações podem ser obtidas nesta resposta do Stack Overflow.

(lldb) po Realm.Configuration.defaultConfiguration.fileURL

Além da biblioteca para persistência de dados, o Realm possui uma lista de addons com componentes que auxiliam a sua utilização.

Neste artigo iremos abordar a versão RealmSwift implementada na linguagem Swift para utilização no desenvolvimento iOS, tvOS e MacOS.

O modelo

No Realm o modelo deve ser uma classe que herde de Object.

import RealmSwift

class Coalition: Object {
  dynamic var id: Int16 = 0
  dynamic var name: String = ""
}

Tipos de Dados

O Realm suporta os seguintes tipos de propriedades: Bool, Int8, Int16, Int32, Int64, Double, Float, String, NSDate, e NSData. Os tipos String, NSDate e NSData podem ser opcionais e os tipos Object devem ser opcionais. Os tipos numéricos podem ser opcionais utilizando-se do RealmOptional. RealmOptional suporta Int, Float, Double, Bool, e todos versões de tamanho do Int (Int8, Int16, Int32, Int64). As propriedades RealOptional devem ser declaradas com let e o seu valor é obtido com .value.

import RealmSwift

class Person: Object {
    dynamic var name: String? = nil
    let age = RealmOptional<Int>()
}

var person = realm.create(Person.self, value: ["Jane", 27])
person.age.value = 28

Todas as propriedades que não forem opcionais têm que ter um valor padrão no Realm. E todos os modelos devem ser válidos mesmo que não sejam utilizados porque o Realm carrega todos na inicialização.

As propriedades dos modelos Realm devem ter o atributo dynamic var exceto as Lists e os RealmOptionals que deve ser declarados com let. Segue abaixo uma tabela com as opções disponíveis.

Tipo Não opcional Opcional
Bool dynamic var value = false let value = RealmOptional<Bool>()
Int dynamic var value = 0 let value = RealmOptional<Int>()
Float dynamic var value: Float = 0.0 let value = RealmOptional<Float>()
Double dynamic var value: Double = 0.0 let value = RealmOptional<Double>()
String dynamic var value = "" dynamic var value: String? = nil
Data dynamic var value = NSData() dynamic var value: NSData? = nil
Date dynamic var value = NSDate() dynamic var value: NSDate? = nil
Object n/d: deve ser opcional dynamic var value: Class?
List let value = List<Class>() n/d: não pode ser opcional
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") n/d: não pode ser opcional

Relacionamentos

Os relacionamentos no Realm podem ser representados por uma propriedade do tipo Object ou uma lista do tipo Object.

import RealmSwift

class Good: Object {
  dynamic var id: Int16 = 0
  dynamic var goodDescription: String = ""
  dynamic var type: GoodType?
  dynamic var value: Double = 0
}

class Coalition: Object {
  dynamic var id: Int16 = 0
  dynamic var name: String = ""
  let parties = List<Party>()
}

O relacionamento inverso é representado no Realm através de LinkingObjects(fromType:property:). Para obtermos a lista de Objects que são relacionados a uma classe, passamos a classe que tem a referência e o campo referenciado ao método LinkingObjects(fromType:property:). Abaixo temos o exemplo da lista de bens (goods) que são referenciados pelo tipo de bem (GoodType).

import RealmSwift

class GoodType: Object {
  dynamic var id: Int16 = 0
  dynamic var typedescription: String = ""
  let goods = LinkingObjects(fromType: Good.self, property: "type")
}

Índices

Os índices são criados através do método indexedProperties() que deve retornar um array de Strings com os nomes das propriedades a serem indexadas.

class Book: Object {
  dynamic var price = 0
  dynamic var title = ""

  override static func indexedProperties() -> [String] {
    return ["title"]
  }
}

O Realm suporta indexar propriedades dos tipos String, Int, Bool, e NSDate.

Indexar uma propriedade irá aumentar consideravelmente a velocidade das consultas quando houver comparação de igualdade (ex. os operadores = e IN), ao custo de inserções mais lentas.

Chaves primárias

A chave primária é definida sobrescrevendo o método primaryKey(). Ele deve retornar uma String com o nome da propriedade que será a chave primária do modelo. Definir uma chave primária permite que o objeto seja consultado e atualizado de forma eficiente e força que seu valor seja único. Uma vez que um valor de chave primária seja definido no Realm ele não pode mais ser alterado.

class Person: Object {
  dynamic var id = 0
  dynamic var name = ""

  override static func primaryKey() -> String? {
    return "id"
  }
}

Propriedades ignoradas

Caso queira definir uma propriedade que não seja persistida ela deve ser do tipo somente leitura ou estar definida no método ignoredProperties(). O método deve retornar uma lista de Strings com os nomes das propriedades a serem ignoradas.

class Person: Object {
  dynamic var tmpID = 0
  var name: String { // read-only properties are automatically ignored
    return "\(firstName) \(lastName)"
  }
  dynamic var firstName = ""
  dynamic var lastName = ""

  override static func ignoredProperties() -> [String] {
    return ["tmpID"]
  }
}

Transações de escrita

Todas as operações realizadas com objetos Realm devem estar dentro de uma transação de escrita para serem persistidas. Os objetos do Realm podem ser instanciados e utilizados como classes normais do Swift sem que seja realizada a transação de escrita mas, estas transações não serão persistidas.

As transações possuem um overhead para serem realizadas. Portanto é recomendado que o maior número possível de operações sejam realizadas dentro de uma mesma transação para otimizar a performance.

Criando um objeto

Para criar um novo objeto do Realm basta instanciar um novo objeto do modelo.

Um objeto pode ser instanciado de várias formas:

  1. Criando o objeto e definindo suas propriedades;
  2. Criando o objeto através de um dicionário com as propriedades e seus valores;
  3. Cirando um objeto através de um array de valores com a mesma ordem em que as propriedades são definidas no modelo.
class Dog: Object {
  dynamic var name = ""
  dynamic var age = 0
}

// (1) Create a Dog object and then set its properties
var myDog = Dog()
myDog.name = "Rex"
myDog.age = 10

// (2) Create a Dog object from a dictionary
let myOtherDog = Dog(value: ["name" : "Pluto", "age": 3])

// (3) Create a Dog object from an array
let myThirdDog = Dog(value: ["Fido", 5])

Também é possível inserir vários valores com um array ou inserir objetos encadeados com arrays e/ou listas.

import RealmSwift

// Dog model
class Dog: Object {
    dynamic var name = ""
    let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}

// Person model
class Person: Object {
    dynamic var name = ""
    dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
    let dogs = List<Dog>()
}

// Instead of using already existing dogs...
let aPerson = Person(value: ["Jane", 30, [aDog, anotherDog]])

// ...we can create them inline
let anotherPerson = Person(value: ["Jane", 30, [["Buster", 5], ["Buddy", 6]]])

Adicionando objetos

Os objetos são adicionados ao Realm da seguinte forma:

// Create a Person object
let author = Person()
author.name = "David Foster Wallace"

// Get the default Realm
let realm = try! Realm()
// You only need to do this once (per thread)

// Add to the Realm inside a transaction
try! realm.write {
  realm.add(author)
}

Todas as alterações realizadas no objeto, dentro da transação, serão disponibilizadas para todas os objetos que utilizam o mesmo Realm de forma automática.

Enquanto uma transação de escrita é realizada ela bloqueia a escrita para outras transações. As operações de leitura não são bloqueadas.

Atualizando objetos

Objetos com chave primária definida podem ser atualizados com o método Realm().add(_:update:). O Realm irá identificar se existe um registro com a chave informada no banco de dados. Caso exista o mesmo será atualizado, senão será criado.

// Creating a book with the same primary key as a previously saved book
let cheeseBook = Book()
cheeseBook.title = "Cheese recipes"
cheeseBook.price = 9000
cheeseBook.id = 1

// Updating book with id = 1
try! realm.write {
  realm.add(cheeseBook, update: true)
}

Se não forem passadas todas as propriedades as que não forem passadas terão o seu valor atual mantido na base.

// Assuming a "Book" with a primary key of `1` already exists.
try! realm.write {
  realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
  // the book's `title` property will remain unchanged.
}

O parâmetro update só deve ser utilizado em modelos com chave primária definida.

Excluindo objetos

Para excluir um objeto basta usar o método delete(_:). A chamada deve estar dentro de uma trasação (realm.write).

// let cheeseBook = ... Book stored in Realm

// Delete an object with a transaction
try! realm.write {
  realm.delete(cheeseBook)
}

Para excluir todos os objetos utilize o método deleteAll().

// Delete all objects from the realm
try! realm.write {
  realm.deleteAll()
}

Consultas

As consultas retornam uma instância de Results que contem uma coleção de Objects. O Results é muito similar a um array com a limitação de possuir apenas um tipo de objeto.

Os objetos das consultas do Realm são lazy. Ou seja, o dado só é lido quando for acessado. Isto serve inclusive para as propriedades dos objetos.

Você pode atualizar os resultados das consultas que irá atualizar o dado no Realm. Você também pode acessar todos os dados dos relacionamentos dos objetos.

Todas as atualizações na base são refletidas diretamente no Results mesmo depois que a consulta for feita.

A forma mais simples de executar uma consulta é com o metódo objects(_:).

let dogs = realm.objects(Dog.self) // retrieves all Dogs from the default Realm

Filtros

Os filtros das consultas do Realm são feitos com NSPredicate da mesma forma que fazemos nos arrays. Um ótimo tutorial sobre predicates pode ser encontrado no site NSHipster.

O filtro pode ser um objeto NSPredicate ou um string predicate.

// Query using a predicate string
var tanDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'")

// Query using an NSPredicate
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = realm.objects(Dog.self).filter(predicate)

Mais informações podem ser encontradas na documentação oficial do Realm.
Alternativamente você pode encadear várias consultas com o Results. O overhead é bem menor se comparado aos bancos de dados tradicionais onde uma nova consulta ao banco é realizada todas as vezes.

let tanDogs = realm.objects(Dog.self).filter("color = 'tan'")
let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")

Ordenação

O Results permite acrescentar uma ordenação informando uma ou mais propriedades com os métodos sorted(_:) e sorted(_:ascending:).

// Sort tan dogs with names starting with "B" by name
let sortedDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted("name")

Não há garantias que o resultado de uma consulta respeite a ordem em que os dados forem inseridos. Se quiser que uma determinada ordem seja comprida utilize o método sorted(_:) ou sorted(_:ascending:).

Limitando os resultados

No Realm não é necessário limitar a quantidade de dados retornado porque os resultados são lazy e só serão carregados se forem acessados. Mas se precisar limitar a quantidade de resultados para satisfazer uma necessidade relacionada a apresentação na interface você pode iterar no Results limitando a quantidade desejada.

// Loop through the first 5 Dog objects
// restricting the number of objects read from disk
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
  let dog = dogs[i]
  // ...
}

Realms

Para criar uma instância do Realm chamamos a função Realm(). Esta função irá criar um arquivo chamado default.realm no diretório Documents (iOS) ou no diretório Application Support (OS X) do seu aplicativo.

Além do arquivo .realm, o Realm também cria arquivos auxiliares para a sua operação interna:

  • .realm.lock – Um arquivo para recursos de lock;
  • .realm.log_a – Arquivo de log para log de transações;
  • .real.note – Um named pipe para notificações.

Realm Configuration

As configurações do database Realm são realizadas através do Realm.Configuration. Nele podemos indicar, por exemplo, o nome e o local onde o arquivo será gravado. Para passar a configuração para a instância do Realm podemos utilizar o initializer Realm(configuration:) ou a propriedade Realm.Configuration.defaultConfiguration.

func setDefaultRealmForUser(username: String) {
  var config = Realm.Configuration()

  // Use the default directory, but replace the filename with the username
  config.fileURL = config.fileURL!.URLByDeletingLastPathComponent?
                         .URLByAppendingPathComponent("\(username).realm")

  // Set this as the configuration used for the default Realm
  Realm.Configuration.defaultConfiguration = config
}

O Realm assim como o Core Data, possui a opção de utilização in-memory. Ou seja, sem persistir os dados no disco. Isto é bastante útil para testes unitários.

let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))

Copiar um objeto de um Realm para outro é bastante simples. Basta utilizar o método realm.create(MyObjectSubclass.self, value: originalObjectInstance).

Notificações

O Realm possui opções de notificações para o Realm, Results, List e LinkedObjects através do método addNotificationBlock. Também é possível observar alterações de um único objeto através de Key-Value-Observation.
Para mais informações sobre notificações no Realm consulte o manual do mesmo.

Migração

O Realm possui suporte a migração de dados através do Realm.configuration. Nele é possível definirmos as versões da base e realizarmos as devidas migrações entre as versões. Exemplo:

// v0
// class Person: Object {
//   dynamic var firstName = ""
//   dynamic var firstName = ""
//   dynamic var age = 0
// }

// v1
// class Person: Object {
//   dynamic var fullName = "" // new property
//   dynamic var age = 0
// }

// v2
class Person: Object {
  dynamic var fullName = ""
  dynamic var email = "" // new property
  dynamic var age = 0
}

A migração ficaria assim:

Realm.Configuration.defaultConfiguration = Realm.Configuration(
  schemaVersion: 2,
  migrationBlock: { migration, oldSchemaVersion in
    // The enumerateObjects:block: method iterates
    // over every 'Person' object stored in the Realm file
    migration.enumerate(Person.className()) { oldObject, newObject in
      // Add the `fullName` property only to Realms with a schema version of 0
      if oldSchemaVersion < 1 {
        let firstName = oldObject!["firstName"] as! String
        let lastName = oldObject!["lastName"] as! String
        newObject!["fullName"] = "\(firstName) \(lastName)"
      }

      // Add the `email` property to Realms with a schema version of 0 or 1
      if oldSchemaVersion < 2 {
          newObject!["email"] = ""
      }
    }
  })

// Realm will automatically perform the migration and opening the Realm will succeed
let realm = try! Realm()

Para outras informações tais como threading, encriptação, testes e exemplos consulte a documentação da biblioteca.

Instalação

O RealmSwift pode ser instalado via CocoaPods através do comando abaixo. Mais detalhes sobre a utilização do CocoaPods pode ser visto aqui.

pod 'RealmSwift'

O RealmSwift também pode ser instalado manualmente e via Carthage.

Criando as classes do modelo

O nosso modelo será composto pelas entidades Candidato (Candidate), Coligação (Coalition), Partido (Party), Bem (Good) e Tipo do Bem (GoodType). Com estas entidades conseguimos mostrar a lista de candidatos com os seus respectivos partidos, coligações e vices. Assim como a lista de bens declarados.

Na imagem abaixo podemos ver como ficaria o modelo utilizando Core Data.
Data Model
A seguir temos as classes dos modelos.
Candidate

//
//  Candidate.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 25/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import Foundation
import RealmSwift

class Candidate: Object {
  dynamic var id: Int16 = 0
  dynamic var nickname: String = ""
  dynamic var name: String = ""
  dynamic var post: String = ""
  dynamic var birthdate: NSDate = NSDate(timeIntervalSince1970: 0)
  dynamic var sex: String = ""
  dynamic var colorRace: String = ""
  dynamic var maritalStatus = ""
  dynamic var nationality = ""
  dynamic var naturalness = ""
  dynamic var literacy: String = ""
  dynamic var occupation: String = ""
  dynamic var party: Party?
  dynamic var coalition: Coalition?
  dynamic var processNumber: String = ""
  dynamic var protocolNumber: String = ""
  dynamic var spendingLimitFirstRound: Double = 0.0
  dynamic var spendingLimitSecondRound: Double = 0.0
  dynamic var image: String = ""
  dynamic var site: String?
  dynamic var vice: Candidate?
  let goods = List()
  let holder = LinkingObjects(fromType: Candidate.self, property: "vice")

  override static func primaryKey() -> String? {
    return "id"
  }

  override static func indexedProperties() -> [String] {
    return ["nickname"]
  }

  // Specify properties to ignore (Realm won't persist these)
  override static func ignoredProperties() -> [String] {
    return []
  }
}

Coalition

//
//  Coalition.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 25/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import Foundation
import RealmSwift

class Coalition: Object {
  dynamic var id: Int16 = 0
  dynamic var name: String = ""
  let parties = List()
  let candidates = LinkingObjects(fromType: Candidate.self, property: "coalition")

  override static func primaryKey() -> String? {
    return "id"
  }

  // Specify properties to ignore (Realm won't persist these)
  override static func ignoredProperties() -> [String] {
    return []
  }
}

Party

//
//  Party.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 25/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import Foundation
import RealmSwift

class Party: Object {
  dynamic var id: Int16 = 0
  dynamic var number: Int16 = 0
  dynamic var acronym: String = ""
  dynamic var name: String = ""
  dynamic var president: String = ""
  let candidates = LinkingObjects(fromType: Candidate.self, property: "party")
  let coalition = LinkingObjects(fromType: Coalition.self, property: "parties")

  override static func primaryKey() -> String? {
    return "id"
  }

  // Specify properties to ignore (Realm won't persist these)
  override static func ignoredProperties() -> [String] {
    return []
  }
}

Good

//
//  Good.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 25/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import Foundation
import RealmSwift

class Good: Object {
  dynamic var id: Int16 = 0
  dynamic var goodDescription: String = ""
  dynamic var type: GoodType?
  dynamic var value: Double = 0
  let candidate = LinkingObjects(fromType: Candidate.self, property: "goods")

  override static func primaryKey() -> String? {
    return "id"
  }

  // Specify properties to ignore (Realm won't persist these)
  override static func ignoredProperties() -> [String] {
    return []
  }
}

GoodType

//
//  GoodType.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 25/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import Foundation
import RealmSwift

class GoodType: Object {
  dynamic var id: Int16 = 0
  dynamic var typedescription: String = ""
  let goods = LinkingObjects(fromType: Good.self, property: "type")

  override static func primaryKey() -> String? {
    return "id"
  }

  // Specify properties to ignore (Realm won't persist these)
  override static func ignoredProperties() -> [String] {
    return []
  }
}

Inserindo os dados no Realm

Para realizar a carga dos dados criei uma classe de ajuda Data com a seguinte estrutura.

//
//  Data.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 25/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import Foundation
import RealmSwift

class Data {
  let realm: Realm
  let birthdateFormatter: NSDateFormatter

  init(realm: Realm) {
    self.realm = realm

    self.birthdateFormatter = NSDateFormatter()
    self.birthdateFormatter.dateFormat = "dd/MM/yyyy"

    self.realmDeleteAll()
    self.loadData()
  }

  private func loadData() {
    //
  }

  private func realmAddObject(object: Object) {
    do {
      try realm.write { realm.add(object) }
    } catch {
      print((error as NSError).localizedDescription)
    }
  }

  private func realmAddObjects(objects: [Object]) {
    do {
      for object in objects {
        try realm.write { realm.add(object) }
      }
    } catch {
      print((error as NSError).localizedDescription)
    }
  }

  private func realmDeleteAll() {
    do {
      try realm.write { realm.deleteAll() }
    } catch {
      print((error as NSError).localizedDescription)
    }
  }
}

A classe possui duas propriedades. A propriedade realm armazena a instância do Realm que será criado no AppDelegate e a propriedade birthdateFormatter que servirá para criar uma data a partir de uma string no formato dd/MM/yyyy.

Temos um inicializador da classe que recebe uma instância do Realm e atribui a instância a nossa propriedade local. Depois configuramos a nossa propriedade para formatar a data e por fim chamamos os métodos realmDeleteAll() e loadData(). O primeiro exclui todos os registros da base e o segundo irá popular a base com as informações dos candidatos.

Note que em um aplicativo de produção realizariamos a carga de dados em segundo plano e adicionaríamos a maior quantidade de dados dentro de uma mesma transação para efeito de performance.

Mas para o nosso tutorial criei os métodos realmAddObject(_:) e realmAddObjects(_:) para inserir os objetos individualmente.

Para inserir um novo candidato precisamos das seguintes informações: partido, coligação, tipo de bens, bens, candidato e vice. Vamos inserir estes dados nesta mesma ordem para o primeiro candidato.

A nossa função loadData() ficará assim:

private func loadData() {
  let PSTU = Party(value: ["id": 17, "acronym": "PSTU", "name": "PARTIDO SOCIALISTA DOS TRABALHADORES UNIFICADO", "president": "JOSÉ MARIA DE ALMEIDA", "number": 16])
  realmAddObject(PSTU)

  let coalition16 = Coalition(value: ["id": 11, "name": "-", "parties": [PSTU]])
  realmAddObject(coalition16)

  let house = GoodType(value: ["id": 2, "typedescription": "Casa"])
  realmAddObject(house)
  let bankAccount = GoodType(value: ["id": 4, "typedescription": "Depósito bancário em conta corrente no País"])
  realmAddObject(bankAccount)
  let vehicle = GoodType(value: ["id": 5, "typedescription": "Veículo automotor terrestre: caminhão, automóvel, moto, etc."])
  realmAddObject(vehicle)
  let land = GoodType(value: ["id": 7, "typedescription": "Terreno"])
  realmAddObject(land)

  var vanessaPortugalGoods = [Good]()
  vanessaPortugalGoods.append(Good(value: ["id": 225, "goodDescription": "VALOR EM CONTA BANCÁRIA", "type": bankAccount, "value": 100_000.00]))
  vanessaPortugalGoods.append(Good(value: ["id": 226, "goodDescription": "CASA EM BOA ESPERANÇA", "type": house, "value": 150_000.00]))
  vanessaPortugalGoods.append(Good(value: ["id": 227, "goodDescription": "1/6 DE GLEBA DE 64 HA", "type": land, "value": 10_000.00]))
  vanessaPortugalGoods.append(Good(value: ["id": 228, "goodDescription": "1/6 DE GLEBA DE 10 HA", "type": land, "value": 1_000.00]))
  vanessaPortugalGoods.append(Good(value: ["id": 229, "goodDescription": "CARRO FIAT UNO 2011", "type": vehicle, "value": 14_000.00]))
  realmAddObjects(vanessaPortugalGoods)

  let vanessaPortugal = Candidate(value: ["id": 21,
    "nickname": "VANESSA PORTUGAL",
    "name": "VANESSA PORTUGAL BARBOSA",
    "post": "Prefeito - BELO HORIZONTE/MG",
    "birthdate": birthdateFormatter.dateFromString("12/04/1970")!,
    "sex": "Feminino",
    "colorRace": "BRANCA",
    "maritalStatus": "Casado(a)",
    "nationality": "Brasileira nata",
    "naturalness": "MG-BOA ESPERANÇA",
    "literacy": "Superior completo",
    "occupation": "Professor de Ensino Fundamental",
    "party": PSTU,
    "coalition": coalition16,
    "processNumber": "1458-37.2016.6.13.0029",
    "protocolNumber": "3663562016",
    "spendingLimitFirstRound": 26_697_376.47,
    "spendingLimitSecondRound": 8_009_212.94,
    "site": "Nenhum site cadastrado.",
    "image": "130000084715",
    "goods": vanessaPortugalGoods,
    "vice": ["id": 22,
      "nickname": "FIRMINIA RODRIGUES",
      "name": "FIRMINIA MARIA OLIVEIRA RODRIGUES",
      "post": "Vice-prefeito - BELO HORIZONTE/MG",
      "birthdate": birthdateFormatter.dateFromString("22/09/1980")!,
      "sex": "Feminino",
      "colorRace": "PRETA",
      "maritalStatus": "Solteiro(a)",
      "nationality": "Brasileira nata",
      "naturalness": "MG-BELO HORIZONTE",
      "literacy": "Superior completo",
      "occupation": "Estudante, Bolsista, Estagiário e Assemelhados",
      "party": PSTU,
      "coalition": coalition16,
      "processNumber": "1459-22.2016.6.13.0029",
      "protocolNumber": "3663572016",
      "spendingLimitFirstRound": 0.0,
      "spendingLimitSecondRound": 0.0,
      "site": "Nenhum site cadastrado.",
      "image": "130000084717"]])
  realmAddObject(vanessaPortugal)
}

O arquivo Data.swift completo pode ser encontrado aqui.

Na classe AppDelegate no método application(_:didFinishLaunchingWithOptions:) iremos criar o nosso banco de dados Realm com a configuração padrão e passaremos a instância do objeto para a classe Data e para a classe CandidatesTableViewController que será o nosso viewController inicial.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  do {
    let realm = try Realm()

    let _ = Data(realm: realm)

    let navigationViewController = window?.rootViewController as! UINavigationController
    let candidateTableViewController = navigationViewController.visibleViewController as! CandidatesTableViewController
    candidateTableViewController.realm = realm
  } catch {
    print((error as NSError).localizedDescription)
  }

  return true
}

Exibindo as informações na interface

A nossa tela inicial terá um grid com as informações dos candidatos a prefeito de Belo Horizonte. A célula irá mostrar uma foto do candidato com o seu apelido, nome da coligação e partido.

Candidatos 01 Candidatos 02 Candidatos 03

A célula possui a seguinte classe.

//
//  CandidatesTableViewCell.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 26/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit

class CandidatesTableViewCell: UITableViewCell {
  @IBOutlet weak var candidatePhotoImageView: UIImageView!
  @IBOutlet weak var nicknameLabel: UILabel!
  @IBOutlet weak var coalitionLabel: UILabel!
  @IBOutlet weak var partyLabel: UILabel!

  override func prepareForReuse() {
    candidatePhotoImageView.image = nil
    nicknameLabel.text = ""
    coalitionLabel.text = ""
    partyLabel.text = ""
  }
}

A classe CandidatesTableViewController possui a seguinte estrutura.

//
//  CandidatesTableViewController.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 26/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit
import RealmSwift

class CandidatesTableViewController: UITableViewController {
  // MARK: - Enums
  private enum Cell {
    static let candidateCell = "CandidateCell"
  }

  private enum Segue {
    static let candidateSegue = "showCandidateSegue"
  }

  // MARK: Properties
  var realm: Realm!
  var candidates: Results!

  // MARK: Life Cycle Methods
  override func viewDidLoad() {
    super.viewDidLoad()

    candidates = realm.objects(Candidate.self).filter(NSPredicate(format: "post = 'Prefeito - BELO HORIZONTE/MG'")).sorted("nickname")

    let candidatesLabel = NSLocalizedString("Candidates.Navigation.Title", value: "Candidates", comment: "")
    title = candidatesLabel
  }

  // MARK: Navigation
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == Segue.candidateSegue {
      let candidateViewController = segue.destinationViewController as! CandidateViewController
      candidateViewController.candidate = sender as! Candidate
    }
  }

  // MARK: - Table view data source
  override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
  }

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

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = setupView(indexPath)

    return cell
  }

  private func setupView(indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(Cell.candidateCell, forIndexPath: indexPath) as! CandidatesTableViewCell
    let candidate = candidates[indexPath.row]

    if let image = UIImage(named: candidate.image) {
      cell.candidatePhotoImageView?.image = image
    }
    cell.nicknameLabel.text = candidate.nickname
    cell.coalitionLabel.text = candidate.coalition?.name
    cell.partyLabel.text = candidate.party?.acronym

    return cell
  }

  // MARK: - Table view delegate
  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let candidate = candidates[indexPath.row]

    performSegueWithIdentifier(Segue.candidateSegue, sender: candidate)

    tableView.deselectRowAtIndexPath(indexPath, animated: true)
  }

  override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
  }

  override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
  }
}

A classe possui duas propriedades. A propriedade realm é passada pelo AppDelegate e a propriedade candidates irá armazenar a nossa consulta.

No método viewDidLoad() consultamos os candidatos armazenados no Realm com o método objects(_:) passando o modelo Candidate. Depois chamamos o método filter(_:) passando um NSPredicate para filtrar apenas os candidatos a prefeito (os vice prefeitos também estão na entidade Candidate). Pra finalizar chamamos o método sorted(_:) para ordenar os candidatos pelo apelido.

No método tableView(_:numberOfRowsInSection:) retornamos a quantidade de objetos no collection Results.

No método setupView(_:) informamos o apelido, a coligação e a sigla do partido do candidato para a célula. Note que acessamos a sigla do partido através da propriedade party do candidato que está relacionado com o modelo Party e através deste acessamos a propriedade acronym. Fazemos a mesma coisa com a coligação.

No método tableView(_:didSelectRowAtIndexPath:) obtemos o candidato selecionado através do índice do tableView e chamamos a segue para a tela de detalhes do candidato.

No método prepareForSegue(_:sender:) passamos o candidato para a classe CandidateViewController onde serão exibidos os detalhes do candidato selecionado.

A tela de exibição dos detalhes do candidato é formada pela classe CandidateViewController.

detalhe-candidato detalhe-vice

A tela possui um tableView com três células diferentes. A primeira célula é utilizada na seção candidato e possui a foto, o apelido, o número do candidato, o cargo pelo qual está concorrendo, o nome do partido e a sigla do partido.

A segunda célula é utilizada na seção Consultas e possui uma consulta para o vice candidato e uma consulta para a declaração de bens.

A terceira célula é usada na seção Dados Pessoais que lista os seguintes dados: Nome Completo, Data de Nascimento, Sexo, Cor / Raça, Estado Civil, Nacionalidade, Naturalidade, Grau de Instrução, Ocupação, Coligação, Composição da Coligação, Número do Processo, Número do Protocolo, Limite de Gastos Primeiro Turno, Limite de Gastos Segundo Turno e Site.

As células possuem o seguinte código.

CandidateTableViewCell

//
//  CandidateTableViewCell.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 27/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit

class CandidateTableViewCell: UITableViewCell {
  @IBOutlet weak var candidatePhotoImageView: UIImageView!
  @IBOutlet weak var nicknameLabel: UILabel!
  @IBOutlet weak var partyNumberLabel: UILabel!
  @IBOutlet weak var postLabel: UILabel!
  @IBOutlet weak var partyNameLabel: UILabel!

  override func prepareForReuse() {
    super.prepareForReuse()

    candidatePhotoImageView.image = nil
    nicknameLabel.text = ""
    partyNumberLabel.text = ""
    postLabel.text = ""
    partyNameLabel.text = ""
  }
}

ConsultationTableViewCell

//
//  ConsultationTableViewCell.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 27/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit

class ConsultationTableViewCell: UITableViewCell {
  @IBOutlet weak var consultationBackgroundView: UIView!
  @IBOutlet weak var menuImageView: UIImageView!
  @IBOutlet weak var consultationLabel: UILabel!
}

PersonalDataTableViewCell

//
//  PersonalDataTableViewCell.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 27/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit

class PersonalDataTableViewCell: UITableViewCell {
  @IBOutlet weak var personalBackgroundView: UIView!
  @IBOutlet weak var personalDataIconImageView: UIImageView!
  @IBOutlet weak var personalDataLabel: UILabel!
  @IBOutlet weak var descriptionLabel: UILabel!

  override func prepareForReuse() {
    super.prepareForReuse()

    personalDataIconImageView.image = nil
    personalDataLabel.text = ""
    descriptionLabel.text = ""
  }
}

A classe CandidateViewController possui as seguintes propriedades: tableView que será a view que mostrará as informações dos candidatos, data que irá armazenar os títulos das seções e das descrições dos campos, candidate onde teremos o nosso objeto Candidate passado pela tela inicial ao clicar no candidato e numberFormatter que servirá para formatar os campos com valores em Reais.

No método viewDidLoad carregamos as informações que serão exibidas no tableView através do método getData() que veremos mais adiante. Também alteramos o título do navigationBar para o nome do candidato.

No método prepareForSegue(_:sender:) passamos o candidato para o controller CandidateViewController quando o usuário clicar na célula de declaração de bens.

//
//  CandidateViewController.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 27/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit
import RealmSwift
import Font_Awesome_Swift

class CandidateViewController: UIViewController {
  // MARK: - Enums
  private enum Cell {
    static let candidateCell = "CandidateCell"
    static let consultationCell = "ConsultationCell"
    static let personalDataCell = "PersonalDataCell"
  }

  private enum Segue {
    static let ListOfGoods = "showListOfGoodsSegue"
  }

  // MARK: Properties
  var tableView: UITableView!
  var data: [[String: AnyObject]]!

  var candidate: Candidate!

  var numberFormatter: NSNumberFormatter = {
    let numberFormatter = NSNumberFormatter()
    numberFormatter.numberStyle = .CurrencyStyle
    numberFormatter.currencyCode = "BRL"
    numberFormatter.minimumFractionDigits = 2
    numberFormatter.maximumFractionDigits = 2

    return numberFormatter
  }()

  // MARK: Life cycle Methods
  override func viewDidLoad() {
    super.viewDidLoad()

    data = getData()

    title = candidate.nickname
  }

  // MARK: Navigation
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == Segue.ListOfGoods {
      let detailsOfGoodsViewController = segue.destinationViewController as! DetailsOfGoodsViewController
      detailsOfGoodsViewController.candidate = candidate
    }
  }
}

Na implementação do protocolo UITableViewDataSource obtemos as quantidades de seções e de linhas do tableView do nosso dicionário data.

Para obtermos as células no método tableView(_:cellForRowAtIndexPath:) primeiramente verificamos em qual seção estamos. Se for a seção Candidato obtemos um objeto da classe CandidateTableViewCell e informamos as propriedades da classe com as informações da propriedade candidate.

Se for a seção Consultas obtemos um objeto da classe ConsultationTableViewCell e utilizamos a propriedade data para obtermos o título da consulta. Como este mesmo controller é utilizado para candidatos a prefeito e candidatos a vice prefeito, precisamos verificar se o mesmo possui a propriedade vice informada para mostrarmos o disclosure indicator. Fazemos a mesma verificação com a consulta de Declaração de Bens verificando a propriedade goods, pois alguns candidatos não declararam bens.

E caso seja a seção Dados Pessoais obtemos um objeto da classe PersonalDataTableViewCell e preenchemos as propriedades da classe com as informações das propriedades data e candidate.

// MARK: -
extension CandidateViewController: UITableViewDataSource {
  // MARK: - UITableViewDataSource
  func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return data.count
  }

  func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return data[section]["values"]!.count
  }

  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    switch indexPath.section {
      case 0:
        if let cell = tableView.dequeueReusableCellWithIdentifier(Cell.candidateCell, forIndexPath: indexPath) as? CandidateTableViewCell {
          cell.nicknameLabel.text = candidate.nickname
          cell.partyNumberLabel.text = String(candidate.party!.number)
          cell.postLabel.text = candidate.post
          cell.partyNameLabel.text = "\(candidate.party!.name) - \(candidate.party!.acronym)"
          if let image = UIImage(named: candidate.image) {
            cell.candidatePhotoImageView.image = image
          }

          return cell
        }
      case 1:
        if let cell = tableView.dequeueReusableCellWithIdentifier(Cell.consultationCell, forIndexPath: indexPath) as? ConsultationTableViewCell {
          if let sections = data[indexPath.section]["values"] as? [String],
            let consultation = sections[indexPath.row] as? String {
            cell.consultationLabel.text = consultation
          }
          cell.menuImageView.setFAIconWithName(FAType.FAList, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))

          switch indexPath.row {
            case 0:
              if let _ = candidate.vice {
                cell.accessoryType = .DisclosureIndicator
              } else {
                cell.accessoryType = .None
              }
            case 1:
              if candidate.goods.count > 0 {
                cell.accessoryType = .DisclosureIndicator
              } else {
                cell.accessoryType = .None
              }
            default: break
          }

          return cell
        }
      default:
        if let cell = tableView.dequeueReusableCellWithIdentifier(Cell.personalDataCell, forIndexPath: indexPath) as? PersonalDataTableViewCell {
          if let sections = data[indexPath.section]["values"] as? [String],
            let detail = sections[indexPath.row] as? String {
            cell.descriptionLabel.text = detail
          }

          switch indexPath.row {
            case 0:
              cell.personalDataLabel.text = candidate.name
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAUser, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 1:
              cell.personalDataLabel.text = NSDateFormatter.localizedStringFromDate(candidate.birthdate, dateStyle: .ShortStyle, timeStyle: .NoStyle)
              cell.personalDataIconImageView.setFAIconWithName(FAType.FACalendar, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 2:
              cell.personalDataLabel.text = candidate.sex
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAVenus, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 3:
              cell.personalDataLabel.text = candidate.colorRace
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAChild, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 4:
              cell.personalDataLabel.text = candidate.maritalStatus
              cell.personalDataIconImageView.setFAIconWithName(FAType.FACircleO, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 5:
              cell.personalDataLabel.text = candidate.nationality
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAMap, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 6:
              cell.personalDataLabel.text = candidate.naturalness
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAMap, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 7:
              cell.personalDataLabel.text = candidate.literacy
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAGraduationCap, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 8:
              cell.personalDataLabel.text = candidate.occupation
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAInstitution, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 9:
              cell.personalDataLabel.text = candidate.coalition!.name
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAGroup, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 10:
              cell.personalDataLabel.text = candidate.coalition!.parties.flatMap { $0.acronym }.joinWithSeparator(" / ")
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAFolderOpen, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 11:
              cell.personalDataLabel.text = candidate.processNumber
              cell.personalDataIconImageView.setFAIconWithName(FAType.FASitemap, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 12:
              cell.personalDataLabel.text = candidate.protocolNumber
              cell.personalDataIconImageView.setFAIconWithName(FAType.FABriefcase, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 13:
              cell.personalDataLabel.text = numberFormatter.stringFromNumber(NSNumber(double: candidate.spendingLimitFirstRound))!
              cell.personalDataIconImageView.setFAIconWithName(FAType.FADollar, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            case 14:
              cell.personalDataLabel.text = numberFormatter.stringFromNumber(NSNumber(double: candidate.spendingLimitSecondRound))!
              cell.personalDataIconImageView.setFAIconWithName(FAType.FADollar, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
            default:
              cell.personalDataLabel.text = candidate.site
              cell.personalDataIconImageView.setFAIconWithName(FAType.FAGlobe, textColor: UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
          }

          return cell
        }
    }

    return UITableViewCell()
  }

  func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if let text = data[section]["section"] as? String {
      return text
    }

    return nil
  }
}

No protocolo UITableViewDelegate implementamos o método tableView(_:didSelectRowAtIndexPath:) para verificarmos se o usuário clicou em uma linha da seção Consultas. Caso tenha clicado verificamos candidato possui vice ou declaração de bens para chamarmos os controllers das telas correspondentes.

// MARK: -
extension CandidateViewController: UITableViewDelegate {
  // MARK: - UITableViewDelegate
  func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if indexPath.section == 1 {
      if indexPath.row == 0 {
        if let vice = candidate.vice {
          let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
          let candidateViewController = storyboard.instantiateViewControllerWithIdentifier("CandidateViewController") as! CandidateViewController
          candidateViewController.candidate = vice

          self.navigationController?.pushViewController(candidateViewController, animated: true)
        } else {
          tableView.deselectRowAtIndexPath(indexPath, animated: true)
        }
      } else {
        if candidate.goods.count > 0 {
          performSegueWithIdentifier(Segue.ListOfGoods, sender: nil)
        } else {
          tableView.deselectRowAtIndexPath(indexPath, animated: true)
        }
      }
    }
  }

  func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
  }

  func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    switch indexPath.section {
      case 0: return 217.0
      case 1: return 63.0
      default: return 73.0
    }
  }
}

O método getData() retorna um dicionário com os campos section e values sendo que eles correspondem ao nome da seção e aos dados utilizados em cada seção.

// MARK: -
extension CandidateViewController {
  func getData() -> [[String: AnyObject]] {
    let candidateLabel = NSLocalizedString("Candidate.Detail.Candidate", value: "candidate", comment: "")
    let consultationLabel = NSLocalizedString("Candidate.Detail.Consultation", value: "Consultation", comment: "")
    let personalDataLabel = NSLocalizedString("Candidate.Detail.PersonalData", value: "Personal Data", comment: "")
    let viceLabel = NSLocalizedString("Candidate.Detail.Vices", value: "Vice", comment: "")
    let listOfGoods = NSLocalizedString("Candidate.Detail.ListOfGoods", value: "List of Goods", comment: "")
    let fullName = NSLocalizedString("Candidate.Detail.FullName", value: "Full name", comment: "")
    let dateOfBirth = NSLocalizedString("Candidate.Detail.DateOfBirth", value: "Date of birth", comment: "")
    let sex = NSLocalizedString("Candidate.Detail.Sex", value: "Sex", comment: "")
    let colorRace = NSLocalizedString("Candidate.Detail.ColorRace", value: "Color / race", comment: "")
    let maritalStatus = NSLocalizedString("Candidate.Detail.MaritalStatus", value: "Marital status", comment: "")
    let nationality = NSLocalizedString("Candidate.Detail.Nationality", value: "Nationality", comment: "")
    let naturalness = NSLocalizedString("Candidate.Detail.Naturalness", value: "Naturalness", comment: "")
    let literacy = NSLocalizedString("Candidate.Detail.Literacy", value: "Literacy", comment: "")
    let occupation = NSLocalizedString("Candidate.Detail.Occupation", value: "Occupation", comment: "")
    let coalition = NSLocalizedString("Candidate.Detail.Coalition", value: "coalition", comment: "")
    let compositionOfTheCoalition = NSLocalizedString("Candidate.Detail.CompositionOfTheCoalition", value: "Composition of the Coalition", comment: "")
    let numberProcess = NSLocalizedString("Candidate.Detail.NumberProcess", value: "number Process", comment: "")
    let numberProtocol = NSLocalizedString("Candidate.Detail.NumberProtocol", value: "number Protocol", comment: "")
    let spendingLimitFirstRound = NSLocalizedString("Candidate.Detail.SpendingLimitFirstRound", value: "Spending Limit 1st Round", comment: "")
    let spendingLimitSecondRound = NSLocalizedString("Candidate.Detail.SpendingLimitSecondRound", value: "Spending Limit 2nd Round", comment: "")
    let candidateSite = NSLocalizedString("Candidate.Detail.CandidateSite", value: "Candidate Site", comment: "")

    let data: [[String: AnyObject]] = [["section": candidateLabel, "values": [candidate]],
                ["section": consultationLabel, "values": [viceLabel, listOfGoods]],
                ["section": personalDataLabel, "values": [fullName, dateOfBirth, sex, colorRace, maritalStatus, nationality, naturalness, literacy, occupation, coalition, compositionOfTheCoalition, numberProcess, numberProtocol, spendingLimitFirstRound, spendingLimitSecondRound, candidateSite]]]

    return data
  }
}

Por último temos a tela de declaração de bens onde listamos os bens declarados em um tableView e mostramos o total declarado na parte inferior da tela.

lista-de-bens

A classe DetailsOfGoodsViewController é bem simples e podemos destacar o método ValueOfGoods() onde somamos o total de bens declarados percorrendo a lista de bens e somando o campo value através da função flatMap.

//
//  DetailsOfGoodsViewController.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 29/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit
import RealmSwift

class DetailsOfGoodsViewController: UIViewController {
  // MARK: - Enums
  private enum Cell {
    static let DetailOfGoodCell = "DetailOfGoodTableViewCell"
  }

  // MARK: Oulets
  @IBOutlet weak var tableView: UITableView!
  @IBOutlet weak var totalValue: UILabel!

  // MARK: Properties
  var candidate: Candidate!
  var goods: List!

  var numberFormatter: NSNumberFormatter = {
    let numberFormatter = NSNumberFormatter()
    numberFormatter.numberStyle = .CurrencyStyle
    numberFormatter.currencyCode = "BRL"
    numberFormatter.minimumFractionDigits = 2
    numberFormatter.maximumFractionDigits = 2

    return numberFormatter
  }()

  // MARK: Life Cycle Methods
  override func viewDidLoad() {
    super.viewDidLoad()

    goods = candidate.goods

    title = candidate.nickname
  }

  override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    ValueOfGoods()
  }

  private func ValueOfGoods() {
    var total: Double = 0.0

    let _ = goods.flatMap { total += $0.value }

    totalValue?.text = numberFormatter.stringFromNumber(NSNumber(double: total))
  }
}

No protocolo UITableViewDataSource obtemos a quantidade de linhas da propriedade goods que é uma cópia da propriedade candidate.goods e no método tableView(_:cellForRowAtIndexPath:) obtemos um objeto da classe DetailOfGoodTableViewCell e informamos a descrição, o tipo e o valor do bem.

// MARK: -
extension DetailsOfGoodsViewController: UITableViewDataSource {
  // MARK: UITableViewDataSource
  func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
  }

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

  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(Cell.DetailOfGoodCell, forIndexPath: indexPath) as! DetailOfGoodTableViewCell
    let good = goods[indexPath.row]

    cell.descriptionLabel.text = good.goodDescription
    cell.typeLabel.text = good.type?.typedescription
    cell.valueLabel.text = numberFormatter.stringFromNumber(NSNumber(double: good.value))

    return cell
  }

  func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    let detailsOfGoodsLabel = NSLocalizedString("DetailsOfGoods.Header.Title", value: "Details of Goods", comment: "")

    return detailsOfGoodsLabel
  }
}

No protocolo UITableViewDelegate apenas informamos o tamanho da célula.

// MARK: -
extension DetailsOfGoodsViewController: UITableViewDelegate {
  // MARK: UITableViewDelegate
  func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
  }

  func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 141.0
  }
}

Segue abaixo a classe DetailOfGoodTableViewCell.

//
//  DetailOfGoodTableViewCell.swift
//  iElections
//
//  Created by Mateus Gustavo de Freitas e Silva on 29/08/16.
//  Copyright © 2016 Mateus Gustavo de Freitas e Silva. All rights reserved.
//

import UIKit

class DetailOfGoodTableViewCell: UITableViewCell {
  @IBOutlet weak var descriptionLabel: UILabel!
  @IBOutlet weak var typeLabel: UILabel!
  @IBOutlet weak var valueLabel: UILabel!

  override func prepareForReuse() {
    super.prepareForReuse()

    descriptionLabel.text = ""
    typeLabel.text = ""
    valueLabel.text = "R$0"
  }
}

Conclusão

Como pudemos ver no artigo, utilizar o Realm para a persistência de dados é uma tarefa bem simples. Ele funciona como uma classe Swift padrão na maior parte dos casos onde manipulamos as propriedades da classe exceto na parte da persistência onde utilizamos os métodos write() e commitWrite(). Por ter as propriedades do tipo lazy o gerenciamento de memória é eficiente. E segundo o desenvolvedor da biblioteca a performance de leitura e escrita é superior ao SQLite na maior parte das operações.

Em termos de recursos avançados temos versionamento, migração, cópia de dados e multithread. Este último item promete ser bem mais fácil de identificar os problemas relacionados a operações em diferentes threads do que temos no Core Data.

Espero que tenham gostado do tutorial. Qualquer dúvida, crítica ou sugestão de novos assuntos deixe um comentário abaixo. Caso queira entrar em contato pode me chamar no twitter @mateusfsilva. Se gostou compartilhe.

Publicidade

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s