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
Parabéns muito bom seu post.
ResponderExcluirFiz 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"