Olá povo,
Nesse post vou mostrar como utilizar o SQLite em aplicações iOS através da criação de um cadastro simples. Vou utilizar também o recurso de Storyboards (
veja esse post) para agilizar a criação das telas.
Comece criando um projeto no Xcode do tipo "
Single View Application" que vou nomeá-lo de ExemploSQLiteBlog. Lembre-se de deixar habilitadas as opções
Use storyboards e
Use automatic reference counting. Após criar o projeto, devemos adicionar a biblioteca do SQLite ao nosso projeto. Para tal, clique sobre o projeto, depois selecione a aba
Build phases. Na seção
Link Binary With Libraries clique no botão "+" e selecione
libsqlite3.dylib.
Uma vez que o projeto está configurado, vamos fazer toda a parte de "negócio" da aplicação, onde criaremos a classe de dados da aplicação, assim como a classe que fará a persistência dos dados da aplicação.
Crie uma nova classe chamada NGContact conforme abaixo (a utilização de prefixos no nome da classe em Objective-C é comum para evitar duplicidade):
// NGContact.h ----------------------
#import <Foundation/Foundation.h>
@interface NGContact : NSObject
@property (assign) NSInteger _id;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *address;
@property (strong, nonatomic) NSString *phone;
- (id)initWithName:(NSString *)name
andAddress:(NSString *)address
andPhone:(NSString *)phone;
@end
// NGContact.m ----------------------
#import "NGContact.h"
@implementation NGContact
@synthesize _id = __id, name = _name,
address = _address, phone = _phone;
- (id)initWithName:(NSString *)name
andAddress:(NSString *)address
andPhone:(NSString *)phone {
self = [super init];
if (self){
self.name = name;
self.phone = phone;
self.address = address;
}
return self;
}
@end
Aqui temos uma classe simples com 4 propriedades (id, nome, endereço e telefone) e um método para inicializar esses atributos. Para quem é "do Java" (como eu) esse método servirá como um construtor (lembrando que Objective-C não tem construtores).
Vamos agora criar a classe que fará a persistência desses objetos e utilizará a API do SQLite. Ela servirá de repositório da aplicação e a chamaremos de NGContactDB, e o arquivo de cabeçalho (.h) deve ficar conforme abaixo.
// NGContactDB.h
#import <Foundation/Foundation.h>
#import <sqlite3.h>
@class NGContact;
@interface NGContactDB : NSObject {
sqlite3 *contactDB;
NSString *databasePath;
}
- (void)createDatabase;
- (void)insertContact:(NGContact *)contact;
- (void)updateContact:(NGContact *)contact;
- (void)deleteContact:(NGContact *)contact;
- (NSArray *)getAllContacts;
@end
Como podemos observar, essa classe contém dois atributos, o primeiro é uma referência para sqlite3, e o segundo é o caminho do arquivo do banco. Além disso, temos também cinco métodos:
- createDatabase, que como o próprio nome diz, criará o banco caso ele não exista;
- insertContact, updateContact e deleteContact irão respectivamente inserir, atualizar e excluir um contato no banco;
- e getAllContacts irá obter todos os contatos cadastrados no banco.
Deixe o arquivo de implementação conforme abaixo:
#import "NGContactDB.h"
#import "NGContact.h"
#import <sqlite3.h>
@implementation NGContactDB
// Inicializa o banco -------------------------------
- (void)initDB {
// Obtém os diretório de arquivos da aplicação
NSArray *dirPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
// Obtém o diretório para salvar o arquivo do banco
NSString *docsDir = [dirPaths objectAtIndex:0];
// Cria o caminho do arquivo do banco
databasePath = [[NSString alloc] initWithString:
[docsDir stringByAppendingPathComponent:
@"contacts.db"]];
const char *dbpath = [databasePath UTF8String];
// Inicializa o atributo contactDB com o banco
if (sqlite3_open(dbpath, &contactDB) != SQLITE_OK){
NSLog(@"Failed to open/create database - 1");
}
}
// Cria a tabela no banco ---------------------------
- (void)createDatabase {
// Chama o método acima...
[self initDB];
char *errMsg;
const char *sql_stmt = "CREATE TABLE IF NOT EXISTS
CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT,
NAME TEXT, ADDRESS TEXT, PHONE TEXT)";
// Cria a tabela no banco se ela não existir
if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL,
&errMsg) == SQLITE_OK) {
NSLog(@"Database successfully created");
} else {
NSLog(@"Failed to create table");
}
sqlite3_close(contactDB);
}
// Inserir um contato no banco ---------------------
- (void)insertContact:(NGContact *)contact {
[self initDB];
char *sql = "INSERT INTO CONTACTS (NAME,ADDRESS,
PHONE) VALUES (?, ?, ?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(contactDB, sql, -1,
&stmt, nil) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1,
[contact.name UTF8String], -1, NULL);
sqlite3_bind_text(stmt, 2,
[contact.address UTF8String], -1, NULL);
sqlite3_bind_text(stmt, 3,
[contact.phone UTF8String], -1, NULL);
}
if (sqlite3_step(stmt) == SQLITE_DONE){
NSLog(@"Record added");
} else {
NSLog(@"Failed to add contact");
}
sqlite3_finalize(stmt);
sqlite3_close(contactDB);
}
// Atualizar contato no banco ----------------------
- (void)updateContact:(NGContact *)contact {
[self initDB];
char *sql = "UPDATE CONTACTS SET NAME = ?,
ADDRESS = ?, PHONE = ? WHERE ID = ?;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(contactDB, sql, -1,
&stmt, nil) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1,
[contact.name UTF8String], -1, NULL);
sqlite3_bind_text(stmt, 2,
[contact.address UTF8String], -1, NULL);
sqlite3_bind_text(stmt, 3,
[contact.phone UTF8String], -1, NULL);
sqlite3_bind_int(stmt, 4, contact._id);
}
if (sqlite3_step(stmt) == SQLITE_DONE){
NSLog(@"Record updated");
} else {
NSLog(@"Failed to update contact");
}
sqlite3_finalize(stmt);
sqlite3_close(contactDB);
}
// Excluir contato ---------------------------------
- (void)deleteContact:(NGContact *)contact {
[self initDB];
char *sql = "DELETE FROM CONTACTS WHERE ID = ?;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(contactDB, sql, -1,
&stmt, nil) == SQLITE_OK) {
sqlite3_bind_int(stmt, 1, contact._id);
}
if (sqlite3_step(stmt) == SQLITE_DONE){
NSLog(@"Record removed");
} else {
NSLog(@"Failed to removed contact");
}
sqlite3_finalize(stmt);
sqlite3_close(contactDB);
}
// Obter lista de objetos do banco -----------------
- (NSArray *)getAllContacts;
{
[self initDB];
NSMutableArray *contacts =
[[NSMutableArray alloc]init];
sqlite3_stmt *statement;
NSString *querySQL = [NSString stringWithFormat:
@"SELECT * FROM contacts"];
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(contactDB, query_stmt, -1,
&statement, NULL) == SQLITE_OK){
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSInteger contactId =
sqlite3_column_int(statement, 0);
NSString *nameField = [[NSString alloc]
initWithUTF8String:(const char *)
sqlite3_column_text(statement, 1)];
NSString *addressField = [[NSString alloc]
initWithUTF8String:(const char *)
sqlite3_column_text(statement, 2)];
NSString *phoneField = [[NSString alloc]
initWithUTF8String:(const char *)
sqlite3_column_text(statement, 3)];
NGContact *contact = [[NGContact alloc]init];
contact._id = contactId;
contact.name = nameField;
contact.address = addressField;
contact.phone = phoneField;
[contacts addObject:contact];
}
sqlite3_finalize(statement);
}
sqlite3_close(contactDB);
return [NSArray arrayWithArray:contacts];
}
@end
O código está comentado, mas vou fazer algumas observações. O método
initDB começa tentando obter o caminho do diretório de documentos, na qual a aplicação pode salvar seus arquivos. Em seguida monta o caminho do arquivo de banco de dados com a variável
databasePath. Por fim, utilizamos a função
sqlite3_open (isso mesmo, função, já que o sqlite é escrito em C) para abrir o arquivo de banco de dados e inicializar o atributo
contactDB.
Já o método
createDatabase criará a tabela Contatcts no banco de dados caso ela não exista. Esse método deve ser chamado apenas uma vez durante o ciclo de vida da aplicação. Pois, uma vez criada, a tabela permanecerá no banco. Esse método começa chamado o
initDB para inicializar o atributo
contactDB depois monta o SQL para criação da tabela. Note que esse SQL é montado com um char*, que é o tipo esperado pela biblioteca (e não NSString). Para executar o comando no banco, utilizamos a função
sqlite3_exec, passando a referência para o banco e a sentença SQL (para os demais parâmetros
olhe aqui).
Os métodos
insertContact,
updateContact e
deleteContact são bem similares. Chamamos o método
initDB para inicializar o atributo
contactDB. Depois declaramos um
sqlite3_stmt que é inicializado através da função
sqlite3_prepare_v2 que recebe a referência do banco, a instrução SQL e a referência para o statement. Cada uma das "?" será um parâmetro que será preenchida com um valor através da função
sqlite3_bind_* (onde * é o tipo do parâmetro que se quer passar). Para executar a instrução SQL, utilizamos a função
sqlite3_step, se tudo correr bem, será retornado SQLITE_DONE. Por fim, fechamos o statement e o banco com as funções
sqlite3_finalize e
sqlite3_close respectivamente.
O método que retorna uma lista de objetos
NGContact utiliza os mesmos conceitos dos métodos anteriores, a diferença é que para percorrer os registros retornados, a função
sqlite3_step retorna SQLITE_ROW quando temos uma linha para ler. E para ler essa coluna, utilizamos as funções
sqlite3_column_* (onde * é o tipo da coluna).
Bem pessoal. Da parte de SQLite é isso, em um próximo post vou mostrar como integrar essa classe com uma UITableViewController (para mostrar os dados) e criar uma tela para inserir e alterar os registros. Mas quem quiser ir se adiantando, pode usar esse post
aqui.
Qualquer dúvida, deixem seus comentários.
4br4ç05,
nglauber