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.
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 List
s e os RealmOptional
s 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 Object
s 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 String
s 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 String
s 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:
- Criando o objeto e definindo suas propriedades;
- Criando o objeto através de um dicionário com as propriedades e seus valores;
- 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 Object
s. 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.
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.
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
.
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.
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.