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; } @endAqui 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; @endComo 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]; } @endO 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
Um comentário:
Parabéns muito bom seu post.
Fiz igual ta no post mas não funcionou sempre que executo da a mensagem "Failed to open/create database - 1" e quando vou criar a tabela aparece a mensagem "Failed to create table"
Postar um comentário