curso iosx
Post on 13-Mar-2016
227 Views
Preview:
DESCRIPTION
TRANSCRIPT
Desenvolvimento de Apps para iOS
Índice de Conteúdo
Introdução
Pré-requisitos
A plataforma iPhone/iPod/iPad
Vantagens da plataforma iOS
Cocoa Touch Framework
Sobre o Apple Developer Program
iOS SDK
Xcode
Interface Builder
iOS Simulator
Hello World
Apresentando a interface
Criando a interface
Referências aos objetos da interface
Conexões da view x controller
Implementações
Refinando a mensagem de exibição
A linguagem de programação
Padrão MVC
Model
View
Controller
MVC no Objective-C
Estrutura e padrões do código
Declaração de funções e métodos
Declaração de métodos estáticos
Declaração de métodos com um objeto de retorno
Ciclo de vida de um objeto
Orientação a objeto em poucas linhas
Criando as classes em objC
Padrões de mensagens
NSLog
Variáveis e Objetos
Strings
NSString e NSMutableString
Formas de inicialização
Principais métodos
Números
Valores lógicos
Arrays
Principais métodos
Dicionários
Principais métodos
Objeto id
Condições
Laços de repetição
O loop "for"
Laços "while" e "do...while"
Getters, Setters e propriedades
Declaração de propriedades
Atributos das propriedades
Protocolos
Na prática
Configurando as variáveis de instância
Implementando a classe
Usando a classe
Exemplos de uso
Objetos de interface com o usuário
Sintaxe padrão para declarar um objeto no Xcode
Sintaxe padrão para declarar uma ação a um evento
Label, textfield, textview e botão
Na prática
Cabeçalhos
Implementações
Switch e Activity Indicator
Na Prática
Segmented Control e Image View
Na Prática
Criando o código
Slider e Progress View
Na prática
Criando o código
Classes relacionadas
UIColor
Cores pré-definidas
UIFont
UIImage
Windows e views
Métodos das views
Métodos do App Delegate
Navigation Bar
TabBar Controller
Propriedades
TableView Controller
Propriedades especiais nas células
Títulos da célula
Ícones de navegação
Modal views
Transições
Mensagens de alerta
Na prática
Configurando a Tab Bar
Incluindo a Tab Bar ao Window
Ícones
Modal Views
Subview a ser aberta
Declarando os métodos do ModalViews.h
Implementando os métodos no ModalViews.m
Alerts
View Padrão - SubView
ViewPadrao
Table View
Acesso a dados e sandbox
NSUserDefaults
Na prática
Acesso aos arquivos do sandbox
PLists
Na prática
Implementação
Core data
Na prática
Criando a tabela
TableListView Controller
Implementação da classe
Configurando a TableListView
Listando os registros
Implementando a navegação
Formulário
Implementando o formulário
Dados padrão nos campos
Salvando o registro
Excluindo o registro
Tornando o formulário visível
Hora de abrir o registro pela tabela
Resumindo
XML
Métodos do NSXMLParserDelegate
Webview
Operações com a webview
Classes relacionadas
Projeto final
Planejando o app
Mãos a obra!
Modelo de dados
Leitura dos feeds
Estrutura do RSS
Objetos relacionados
Implementações
Parser
Implementação e init
didStartElement:
foundCharacters:
didEndElement:
Título do feed
Fechamento da tag item
Dados dos posts
Fechamento da tag RSS
Verificação do cadastro do feed
Cadastro do feed
Verificação dos posts do feed
Cadastro dos posts
O código do bloco na íntegra
parse:
A view principal
View EditFeeds
Controller EditFeeds
Controller da lista de feeds
Implementação do controller
abreDados:
Configurações da tabela
View para lista de posts
Cabeçalhos
Implementação
Lista de posts
Configuração da tabela
Abrindo esta view
View de exibição do post
Cabeçalhos
Implementação
Formatação CSS
Exibindo a view pela lista de posts
A view da aba Novidades
Implementação do arquivo Novidades.m
Configurações da tabela
Refinamentos
Desafios propostos para melhorias
Considerações finais
Introdução (1)
Olá! Seja bem-vindo ao curso de desenvolvimento para iOS com objC do iMasters PRO. Neste
curso abordaremos temas que, até o final, você se tornará capaz de desenvolver boa parte dos
tipos de aplicativos que estão publicados na AppStore, sendo introduzido aos recursos e às
ferramentas necessárias.
Através de exemplos práticos e explicativos, você terá uma melhor noção de como funciona a
lógica das ferramentas e dos objetos, para que em pouco tempo, já esteja caminhando com as
próprias pernas, e claro, ganhando dinheiro com seus apps!
Pré-requisitos (2)
Para rodar o Xcode (plataforma de desenvolvimento para iPhone), é necessário ter um
computador da Apple com o MacOS X, pois ainda não há versão em outros sistemas
operacionais. Desejável que se tenha um dispositivo (iPhone ou iPod Touch ou iPad). Na
maioria dos testes é possível utilizar o simulador disponível no pacote de desenvolvimento.
Para realizar testes nos dispositivos, é necessário que se tenha a chave de desenvolvedor
acoplada (Provisioning profile).
Houve casos de pessoas que conseguem rodar o MacOS X em máquinas virtuais no Windows
sem problemas, tal instrução não será colocada aqui pois o procedimento pode variar de acordo
com a máquina e versão do MacOS, mas é possível achar facilmente na Internet os
procedimentos.
A plataforma iPhone/iPod/iPad (3)
Um ponto pode parecer muito óbvio agora, mas na hora de desenvolver, muita gente esquece: o
quesito de limitações de hardware. Vocês já devem ter ouvido falar, por aí, que “smartphone
não é um PC” ou algo do gênero. Pois bem, isso é bem verdade. O fato é que, apesar de ser algo
em teoria inferior, não impede que possamos fazer o melhor uso do que temos em mãos.
Na hora de projetar uma aplicação, pense nela na forma mais otimizada possível. Pense que, ao
invés de criar um aplicativo grande, com vários recursos que muitas vezes não serão utilizados,
considere a possibilidade de dividí-los em apps menores, com tarefas mais focadas.
Vantagens da plataforma Ios (4)
Única fonte de distribuição. Apesar de parecer algo monopolista e centralizador, facilita
muito na hora de gerenciar a venda do aplicativo, por deixar que um meio apenas zele pela
parte comercial do seu programa.
Público alvo, em boa parte, elitizado. Por ser um produto muito conhecido pelo status, boa
parte dos usuários de iOS não se importam em pagar por programas que, em sua maioria, têm
valores irrisórios.
Total compatibilidade com hardware. Salvo algumas exceções, como em atualizações e dos recursos de telefonia apenas disponíveis apenas para iPhone, no geral, não há preocupação com o hardware no qual o app será usado, justamente pela exclusividade da plataforma iOS estar presente em dispositivos móveis da Apple.
Cocoa Touch Framework (5)
Consiste numa biblioteca de APIs, objetos e runtimes que herdam a camada de
desenvolvimento do sistema operacional. No MacOS, trabalha-se com o Cocoa, que
engloba todos os mecanismos do sistema operacional para o desenvolvimento de
aplicações (janelas, menus, botões, etc), enquanto no iOS se utiliza do Cocoa Touch,
com o mesmo background do Cocoa. No entanto, com funções específicas para a
plataforma móvel, como multitouch, views, gps, acelerômetro, etc.
Hierarquia do Cocoa Touch Framework
Sobre o Apple Developer Program (6)
Até o momento, a única maneira de distribuir seus aplicativos oficialmente para os
usuários finais é através da AppStore da Apple. A inscrição é um processo um pouco
burocrático e demorado, sendo necessário o envio de cópias dos seus documentos via
fax para a Apple. Além disso, todo aplicativo submetido deverá passar pelo controle de
qualidade antes de ser publicado.
A taxa anual do programa é de US$ 99,00 para o programa padrão, que permite a
distribuição na AppStore. Há um programa para empresas que desejam desenvolver
apps in-house, sem distribuição pública (na AppStore). Para a inscrição deste programa,
o valor anual é de US$ 299,00 e a empresa deverá comprovar o endereço nos EUA, o
que já elimina boa parte dos aspirantes ao programa.
Maiores informações, visite o site do programa http://developer.apple.com/ iOS SDK (7)
O iOS SDK contém um conjunto completo de ferramentas para desenvolvimento em
iOS, para Mac e tudo que esteja relacionado a Apple. No nosso curso, vamos focar
apenas nas ferramentas que diz respeito ao iOS. São elas: o Xcode, Interface Builder e o
iOS Simulator.
Você pode adquirir o iOS SDK gratuitamente no site
http://developer.apple.com/xcode/index.php. É necessário que se faça um cadastro antes
de baixá-la. Xcode (8)
É onde todo código relacionado ao seu app será feito. Todos os frameworks, classes,
arquivos adicionais, XIBs, tudo estará concentrado neste programa, além da compilação
e execução.
Interface padrão do Xcode 4
Interface Builder (9)
Toda a parte visual do seu app será desenvolvido no Interface Builder. Nele você
encontra todo o conjunto de ferramentas e objetos herdados do Cocoa Touch
Framework. Os arquivos gerados nele (NIBs) não contém nenhum código acoplado,
apenas a disposição dos elementos da interface.
Na versão 3 do SDK, o IB ainda é um programa à parte. Na versão 4, ele já se torna
único com o Xcode. Neste curso, o foco será dado à versão 4.2.
Apesar dos arquivos do Interface Builder (IB) serem chamados de NIB (NextStep
Interface Builder), a extensão é .xib, pois atualmente se usa do XML para a formatação.
Por razões históricas, mantém-se esta nomenclatura.
Entenda as janelas do IB
Janela de Projeto
Este é o gerenciador de projeto. Centraliza todos componentes necessários para a
criação da interface. Por padrão, os três primeiros ícones estarão incluídos no projeto
(File’s owner, First responder e o Controller), que são fundamentais para a integração
com o código. O objeto Window já corresponde à parte visual, sendo todos os
elementos gráficos subordinados a ele.
Library
A Library contém essencialmente os objetos para serem usados. Podemos compará-la
com uma caixa de ferramentas, onde tudo está disponível para o uso.
View
Esta é a sua área de trabalho, sua tela onde será desenhada a interface gráfica do seu
app. Cada agrupamento de objetos dentro da janela (window), denomina-se view.
Inspector
O Inspector permite a personalização dos objetos inseridos na view. Toda a
configuração, desde a parte visual até a definição dos métodos é feita nesta tela.
Estudaremos com mais detalhes adiante
iOS Simulator (10)
Para testar seu aplicativo sem a necessidade de um iPhone ou iPad, você pode usar o
iOS Simulator, que engloba a maioria das funções do dispositivo móvel. Claro que, para
um teste mais apurado e fiel, o iPhone é mais recomendado, mas para a maioria dos
casos, o iOS Simulator atende bem.
iPhone Simulator
iPad Simulator
Hello World (11)
Seguindo os protocolos de boas maneiras para o aprendizado de uma linguagem de
programação, vamos quebrar o gelo com o Hello World.
Inicie um novo projeto no XCode. Se estiver na tela inicial, escolha Create New
XCode Project, ou vá em File -> New Project ou aperte Cmd + shift + N.
Tela inicial do XCode
Tenha certeza que você está em iOS/Application. Escolha Single View Application.
Em product tenha certeza que tenha escolhido iPhone. Clique em choose e dê o nome
de PrimeiroApp.
Importante, para todos os exemplos adotados neste curso, a opção USE
STORYBOARD deve está desmarcada.
Novo projeto
Apresentando a interface (12)
A seguir, você tem a área de trabalho do XCode. À esquerda, você verá os arquivos
com as classes, frameworks, Supporting Files e etc. E, à direita, a área no qual você vai
editar estes arquivos.
XCode com o projeto aberto
Em Supporting Files, clique duas vezes em ViewController.xib. Este arquivo contém a
view no qual a nossa interface será criada. Ao fazer isso, abriremos o Interface Builder.
Criando a interface (13)
Abra o arquivo ViewController.xib e verás o Interface Builder.
Deixaremos o fundo da view branca, para isso, no Inspector, mudaremos a cor do fundo
de acordo com a imagem abaixo.
Na lista de objetos, clique e arraste um objeto Label para dentro da view. Dê um clique
duplo sobre ela e coloque o texto Digite seu nome:.
Faça o mesmo com o objeto TextField. Coloque abaixo do Label. Redimensione-o pela
lateral.
Insira agora um botão abaixo do TextField. Dê um clique duplo e coloque o texto Diga
olá!
E por fim, insira mais um label abaixo do botão. Redimensione-o para que que ocupe a
largura da view e altere a cor para vermelho. Este label responderá ao evento do botão.
E pronto! agora precisamos de fazer as conexões entre os elementos ao controller, para
que o nosso app possa funcionar.
Referências aos objetos da interface (14)
Agora, de volta ao XCode. Abra o arquivo ViewController.h. Neste arquivo vamos
criar as referências aos objetos criados na view, para que possamos passar os eventos e
recolher as propriedades.
Logo abaixo do @interface, vamos declarar o label que vai receber a mensagem e o
textfield no qual vai passar o valor para ser processado.
IBOutlet UILabel *mensagem;
IBOutlet UITextField *nome;
E logo abaixo, depois das {}, vamos declarar o método que vai receber o evento do
toque sobre o botão.
-(IBAction) dizerOla;
O código no final deverá estar assim:
@interface ViewController : UIViewController
{
IBOutlet UILabel *mensagem;
IBOutlet UITextField *nome;
}
-(IBAction) dizerOla;
@end
Conexões da view x controller (15)
Abra o ViewController.xib e tenha a janela Project a vista. Clique com o botão direito
no File's Owner, arraste e solte sobre o TextField. Um menu suspenso aparecerá.
Escolhe o nome.
Faça o mesmo com label Olá. Marque a opção mensagem.
Agora vamos atribuir o evento dizerOla: ao botão. Mas neste caso, faremos o caminho
inverso, clique com o botão direito sobre o botão e arraste sobre o File's Owner.
Escolha o método dizerOla:.
Pronto, todas as conexões estão feitas entre o View e o Controller.
Implementações (16)
Feitas as conexões, vamos ao código. O que fizemos antes no arquivo .h foi a
declaração dos cabeçalhos. A partir dele, precisamos implementar a classe. Faremos no
arquivo ViewController.m.
Logo abaixo do @implementation, colocaremos o método que declaramos
anteriormente:
-(IBAction) dizerOla
{
}
O nosso objetivo ao acionar o botão Diga Olá, é que o label mensagem receba a
mensagem que foi escrita no textfield. Isso se fará com a seguinte linha, que será
inserida dentro das chaves { }
-(IBAction) dizerOla
{
[mensagem setText:[nome text]];
}
Sendo:
mensagem e nome: os IBOutlets declarados e ligados a view.
setText: comando para alterar a propriedade text do label mensagem.
Execute e verá como fica
Execução
Refinando a mensagem de exibição (17)
Podemos melhorar um pouco a exibição da mensagem, já o que o botão está dizendo
“Diga olá!”, vamos fazer o label atender desta forma.
[mensagem setText:[NSString stringWithFormat:@"Olá %@!", [nome text]]];
Sendo:
NSString: objeto de manipulação de Strings.
stringWithFormat: método para formatar strings com caracteres curinga. Estes são
representados pelo sinal % e seguido do símbolo que representa o tipo.
%@ : caracter curinga que representa a saída de qualquer objeto.
Execute e veja como ficou!
A linguagem de programação (18)
Apesar de existirem vários frameworks de desenvolvimento para iOS alternativos,
como para HTML5, Flash e afins, a linguagem padrão, e que na prática roda as
aplicações, é o Objective-C. Esta linguagem, apesar de parecer um pouco estranha no
começo, com a prática se torna de simples compreensão e que com poucas linhas de
código você fará bastante coisa.
O Objective-C (também chamada de objC) é uma linguagem de programação simples
com sofisticados meios de orientação a objeto. Pode ser definida como um conjunto de
extensões do ANSI C, a fim de dar ao C uma completa implementação de orientação a
objetos, numa maneira mais simples e direta.
Padrão MVC (19)
O Modelo MVC (Model, View, Controller) é um padrão que isola a interface gráfica
(view) do núcleo do sistema (model), permitindo que ambas as partes possam ser
desenvolvidas independentes de uma da outra, além de ter um melhor re-
aproveitamento do código. O controller faz o papel de ponte entre o model e a view.
Padrão MVC
Model (20)
Gerencia os dados e estados do app
Sem preocupação com a interface gráfica, apenas os dados
Funcionamento persistente
O mesmo modelo pode ser reutilizado em várias interfaces diferentes
View (21)
Mostra ao usuário os dados contidos no Model
Permite a manipulação dos dados
Não salva dados (no caso do iOS, a exceção está no cache state, que veremos com detalhes no
capítulo sobre views)
Reutilizável e configurável para exibr diferentes tipos de dado de maneira simples
Controller (22)
Intermedia o Model e a View
Atualiza a View quando há alteração no Model
Atualiza o Model quando o usuário manipula a View
Geralmente é onde está a lógica do app
MVC no Objective-C (23)
Vejamos agora como funciona na prática o padrão MVC na hora de programar seu app.
No esquema abaixo, vemos que o Model Object, substrato do programa, se comunica
diretamente ao Controller através do Outlet, que se comunica com a View diretamente
ao objeto alvo.
No caminho de volta, vemos que a View, se comunica com o Controller, pelas Actions,
que corresponde a algum método de interação com o usuário.
Outlet: é a representação do objeto. Em algumas linguagens de programação, seria o
parâmetro name. Em código, este é declarado como IBOutlet.
Action: declaração de um evento associado a um método, por exemplo, a chamada de
uma função ao clicar num botão. Em código, é declarado como IBAction.
Esquema MVC no Objective-C
2 - A linguagem de programação
2.1 - Padrão MVC
2.2 - Estrutura e padrões do código o 2.2.1 - Declaração de funções e métodos
o 2.2.2 - Declaração de métodos estáticos
o 2.2.3 - Declaração de métodos com um objeto de retorno
o 2.2.4 - Ciclo de vida de um objeto
2.3 - Orientação a objeto em poucas linhas
2.4 - Criando as classes em objC
2.5 - NSLog
2.6 - Variáveis e Objetos
2.7 - Condições
2.8 - Laços de repetição
2.9 - Getters, Setters e propriedades
2.10 - Protocolos
2.11 - Na prática
3 - Objetos de interface com o usuário
Estrutura e padrões do código (24)
No objC há algumas diferenças nas notações de sintaxe, algo que a princípio pode parecer
estranho, mas você verá que não há grandes mistérios, além do que em pouco tempo já estará
familiarizado.
Declaração de funções e métodos (25)
Toda função e/ou método do objeto é declarado seguindo a seguinte sintaxe:
- (tipo_de_retorno) funcao {
// ... instruções
}
Exemplos
-(id)init;
-(float)altura;
-(void)executar;
O sinal de menos “-” no início da declaração, indica ao compilador que este método é do
objeto, ou seja, é válido quando o objeto é instanciado.
Função com argumentos
Caso deseje declarar uma função com parâmetros, segue a sintaxe:
-(tipo_de_retorno) funcaoComParametro1: (tipo_do_param)varParam1
parametro2:(tipo_do_param)varParam2 {
// instrucoes
}
Exemplos
-(id)initWithName:(NSString *) nome;
-(float)calculaAreaQuadradoComLado1:(float) lado1 lado2:(float) lado2;
Alguns tipos de variáveis mais usados
void a função não exige retorno
id representa qualquer objeto
int a variável de retorno é inteiro
Métodos estáticos são aqueles que podem ser invocados pela classe, sem precisar instanciar o
objeto.
A estrutura para para declará-los é a mesma das citadas anteriormente. O que diferencia é o
sinal da declaração: no lugar do sinal de menos “-” usa-se o sinal de mais “+”.
+ (id)alloc
Para chamar um método estático, segue-se a seguinte estrutura:
[ClasseDoObjeto metodoEstatico];
Declaração de métodos estáticos (26)
Métodos estáticos são aqueles que podem ser invocados pela classe, sem precisar instanciar o
objeto.
A estrutura para para declará-los é a mesma das citadas anteriormente. O que diferencia é o
sinal da declaração: no lugar do sinal de menos “-” usa-se o sinal de mais “+”.
+ (id)alloc
Para chamar um método estático, segue-se a seguinte estrutura:
[ClasseDoObjeto metodoEstatico];
Declaração de métodos com um objeto de retorno (27)
Nos exemplos anteriores, você pode ter notado um * logo após o tipo do objeto. No objC, todo
objeto é declarado como ponteiro dentro de uma função.
-(NSObject *)estaFuncaoRetornaUmObjeto {
// Instrucoes
return objeto;
}
O NS (abreviação de Next Step) sinaliza que o objeto é original do objC. Os demais são
herdados do C.
Alguns tipos especiais do Interface Builder
IBAction indica a declaração de um método. Tem o mesmo valor do void, com a diferença de
que desta forma, o Interface Builder vai reconhecer que a função a seguir é um método a ser
acoplado a um objeto da interface gráfica (UIKit).
-(IBAction)clickNoBotao:(id)sender;
IBOutlet indica a declaração de um objeto da interface gráfica. Serve apenas para que o
Interface Builder reconheça no código. Não é necessariamente um tipo de objeto, apenas uma
flag.
IBOutlet UILabel *lblNome;
Ciclo de vida de um objeto (28)
A linguagem objC é totalmente orientada ao objeto. Logo, precisamos entender como funciona
o ciclo de vida do objeto, desde quando são instanciados.
Alocação e inicialização
Todo objeto, quando instanciado, deve estar alocado numa variável e em seguida inicializado.
A maneira mais simples de se inicializar segue-se na sintaxe:
NomeDaClasse *variavel = [[NomeDaClasse alloc] init];
Por padrão, todo objeto é declarado como ponteiro, incluindo o asterisco na frente da
variável. Esta notação do ponteiro é herança da linguagem C, que representa o endereço físico
da variável na memória.
Esta é a forma mais comum de se inicializar um objeto. Há objeto que pode-se passar
parâmetros na inicialização. O melhor exemplo é na variável NSString:
NSString *resultado = [[NSString alloc] initWithString:@?Texto da variável?];
Estas formas de inicialização são particulares de cada objeto.
Nota de atualização para o iOS5: a partir desta versão, um novo mecanismo denominado
ARC (Automatic Reference Counting) no qual faz o gerenciamento de memória é por conta do
compilador e não mais pelo usuário. Portanto, considere as informações a seguir apenas se for
desenvolver para versões anteriores do iOS (o que não é recomendado). Ao longo do curso, o
foco será dado ao iOS5, logo, informações como release/retain não serão usados.
Dealocação
Para limpar o objeto da memória, usa-se o método release no objeto.
[resultado release];
Você pode usar este comando quando não for mais necessário o objeto. Se o objeto for uma
variável de instância, este poderá ser dealocado num método especial dentro do objeto que
esteja trabalhando:
-(void) dealloc {
[resultado release];
[super dealloc];
}
Desta forma, quando o objeto pai for dealocado, todos os que estiverem contidos nesta função,
serão liberados em fila. Por padrão, toda clase tem o método dealloc a ser implementado.
[resultado autorelease];
Retenção
Uma vez que você instancia o objeto, pode ser que seja necessário garantir que o objeto persista
na memória. Neste caso, usa-se o método retain.
Seguindo o exemplo da string criada anteriormente, a sintaxe funcionaria da seguinte maneira:
[resultado retain];
Imaginemos um contador acoplado ao objeto. Toda vez que um objeto é instanciado, este
contador recebe o valor 1. A cada retain invocado no objeto, este contador soma 1 e a cada
release, este contador diminui 1. Quando este contador chega a zero, o objeto é liberado da
memória.
Ciclo de vida de um objeto
Orientação a objeto em poucas linhas (29)
Classe ( concentra as variáveis e códigos. É o código do objeto a ser instanciado propriamente
dito. O nome da classe determina o nome do tipo de objeto.
Objeto ( é a declaração de uma instância de uma classe.
Instância ( é a alocação de uma classe na memória. Uma classe pode ter várias instâncias de
um objeto.
Método (: é a função acoplada a um objeto. Toda mensagem e instrução enviada ao objeto, se
faz por intermédio de um método.
Variável de instância (: um pedaço específico de dados que pertence a um objeto.
Encapsulamento ( mantém a implementação privada e separada da interface.
Polimorfismo ( capacidade de uma mesma classe possuir diversas instâncias distintas de
objetos.
Herança ( possibilita que um novo objeto herde funcionalidades de um outro objeto.
Mensagem ( forma de troca de dados com um objeto.
Criando as classes em objC (30)
Chega de teoria! Vamos agora ao que interessa: como criar as classes no objC.
Esta parte introdutória é super importante para que vocês entendam todo o background da
linguagem de programação, facilitando o entendimento.
Arquivo .h
O arquivo .h (header) contêm todas as declarações de variáveis e métodos da classe. Utliza-se
nesta declaração os métodos que se tornarão públicos, ou seja, os métodos acessíveis na
instância do objeto.
Exemplo
#import <Foundation/Foundation.h>
@interface Objeto : NSObject {
NSObject *exObjeto;
}
@property (nonatomic, retain) NSObject * exObjeto;
-(void)exemploDeMetodo;
@end
Fique tranqüilo que a estrutura básica é gerada automaticamente pelo Xcode.
Arquivo .m
Contém a implementação da classe. No .h foram apenas declaradas as variáveis e funções, mas
não há nenhuma implementação.
Seguindo o exemplo anterior:
#import "Objeto.h"
@implementation Objeto
@synthesize exObjeto;
-(void) exemploDeMetodo
{
// implementação
}
@end
Não se preocupe se não entendeu o que está escrito. Tudo isso será explicado em breve.
Padrões de mensagens (31)
Já vimos anteriormente como se declaram métodos e, nos exemplos antes mostrados, já deve se
tem uma noção de como chamá-los de maneiras mais detalhadas.
[objeto mensagem];
A declaração mais simples de invocação de um método. A notação entre colchetes [ .. ] é a
forma padrão do envio de mensagens ao objeto.
Imagine que o método acima chamado, esteja contido numa classe chamada objeto e que tenha
a seguinte implementação:
-(void)mensagem;
Mensagens com mais de um argumento
[objeto mensagem1:argumento1 mensagem2:argumento2 ...];
Desta vez, argumentos são passados na chamada do método, podendo ser um ou mais valores,
dependendo de como foi construída a função. Imaginemos a implementação deste método:
-(void)mensagem1:(int)argumento1 mensagem2:(float)argumento2;
Notação por ponto
Desde a versão 2.0 do objC, foi introduzida a notação por ponto para invocar métodos de um
objeto, como acontece na maioria das linguagens de programação. Ambas as sintaxes estão
corretas, cabe a você escolher qual é a mais confortável.
Desta vez, vamos com um exemplo prático. Suponha que temos um objeto chamado pessoa,
que nele vamos definir e receber os valores da altura.
float alt = [pessoa altura];
float alt = pessoa.altura
Em ambos os exemplos, foi declarada a variável alt como float e na inicialização já recebera o
valor da variável de instância altura.
Para chamar os métodos de um objeto passando valores, segue-se os exemplos:
[pessoa setAltura:novaAltura];
pessoa.altura = novaAltura;
Neste caso, o objeto pessoa está recebendo o valor novaAltura. Nota-se uma pequena diferença
acima, na notação de colchetes, há um antes da variável. Esta é uma notação padrão do objC
para declaração de métodos getters e setters. Veremos com mais detalhes quando estudarmos as
declarações de propriedades.
NSLog (32)
Retomando o que vimos na introdução, usamos este comando que dá saída no console. Este
comando é de extrema utilidade, principalmente na hora do debug.
NSLog(@"mensagem de saída");
Esta é a forma mais simples de dar saída a uma mensagem. O "@" antes da mensagem significa
que a sequência é do tipo NSString.
NSLog(@"o valor inteiro é %d, o float é %f e o NSString é %@", varInt, varFloat,
varNSString);
Este exemplo ilustra como concatenarmos variáveis dentro de uma string. No lugar de %d
(para int), %f (para float) e %@ (para NSString), teremos estes curingas substituídos pelos
valores de varInt, varFloat e VarNSString.
Variáveis e Objetos (33)
Veremos agora os objetos e variáveis mais comuns e suas principais funções.
Strings (34)
Toda seqüência alfanumérica determina-se String. Originalmente, no C, a variável era definida
pelo tipo char. Entretanto, há algumas limitações, como por exemplo, a falta de suporte a
algumas codificações. Neste caso, surge o tipo NSString e sua subclasse, NSMutableString.
Veremos sobre elas agora.
NSString e NSMutableString (35)
Inicializa uma variável string com valor imutável, ou seja, uma vez que um valor é associado,
não poderá ser alterado. Caso deseje que a string possa ter o valor alterado, usa-se a subclasse
NSMutableString, que instancia uma variável ainda do tipo String, com as mesmas funções e
métodos, porém, com valor alterável. Vale lembrar que todos os métodos abaixo listados
servem para ambas as classes.
Para inicializar um NSString:
NSString *imutavel = [[NSString alloc] init];
E um NSMutableString:
NSMutableString *mutavel = [[NSMutableString alloc] init];
Para atribuir um valor a um NSString:
imutavel = @"valor que uma vez adicionado não poderá ser alterado";
mutavel = @"valor que poderá ser alterado em qualquer momento";
Formas de inicialização (36)
-(id) initWithString:
Inicializa a string com um valor acoplado.
NSString *exemplo = [[NSString alloc] initWithString:@"valor inicial"];
-(id) initWithFormat:
Inicia a string com um valor com variáveis concatenadas.
int valorinteiro = 15;
NSString *exemplo = [[NSString alloc] initWithFormat:@"o valor inteiro = %d", valorinteiro];
Principais métodos (37)
UTF8String
Retorna o valor da string compatível com o tipo char do C.
char texto = [obj_nsstring UTF8String];
intValue
Retorna o valor do tipo int, se a string tiver conteúdo numérico.
int valor_inteiro = [obj_nsstring intValue];
isEqualToString
Retorna um valor bool (true ou false / YES ou NO) ao comparar duas strings
NSString *string1 = [[NSString alloc] initWithString:@"abcdef"];
NSString *string2 = [[NSString alloc] initWithString:@"ghij"];
BOOL result1 = [string1 isEqualToString:string2]; // result1 = NO
BOOL result2 = [string1 isEqualToString:@"abcdef"]; // result2 = YES
length
Retorna o número de caracteres unicode contidos na string.
NSString *variavel = [[NSString alloc] initWithString:@"ABCDEF"];
int quantidade = [variavel length]; // quantidade = 6
Números (38)
Os tipos numéricos mais usados no objC o int e o float, ambos herdados do C.
int admite valores inteiros.
float admite valores fracionários.
Formas de declaração:
int variavel_inteira;
float variavel_fracao;
int contador = 1;
float pi = 3.1415;
No objC existe também o objeto NSNumber, que seria uma versão objeto que engloba vários
tipos de valores numéricos.
Valores lógicos (39)
Os tipos lógicos (booleanos) admitem valores de verdadeiro ou falso. No objC, estes valores
podem ser denominados como true e false ou como YES e NO. Ambas as representações são
idênticas.
Para declarar uma variável lógica:
BOOL variavel;
O tipo BOOL é herdado do C, logo não tem valor de objeto, não sendo necessária a declaração
como ponteiro.
Arrays (40)
Assim como o NSString, O NSArray instancia um objeto array com valores já definidos, sem
poder ser alterado, ao contrário do NSMutableArray, que poderá ter o conteúdo alterado.
Para inicializar um array:
NSArray *arr = [[NSArray alloc] initWithObjects:@"obj1", @"obj2"
, @"obj3", nil];
NSMutableArray *mArr = [[NSMutableArray alloc] init];
Sempre que for inicializar um array com uma série de objetos, nunca se esqueça de colocar o
terminador nil.
Principais métodos (41)
count
Retorna o número de objetos que contém num array.
int qtd = [arr count]; // retornará 3
objectAtIndex:
Retorna um objeto do tipo id a partir de um índice. O primeiro item do array está no índice 0.
id linha2 = [arr objectAtIndex:1]; // Retorna ao objeto NSString com o valor obj2
Sobre o tipo id explicaremos a seguir.
Métodos exclusivos do NSMutableArray
addObject:
Insere um novo registro no array.
[arr addObject:@"obj4"]; // Adiciona a quarta linha com o objeto NSString com o valor "obj4"
insertObject:atIndex:
Insere um novo registro no array num índice determinado.
[arr insertObject:@"entre 2 e 3" atIndex:1];
Se o índice já estiver ocupado, os objetos, a partir do ponto indicado, moverão para um nível
acima, para dar espaço ao novo objeto inserido.
removeObjectAtIndex:
Remove o objeto do array localizado no índice.
[arr removeObjectAtIndex:2];
Dicionários (42)
Similar aos arrays, temos o NSDictionary e o NSMutableDictionary que são objetos que
também enumeram valores vetoriais, mas neste caso, você pode determinar valores nos índices,
ao invés destes serem numerados.
NSDictionary *dict = [[NSDictionary alloc] initWithObjects:(array) forKeys:(array)];
Neste caso, o número objetos em ambos arrays devem ser iguais.
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"valor1", @"chave1",
@"valor2", "chave2",....,nil];
Este caso é bem similar ao primeiro método, com a diferença de que os valores estão
enumerados aos pares, seguindo respectivamente o valor e a chave, com o terminador nil.
Principais métodos (43)
count
Funcionamento idêntico ao NSArray, inclusive na sintaxe.
allKeys
Retorna um NSArray com todas as chaves contidas num NSDictionary.
NSArray *keys = [dict allKeys];
allValues
Retorna um NSArray com todos os valores contidos num NSDictionary.
NSArray *values = [dict allValues];
Métodos exclusivos do NSMutableDictionary
setObject:forKey:
Insere um novo objeto no NSDictionary.
[dict setObject:@"valor" forKey:@"nome da chave"];
removeObjectForKey:
Remove o objeto do NSDictionary.
[dict removeObjectForKey:@"nome da chave"];
Objeto id (44)
Este objeto é um caso especial, pois pode assumir todos os tipos de valores no escopo do
objeto. Apesar ser um objeto do objC, não se declara como ponteiro, a não ser que você
realmente precise de tal declaração, mas no uso comum, não é utilizado.
Condições (45)
Como em qualquer linguagem de programação usamos a estrutura if... else
if (condição) {
...
}
else if (condição) {
...
}
else {
...
}
Nas condições, usa-se a seguinte notação para comparação:
== -> igual
!= -> diferente
>, <, >=, <= maior, menor, maior ou igual, menor ou igual
Laços de repetição (46)
O objC usa basicamente três estruturas de repetição: for, while, e do..while.
O loop “for” (47)
Estrutura
for (valor inteiro inicial, condição, expressão do loop)
{
...
}
Exemplo:
for (int x=1; x<10; x++)
{
NSLog(@"O valor de x está em %d", x);
}
Neste caso, o laço se seguirá de 1 em 1 com a variável x, que se iniciará no valor 1 e terminará
no 9.
Loops infinitos
for (;;)
{
// ....
}
Interrompendo um loop for
for (int x=1; x<100; x++)
{
// ....
break;
}
Laços “while” e “do…while” (48)
Estrutura "while"
while (condição)
{
//..
}
Se a condição for verdadeira, executa o bloco repetidamente até se tornar falsa. Se a condição
desde o princípio já for falsa, o bloco não é executado.
Exemplo
int x = 1;
while (x<10)
{
//...
x++;
}
Estrutura "do... while"
do
{
//...
} while (condicao)
Segue a mesma lógica da estrutura anterior, com a diferença de que, antes de avaliar a
condição, o bloco é executado.
Getters, Setters e propriedades (49)
Como toda boa linguagem de programação OOP, uma das características dos objetos que deve
ser mantida e respeitada é a do encapsulamento, que implica a não-permissão de acesso às
variáveis de instância diretamente a elas, mas sim, por meio de métodos que são denominados
getters (que recupera o valor) e setters (que determina um valor).
Felizmente, o objC, neste caso, trabalha de uma forma muito simplificada, ao ponto de fazer
parte deste trabalho para você. Para isto, declaramos uma variável/objeto como propriedade.
Declaração de propriedades (50)
No arquivo .h, você já deve ter visto a instrução em outros exemplos:
@property (nonatomic, retain) NSString *exemploPropriedade;
E no arquivo .m
@synthesize exemploPropriedade;
Pois bem, estas instruções nada mais fazem do que declarar dois métodos para o objeto, que são
exemploPropriedade (getter) e o setExemploPropriedade (setter).
Por padrão, a primeira letra do nome do objeto passa a ser maiúscula na declaração do setter.
Logo, supondo que estejamos trabalhando na classe chamada Objeto, o código ficaria assim:
// Aloca e inicaliza o objeto
Objeto obj = [[Objeto alloc] init];
[obj setExemploPropriedade:@"valor"];
NSObject retorno = [obj exemploPropriedade];
Atributos das propriedades (51)
E quanto ao nonatomic e retain que estão entre parênteses? São chamados os atributos das
propriedades, o que determina como o compilador vai lidar com os objetos na hora de gerar o
getter e o setter. Os principais são:
readwrite: quando você quer que a propriedade seja modificável. O compilador irá gerar
um getter e um setter. É o padrão, caso não declare nada.
readonly: quando você não deseja que o valor seja alterável. O compilador não irá gerar
um setter, apenas um getter.
assign: usado quando você estiver lidando com tipos básicos do C (int, float, etc.). O
compilador irá gerar um setter para estes valores. É o padrão, mas não é o mais usual de
todas as opções.
retain: é usado quando se trabalha com objetos. Ao receber um novo valor, o objeto irá
se assegurar que o valor seja mantido até que um novo chegue.
copy: quando você quer uma cópia do valor passado ao invés do valor por si só.
nonatomic: assegura que o objeto terá um controle na hora de receber e enviar novos
valores, evitando crash no app no tempo de execução.
Em 99% dos casos, quando for trabalhar com objetos, acredite na combinação (nonatomic,
retain). É sem dúvida a mais usada e atende a todas as necessidades.
Protocolos (52)
Toda classe no objC é herdada de uma única classe que, por seguinte, esta é herdada de outra,
até chegarmos no NSObject. Mas e quando precisamos herdar métodos de outros objetos que
não estão nesta hierarquia? Neste caso, usamos os protocolos.
Os protocolos são declarações que fazemos dentro do cabeçalho, logo após a declaração da
interface, entre <>, como no exemplo abaixo:
@interface QualquerViewController : UIViewController <UITableViewDelegate,
UITableViewDataSource>
Esta declaração vai herdar os métodos das classes declaradas dentro dos <>. A melhor
aplicação disso poderemos ver nos próximos capítulos.
Na prática (53)
Vamos entender agora como funciona, na prática, a criação e a declaração de objetos.
Inicie um novo projeto (File > New project ou Cmd Shift N) no Xcode. Escolha o Single View
Application como nome de TesteClasses.
Na lista Groups & Files à esquerda, clique na aba Classes. Ali se encontram dois grupos de
arquivos: ~AppDelegate e ~ViewController. Como não trabalharemos a parte visual neste
projeto, mexeremos agora apenas no AppDelegate.
Neste exemplo criaremos uma classe simples que armazenará uma relação de nomes e idade,
apenas para ilustrar o funcionamento da classe. Demonstraremos também como se declara um
método e um construtor.
Crie um novo arquivo (File> New> File... ou Cmd N) e na guia iOS > Cocoa Touch, escolha
Objective-C Class e na subclass escolha NSObject. Clique em NEXT.
Em File Name, coloque Contatos.m e certifique-se que “Also Create Contatos.h” esteja
selecionado. Clique em FINISH.
Se já não estiver assim, arraste os dois arquivos criados para o grupo Classes, conforme a
imagem abaixo:
Configuração do XCode
Configurando as variáveis de instância (54)
Para este exemplo, vamos trabalhar com dois campos: nome e idade. No arquivo Contatos.h,
deverá ficar assim:
@interface Contatos : NSObject {
NSString *nome;
NSNumber *idade;
}
@property (nonatomic, retain) NSString *nome;
@property (nonatomic, retain) NSNumber *idade;
-(id)initWithName:(NSString *)n age:(NSNumber *)i;
-(BOOL)isMaiorDeIdade;
Explicação do código
O código começa com a declaração do cabeçalho do objeto, definindo o nome da classe e qual é
a classe-pai.
@interface Contatos : NSObject
Dentro das chaves, estão declaradas as variáveis de instância do objeto. No caso do nome,
colocamos do tipo NSString, e a idade do tipo NSNumber. Não declaramos aqui do tipo int pois
é mais fácil e direto trabalharmos com classes ao invés dos tipos do C.
NSString *nome;
NSNumber *idade;
Logo abaixo, criamos as propriedades para tornar as variáveis de instância acessíveis a outros
objetos. Como já vimos anteriormente, a chamada @property declara automaticamente um
getter e um setter para um objeto.
@property (nonatomic, retain) NSString *nome;
@property (nonatomic, retain) NSNumber *idade;
Em seguida determinamos os métodos acessíveis ao objeto. O initWithName determina que o
objeto deve ser inicializado com os parâmetros Nome e Idade. Apesar da nomenclatura em
inglês nesta declaração, não precisa ser necessariamente esta e com as mesmas variáveis. E
logo depois o método de exemplo que determina se a pessoa é maior de idade ou não.
-(id)initWithName:(NSString *)n age:(NSNumber *)i;
-(BOOL)isMaiorDeIdade;
Implementando a classe (55)
Agora no arquivo Contatos.m, primeiro vamos colocar pra funcionar as propriedades
declaradas. Abaixo do @implementation Contatos, insira o código a seguir:
@synthesize nome, idade;
Esta declaração implementa os métodos de entrada e saída dos objetos. Ou seja, um método
chamado nome (getter) e um chamado setNome (setter) acaba de ser criado sem ter de
propriamente criá-los. O mesmo acontece com o idade.
Em seguida, vamos declarar o construtor, conforme declaramos no .h:
-(id)initWithName:(NSString *)n age:(NSNumber *)i {
// certifica-se que você esteja trabalhando na superclasse
if (self = [super init]) {
// Define as variáveis de instância de acordo com os parâmetros
self.nome = n;
self.idade = i;
}
return self;
}
Neste caso, o self corresponde a própria instância da classe. É o equivalente ao this na maioria
das linguagens de programação.
E em seguida, o método usado como exemplo:
-(BOOL)isMaiorDeIdade {
if ([[self idade] intValue]>=18) {
return YES;
}
else {
return NO;
}
}
Neste método, na própria declaração já avisa que ele vá retornar um tipo BOOL (YES/true ou
NO/false).
-(BOOL)isMaiorDeIdade
Dentro da condição, chamamos a ivar idade, que é do tipo NSNumber, e através do método
intValue, foi retornado o valor com o tipo int.
[[self idade] intValue]
Na condição, se a idade for maior ou igual a 18, retorna YES, senão, retorna NO.
Usando a classe (56)
Utilizaremos a classe AppDelegate, que contem todos os métodos de inicialização do
aplicativo.
No arquivo AppDelegate.h, abaixo do #import <UIKit/UIKit.h>, digite:
#import "Contatos.h"
Essa linha agregará o objeto Contatos anteriormente criado à classe atual sendo usada. Toda
vez que desejar utilizar uma classe que criara, este é o procedimento.
Agora no arquivo AppDelegate.m, veja que há uma série de métodos que designam um estado
da aplicação. No nosso caso, precisamos quando o app estiver iniciado, logo, usaremos o
primeiro método, o didFinishLaunchingWithOptions. Este método já está implementado e não é
aconselhado que se modifque o que já está escrito.
O código a seguir poderá ser escrito em qualquer parte do método antes do return. Se colocar
no começo, antes do código padrão, tudo será executado antes da aplicação se tornar visível. Se
colocar depois, apenas quando o app mostrar sua janela. No nosso exemplo, fica a seu critério.
Primeiro vamos instanciar a classe. Lembrando que toda classe no objC deverá ser alocada e
inicializada. A seguir veremos como inicializar de ambas as formas.
Contatos *contato = [[Contatos alloc] init];
No exemplo acima, foi atribuido à variável contato o objeto Contatos, mas sem parâmetros de
inicialização, ou seja, se for fazer um get em alguma das variáveis de instância, o retorno será
null. Confira:
NSLog(@"%@",[contato nome]);
Exemplos de uso (57)
Vamos supor que você queira atribuir este objeto ao nome “Maria” com a idade “20”, que são
os valores que este objeto comporta:
[contato setNome:@"Maria"];
contato.idade=[NSNumber numberWithInt:20];
Acima eu representei as duas notações usadas no objC. Ambas são idênticas, só quis
demonstrar na prática como funciona. Vale reforçar que na notação por ponto, não se usa set ao
atribuir um valor. E como idade é do tipo NSNumber, o tipo é incompatível com o tipo int,
sendo necessário convertê-lo.
[NSNumber numberWithInt:20]
Confira agora a saída pelo console
NSLog(@"Nome: %@, idade: %@",[contato nome], [contato idade]);
Algo assim será listado
TesteClasses[2727:207] Nome: Maria, idade: 20
Deixemos este objeto de stand by e vamos agora declarar um segundo, mas usando os métodos
de inicialização que criamos antes. Vamos dar o nome de João e ele tem 16 anos:
Contatos *contato2 = [[Contatos alloc] initWithName:@"João" age:[NSNumber
numberWithInt:16]];
Ao fazermos a saída:
NSLog(@"Nome: %@, idade: %@",[contato2 nome], [contato2 idade]);
Teremos:
TesteClasses[2929:207] Nome: João, idade: 16
Agora vamos testar o método que criamos. No bloco abaixo:
if ([contato isMaiorDeIdade]) {
NSLog(@"%@ pode votar", [contato nome]);
}
else {
NSLog(@"%@ ainda é menor de idade", [contato nome]);
}
Usamos dentro de um IF, caso o método retornar YES, exibirá o primeiro bloco, senão o
segundo. Tente fazer o mesmo com o outro objeto.
O código na íntegra
Contatos *contato = [[Contatos alloc] init];
[contato setNome:@"Maria"];
contato.idade=[NSNumber numberWithInt:20];
NSLog(@"Nome: %@, idade: %@",[contato nome], [contato idade]);
Contatos *contato2 = [[Contatos alloc] initWithName:@"João" age:[NSNumber
numberWithInt:16]];
NSLog(@"Nome: %@, idade: %@",[contato2 nome], [contato2 idade]);
if ([contato isMaiorDeIdade]) {
NSLog(@"%@ pode votar", [contato nome]);
}
else {
NSLog(@"%@ ainda é menor de idade", [contato nome]);
Objetos de interface com o usuário (58)
Chega de teoria, vamos a parte que nos interessa! Afinal de contas, não é só de código que é
feito o seu app, ele precisa de um visual, de uma interface intuitiva e agradável ao usuário para
garantir o sucesso do seu programa (e da aprovação pela equipe da Apple também).
Neste capítulo vamos abordar com detalhes os principais componentes visuais da biblioteca
UIKit, que incluem botões, caixas de texto, imagem, etc. Para esta parte do curso, usaremos
muito tanto o Xcode, como o Interface Builder, logo, esteja preparado para uma constante
mudança de janelas.
Sintaxe padrão para declarar um objeto no Xcode (59)
Para todos os objetos da biblioteca UIKit, utiliza-se o mesmo padrão de declaração no Xcode,
para interligar a interface ao código.
No arquivo .h, dentro do bloco @interface...
IBOutlet UIObjeto *nome_do_objeto;
e caso queira torná-la acessível publicamente:
@property (nonatomic, retain) IBOutlet UIObjeto *nome_do_objeto;
No arquivo .m, logo após o @implementation, se o @property foi declarado:
@symphesize nome_do_objeto;
Feito isso, você já pode no Interface Builder associar os objetos com o controller, clicando
sobre o File’s owner segurando o botão control e arrastando sobre o objeto na view. Em
seguida faça a associação. Estes caminhos foram descritos no tutorial do capítulo 1, no Hello
World.
Sintaxe padrão para declarar uma ação a um evento (60)
Ao tocar num botão, escrever um texto, selecionar o item numa lista, espera-se que o programa
reaja de alguma forma. Para isso, precisamos de criar uma ação a um evento. A forma mais
usual de se declarar uma ação é:
No arquivo .h, abaixo do bloco @interface...
-(IBAction) nomeDaAcao:(id)sender;
E para implementar o código, no arquivo .m...
-(IBAction) nomeDaAcao:(id)sender {
// implementação
}
O parâmetro (id)sender representa o objeto UIKit que enviou a ação.
Para associar o método do componente a uma ação, selecione o objeto; na janela de atributos,
escolha a aba de eventos (uma seta dentro de um círculo) e dentro do círculo associado ao
evento na lista (sent events), clique segurando o botão control e arraste sobre o File’s Owner.
Em seguida, selecione o evento no qual deseja associar. Este passo já foi descrito no capítulo 1.
Label, textfield, textview e botão (61)
Nesta unidade trabalharemos com estes quatro componentes em conjunto, num único exemplo,
para já ilustrarmos a ligação entre os componentes.
Label (UILabel)
Representa uma etiqueta simples, geralmente com uma linha, usado principalmente para
denominar outros objetos ao usuário. Não há eventos associados a este componente
Principais propriedades
text / setText:
Recupera / altera o texto contido no label.
NSString *texto = [objLabel text];
[objLabel setText:@"outro texto"];
Textfield (UITextField)
Campo de inserção de dados por texto em uma única linha. Entre as propriedades do inspector
para este objeto incluem, além da aparência do componente, a configuração do teclado exibido.
Principais propriedades
text / setText:
Recupera / altera o texto contido no textfield.
NSString *texto = [objTextField text];
[objTextField setText:@?outro texto?];
editing
Propriedade somente-leitura que retorna YES ou NO se o campo está sendo editado.
Principais métodos
resignFirstResponder
Por padrão, o teclado uma vez aberto, não se fecha, sendo necessário invocar este método para
fechá-lo.
[objTextField resignFirstResponder];
Principais eventos
Did End On Exit
Quando o botão RETURN for pressionado.
Value Changed
Quando o valor do campo for alterado.
Textview (UITextView)
Similar ao textfield, entretanto, com a possibilidade de exibir textos com mais de uma linha.
Sua aparência é plana e já possui um scroll automático para quando o texto ultrapassar os
limites do box.
As propriedades e métodos são similares ao do texfield. A exceção ocorre nos eventos, pois não
há nenhum vinculado a este objeto.
Botão (UIButton)
Responde às ações de toque na interface. Pode assumir diversas formas. Há diversas formas de
personalização de layout, podendo conter texto, imagem e há também outros desenhos pré-
definidos.
Principais propriedades
currentImage
Propriedade somente-leitura que recupera a imagem (UIImage) que está contida no botão.
currentTitle
Somente-leitura. Recupera o texto contido no botão num NSString.
titleLabel
Somente-leitura. Recupera o objeto UILabel que contem o texto do botão.
setImage:forState:
Define uma imagem (UIImage) para um estado específico (UIControlState). Este estado
corresponde ao ponto de interação com o botão (estado normal, ao tocar, desabilitado, etc.). Os
valores deste estado podem ser:
UIControlStateNormal
Estado padrão: habilitado, sem estar em selecionado e nem acionado.
UIControlStateHighlighted
Quando o botão está em destaque.
UIControlStateDisabled
Botão desabilitado.
setTitle:forState:
Similar ao setImage, porém, no lugar da imagem, se define o texto do label do botão.
Na prática (62)
Crie um novo projeto e escolha o Single View Application. Dê o nome do projeto de UIKit1.
Abra o UIKitViewController.xib no Interface Builder.
Insira na view um label, um textfield, um botão e um textview. Para isso, selecione da library e
arraste para a view. Deixe-a configurada mais ou menos assim:
Dê dois cliques sobre o label e altere o título para “Digite alguma coisa”. Faça o mesmo no
botão com o texto “Entrar”. Dê dois cliques no textview e delete o texto padrão contido no
box. No final, ela ficará assim:
Cabeçalhos (63)
Volte ao XCode, e abra o arquivo UIKit1ViewController.h, que é o cabeçalho da classe que
controla a view. Abaixo da @interface, vamos declarar os IBOutlets com textview e com o
textfield. Com isso, temos agora a possibilidade de recuperar e alterar as propriedades destes
objetos.
IBOutlet UITextView *textos;
IBOutlet UITextField *entrada;
Mas por que não o botão e o label? Os outlets são declarados para os componentes que vão ter
suas propriedades alteradas ou acessadas. No caso do label está apenas para informar ao usuário
da ação que tem de fazer no textfield, e no botão só há necessidade de declararmos sua action.
Logo após as {} e antes do @end, vamos declarar a action que vai ser passada ao acionarmos o
botão.
-(IBAction)onEnter:(id)sender;
Voltemos agora ao Interface Builder e vamos fazer as conexões dos objetos com a classe (File’s
owner). Segure o botão control, clique sobre o File’s owner e solte sobre o textfield e selecione
entrada. Faça o mesmo no text view, selecionando a opção textos.
Para o botão, segure control e clique no botão. Arraste para o File’s owner e selecione a opção
onEnter.
Em caso de dúvidas como fazer isso, o tutorial mostrado no capítulo introdutório, no exemplo
Hello World.
Implementações (64)
Agora no arquivo UIkit1ViewController.m, vamos implementar o método onEnter.
-(IBAction)onEnter:(id)sender
{
}
Neste método vamos seguir o seguinte roteiro:
Fechar o teclado
[entrada resignFirstResponder];
Montar uma string contendo o texto atual do textview e com o texto novo a ser inserido,
contido no textfield
NSString *texto = [[NSString alloc] initWithFormat:@"%@ %@",[entrada text], [textos text]];
Sendo:
[entrada text]: texto contido no textfield
[textos text]: texto contido no textview
Passar o valor da string para o textview
[textos setText:texto];
Limpar o textfield
[entrada setText:@""];
No final, o método está assim:
-(IBAction)onEnter:(id)sender {
[entrada resignFirstResponder];
NSString *texto = [[NSString alloc] initWithFormat:@"%@ %@",[entrada text],
[textos text]];
[textos setText:texto];
[entrada setText:@""];
}
Pronto! Execute o app e veja como fica.
Switch e Activity Indicator (65)
Switch (UISwitch)
Componente que trabalha em dois estados: ON e OFF, similar ao interruptor elétrico.
Principais propriedades
isOn
Retorna um valor YES ou NO dependendo do estado do componente.
BOOL estado = [objSwitch isOn];
setOn:animated:
Atribui o valor YES ou NO ao switch e com a opção de ser animado ou não.
[objSwitch setOn:YES animated:YES];
[objSwitch setOn:YES animated:NO];
Principais eventos
Value changed
Evento invocado quando o estado do switch é alterado.
Activity Indicator (UIActivityIndicatorView)
Componente que fornece ao usuário um feedback visual de atividade de processamento.
Principais propriedades
hidesWhenStopped / setHidesWhenStopped
Recupera / atribui a propriedade com valores YES ou NO indicando se o indicador deve ou não
ser mostrado enquanto a animação está parada.
isAnimating
Retorna YES ou NO, dependendo do estado do indicador.
Principais métodos
startAnimating
Faz com que o indicador inicie a animação.
stopAnimating
Faz com que o indicador interrompa a animação. Neste caso, se a propriedade
hidesWhenStopped estiver marcada como YES, o componente será ocultado automaticamente.
Na Prática (66)
Crie um novo projeto Single View Application. Dê ao projeto o nome de UIKit2. Abra o
arquivo UIKit2ViewController.xib no Interface Builder.
Coloque na view o Switch e Activity Indicator. No inspector, coloque a propriedade State do
switch para Off. Para o indicador, maque a propriedade “Hide when stopped” e desmarque a
propriedade “Animating”.
No arquivo UIKit2ViewController.h, declare o objeto do indicador:
IBOutlet UIActivityIndicatorView *spinner;
E declare também o método que vai capturar a mudança de estado do switch:
-(IBAction)setOnOff:(id)sender;
Vá no interface builder e faça as conexões com os componentes:
File’s owner -> Activity Indicator
Switch -> File’s owner
Salve a view.
Agora no arquivo UIKit2ViewController.m, vamos implementar o método setOnOff. Na
execução deste método, quando o switch ficar ON, o indicador aparece, caso contrário, a
animação para e ele desaparece.
-(IBAction)setOnOff:(id)sender {
if ([sender isOn])
{
[spinner startAnimating];
}
else {
[spinner stopAnimating];
}
}
Simples! Agora é só testar!
Segmented Control e Image View(67)
Segmented Control (UISegmentedControl)
Objeto que exibe um set de botões segmentados para exibir opções de escolha.
Principais propriedades
numberOfSegments
Somente-leitura. Recupera o número de opções (segmentos) contidos no objeto.
int segs = [objSeg numberOfSegments];
selectedSegmentIndex / setSelectedSegmentIndex
Recupera/atribui o valor do segmento selecionado. O primeiro item sempre tem o índice 0
(zero) e crescente a cada item.
Principais eventos
Value changed
Acionado quando uma outra opção do segmento for alterada.
Image View (UIImageView)
Container para exibir imagens.
Principais propriedades
image / setImage
Recupera / atribui um objeto UIImage associado a view. Esta imagem pode tanto ser definida
pelo inspector ou em runtime, podendo ser local ou carregada remotamente.
Na Prática (68)
Crie um novo projeto Single View Application, com o nome de UIKit3. Abra o
UIKit3ViewController.xib no Interface builder.
Adicione um segmented control e um image view na tela. No inspector, defina Segments para
2, e coloque os títulos dos botões respectivamente Brasil e Japão.
Baixe aqui as imagens usadas no projeto, que são as bandeiras dos dois países. Descompacte o
arquivo e arraste a pasta para a o grupo Supporting Files.
Clique no image view, na propriedade image, escolha brasil.png. Na seção SIZE (com o ícone
de uma régua), determine os seguintes valores para os tamanhos: W = 320 e H = 206. No final,
vc terá algo assim:
Salve a view e voltemos agora ao Xcode.
Criando o código (69)
No arquivo UIKit3ViewController.h, vamos declarar o outlet do image view, que vai ter a
imagem alterada na medida que o segmented control tem seu estado alterado, e o método de
mudança do estado do segmented control.
IBOutlet UIImageView *imagem;
-(IBAction)toggle:(id)sender;
Faça as devidas conexões no Interface Builder, e vamos à implementação.
A ideia é simples. De acordo com a opção do outlet, o image view irá exibir a bandeira
correspondente ao país. Na implementação:
-(IBAction)toggle:(id)sender {
}
Criaremos um array com as imagens, na ordem que está disposto no segmented control
NSArray *imgs = [NSArray arrayWithObjects:
[UIImage imageNamed:@"brasil.png"],
[UIImage imageNamed:@"japao.png"],
nil];
Definir a imagem do image view de acordo com o índice selecionado
[imagem setImage:[imgs objectAtIndex:[sender selectedSegmentIndex]]];
Sendo:
[sender selectedSegmentIndex]: O valor do índice do segmented control.
No final, o código está assim:
-(IBAction)toggle:(id)sender {
NSArray *imgs = [NSArray arrayWithObjects:
[UIImage imageNamed:@"brasil.png"],
[UIImage imageNamed:@"japao.png"],
nil];
[imagem setImage:[imgs objectAtIndex:[sender selectedSegmentIndex]]];
}
Slider e Progress View (70)
Slider (UISlider)
Este componente é similar a uma barra de rolagem, na qual o valor ser altera na medida que o
cursor muda de posição.
Uma particularidade deste componente é que ele não responde muito bem ao método com o
objeto sender, logo, excepcionalmente neste caso, recomenda que declare o IBOutlet deste
componente, mesmo associando um método a ele.
Principais propriedades
value
Retorna um valor do tipo float com o valor correspondente à posição do cursor.
setValue:animated:
Define um valor float para o slider. O argumento animated determina se a mudança de valor vai
ser animada ou não (efeito de slide do cursor).
[objSlider setValue:2.7 animated:YES];
minimumValue / setMinimumValue
Determina o valor mínimo comportado pelo slider. Este valor é do tipo float.
maximumValue / setMaximumValue
Determina o valor máximo comportado pelo slider. Este valor é do tipo float.
Principais eventos
Value Changed
Invocado quando o cursor muda de posição.
Progress View (UIProgressView)
Componente que ilustra o progresso de 0 a 100%, admitindo valores float de 0 a 1. Sua única
propriedade disponível é a progress.
progress / setProgress
Determina um valor de 0 a 1 do tipo float para marcar o progresso na view.
Na prática (71)
Crie um novo projeto View-based application, dê o nome de UIKit4. Abra o arquivo
UIKit4ViewController.xib e coloque um label, um slider e um progress view.
No label, edite o texto colocando um 0 (zero). No inspector, configure o slider, definindo o
minimum value para 0 e maximum 100, e initial 0. No progress view, coloque a propriedade
progress para 0.
No final, sua view deverá estar similar a imagem abaixo:
Criando o código (72)
No arquivo UIKit4ViewController.h, declare os três componentes inseridos, mais o método que
vai responder à mudança do slider:
IBOutlet UISlider *slider;
IBOutlet UIProgressView *progress;
IBOutlet UILabel *display;
e
-(IBAction)mudanca:(id)sender;
Faça as conexões no Interface builder dos componentes e voltemos ao arquivo
UIKit4ViewController.m.
Na implementação do método, vamos seguir os seguintes passos:
Pegar o valor atual do slider
float valorAtual = [slider value];
Atribuir ao título do label
[display setText:[NSString stringWithFormat:@"%.1f",valorAtual]];
Calcular o progresso para que seja compatível ao progress
float progresso = valorAtual / 100;
Definir o valor ao progress view
[progress setProgress:progresso];
No final, o código será assim:
-(IBAction)mudanca:(id)sender
{
float valorAtual = [slider value];
[display setText:[NSString stringWithFormat:@"%.1f",valorAtual]];
float progresso = valorAtual / 100;
[progress setProgress:progresso];
}
Classes relacionadas (73)
Para manipularmos os objetos, precisamos trabalhar com algumas classes que lidam com a
interface. São várias, todas bem documentadas pela Apple, entretanto, chamo a atenção em
duas: a UIFont e a UIColor.
UIColor(74)
As cores dos objetos e o valor de transparência (alpha) são definidos pelo objeto UIColor. Há
algumas cores padrão já definidas, mas você pode livremente criar outras cores, baseadas no
padrão RGB (vermelho, verde, azul) ou RGBA (com a camada Alpha).
Modos de inicialização
Cores em preto e branco (grayscale)
[UIColor colorWithWhite:0.0a1.0 alpha:0.0a1.0];
Cores no sistema RGBA
[UIColor colorWithRed:0.0a1.0 green:0.0a1.0 blue:0.0a1.0 alpha:0.0a1.0];
Imagem de fundo no lugar da cor
[UIColor colorWithPatternImage:UIImageObjeto];
Cores pré-definidas (75)
Basta invocar o método estático do objeto:
[UIColor cor];
O valor cor pode ser:
blackColor
darkGrayColor
lightGrayColor
whiteColor
grayColor
redColor
greenColor
blueColor
cyanColor
yellowColor
magentaColor
orangeColor
purpleColor
brownColor
clearColor
Sendo o clearColor para transparente.
UIFont (76)
Gerencia as fontes dos objetos. Alguns métodos importantes a saber.
Retorna um array com os nomes das fontes:
[UIFont familyNames];
Pra declarar uma fonte e com um tamanho:
[UIFont fontWithName:@"Nome da fonte" size:tamanho];
UIImage (77)
Classe responsável por gerenciar arquivos de imagem. No geral, os componentes do UIKit
framework que trabalham com imagens, possui esta classe envolvida.
Esta classe suporta os arquivos do tipo tif, jpg, gif, png, bmp, ico, cur e xbm.
Modos de inicialização
imageWithContentsOfFile:
Inicia o objeto com o conteúdo de uma string indicado pelo caminho da imagem.
imageNamed:
Inicia o objeto com uma imagem contida no resource.
Windows e views (78)
No iOS se usa windows e views para mostrar as informações visuais contidas na aplicação.
Uma janela (window) não contem elementos visíveis em si mas serve de suporte à diversas
views e subviews, que pode ser um container, um botão, texto, imagem, etc e é usada para
gerenciar seus subgrupos.
Toda aplicação tem pelo menos uma janela e uma view para apresentar o conteúdo. Caso
contrário, nada seria exibido ao iniciar e provavelmente a aplicação se terminaria. Há vários
tipos de views a serem explorados, que vão desde uma simples tela branca a outros tipos mais
complexos, como os de rolagem, paginação, tabela, etc.
Métodos das views (79)
Todo objeto do tipo ViewController possui métodos de execução já pré-definidos, de acordo
com os eventos que ela recebe.
initWithNibName
Método inicializador no qual se define um arquivo NIB para ser lido associado ao controller ao
ser invocado. Ao ser implementado, tudo que for descrito neste método será executado quando
a view for inicializada com este método.
viewDidLoad
Este método é invocado quando a view se torna visível, independente do meio que ela
aparecerá.
shouldAutorotateToInterfaceOrientation
Ao rotacionar o iPhone, este evento é invocado. Ao retornar YES, os componentes responderão
a auto-rotação.
didReceiveMemoryWarning
Método chamado quando o app recebe um aviso de memória baixa, geralmente neste método é
descarregado tudo que estiver ocupando espaço na memória atoa.
viewDidUnload
Chamado quando a view é descarregada.
Métodos do App Delegate (80)
No App Delegate, todas as ações relacionadas ao app de uma maneira geral, numa instância
maior, são designadas nesta classe. Alguns métodos são importantes conhecer para se ter um
controle maior do funcionamento da aplicação.
didFinishLaunchingWithOptions
Método chamado quando a aplicação é iniciada. Geralmente neste método são iniciados as
views quando o app é Window based.
applicationWillResignActive
Invocado quando a aplicação entrar em modo inativo (quando recebe um sms ou uma chamada
é recebida, botão home acionado, etc.). Geralmente se pausa as atividades com maior consumo
de memória e em caso de jogos, automaticamente entra em PAUSE.
applicationDidEnterBackground
Méotodo chamado quando a aplicação entra no modo de segundo plano, quando o botão home
for pressionado ou quando uma outra aplicação entra em primeiro plano.
applicationWillEnterForeground
Chamado quando a aplicação estiver prestes a entrar em atividade.
applicationDidBecomeActive
Invocado quando a aplicação volta ao primeiro plano. No geral, os processos parados no
método applicationDidEnterBackground são retornados à atividade.
applicationWillTerminate
Método chamado quando a aplicação for finalizado.
applicationDidReceiveMemoryWarning
Similar ao evento da view didReceiveMemoryWarning, mas num contexto geral da aplicação.
Navigation Bar (81)
Iniciando o tópico sobre navegação pelas views, eis o tipo mais comum, que é pela barra de
navegação.
Esta sem dúvida é uma das formas mais convenientes quando se trata de uma navegação linear
e o iOS facilita muito neste ponto, pois muitas das coisas mais básicas, como o meio de voltar à
view anterior, já são implementadas por default.
TabBar Controller (82)
Compõe uma lista de opções justapostas na parte mais baixa do aplicativo. Um bom exemplo
para ilustrar o funcionamento desta view é no player de música do iPhone. Logo abaixo você vê
“Albums, artistas, playlists...”, pois então, este é o TabBar Controller.
Este componente é útil quando precisamos de ramificar as opções de funções do app, sendo que
cada aba inicia uma nova ramificação de views, independente das demais.
Propriedades (83)
Para formatação desta view, deve-se seguir o padrão: ícone e texto. Há algumas opções pré-
definidas dentro da propriedade Tab bar item > Identifier. Para encontrar esta propriedade, dê
dois cliques sobre o item no Interface Builder. Caso queira criar outros nomes e definir outras
imagens, use a aba logo abaixo “Bar Item”. Lembrando que a imagem deverá ser na cor branca,
com fundo transparente. Para variação de tonalidade, use os valores de opacidade na hora de
criá-la. A imagem deve seguir o tamanho de 30x30 px.
Este tamanho servirá bem para os iPhones e iPods sem o retina display. Para que suas imagens
funcione bem nas novas gerações de tela, deve-se criar um arquivo com o dobro das dimensões
(no caso do tab bar, 60x60 px) e no nome do arquivo com o final @2x antes da extensão. Ex:
mapa.png / mapa@2x.png
TableView Controller (84)
Este tipo de view é uma das mais importantes e merece uma atenção especial. O seu uso mais
básico é na listagem de dados tabulares, que consqüentemente serve como meio de navegação
de dados. Além disso, há diversas formas de se trabalhar o TableView no quesito formatação,
como também inserir componentes, etc.
Tipos de tabela
Veremos agora alguns exemplos de como suas tabelas podem ser.
Tabela padrão
Modelo mais usado, mais simples e prático de exibir dados. Caracteriza-se por ter apenas o
título na célula.
Tabela com índice
Neste caso, há uma ordenação por ordem alfanumérica. Muito útil para quando se trabalhar
com listas de contatos. Possui uma separação nas células, além de uma rolagem de pesquisa
rápida na direita.
Tabela de seleção
Uma pequena variação do primeiro exemplo, mas aqui os itens podem ser checados como
selecionados (checklist).
Tabelas agrupadas
Ideal para quando queira fazer uma melhor distribuição da informação na view. Você pode
agrupar as informações em comum no mesmo grupo.
Propriedades especiais nas células (85)
Esta propriedade é definda dentro do código da TableViewController, ao invocar o método
cellForRowAtIndexPath, logo após o código.
cell = [[[UITableViewCell alloc] initWithStyle:...
UITableViewCellStyleDefault
Este é o tipo padrão de célula. Corresponde à lista com o titulo somente.
UITableViewCellStyleValue1
Nesta lista, o título fica alinhado à esquerda, e à direita, num corpo menor, as informações de
detalhe. Neste caso, recomenda-se caso este detalhe seja pequeno.
UITableViewCellStyleValue2
Neste caso, ambos os títulos ficam no mesmo corpo de fonte, entretanto, a valorização maior do
espaço é com o valor dos detalhes.
UITableViewCellStyleSubtitle
As informações de detalhe ficam logo abaixo do título.
Títulos da célula (86)
Dentro do método cellForRowAtIndexPath, para definirmos o título, logo abaixo do comentário
“// Configure the cell...”
cell.textLabel.text = @"texto";
Para definirmos o subtítulo:
cell.detailTextLabel.text = @"texto";
Para definirmos uma imagem ilustrativa na célula:
UIImage *objImg = [UIImage imageNamed:@"nomeImagem.png"];
cell.imageView.image = objImg;
Sendo esta imagem tem de estar contida no projeto. Para isso, basta arrastar os arquivos para a
pasta Resources (no XCode 4 esta pasta se chama Supporting files) e marque a opção que "se
deseja copiar os arquivos".
Pode-se usar os formatos JPG, GIF se desejar, mas o recomendado é o PNG.
Ícones de navegação (87)
Às vezes, para deixar claro que, ao selecionarmos uma célula, ela nos levará a outra view, usa-
se uns ícones de forma de seta, localizados à direita da célula. Estes ícones são chamados de
AcessoryType, definidos ainda dentro do método cellForRowAtIndexPath.
cell.accessoryType = escolha_de_acordo_com_a_lista_abaixo;
UITableViewCellAccessoryDisclosureIndicator
UITableViewCellAccessoryDetailDisclosureButton
UITableViewCellAccessoryCheckmark
Modal views (88)
As views modais (modal view) são aquelas que abrem sobrepostas a todas a janelas presentes.
Para exibir uma view modal:
View *objView = [[View alloc] init];
[self presentModalViewController:objView animated:YES];
Sendo View o nome da classe que contém a view na qual deseja exibir.
Para fechá-la:
[self dismissModalViewControllerAnimated:YES];
Transições (89)
Algumas transições definem o comportamento da animação ao exibir o modal view. Para
configurarmos, usa-se o método:
[objView setModalTransitionStyle:AQUI_ENTRA_O_MODO_DE_TRANSICAO];
UIModalTransitionStyleCoverVertical
Este é a transição padrão, com a animação de baixo para cima ao exibir, e de cima para baixo
ao sair. E para esta, não precisa do código acima. Se nenhuma transição for definida, esta é a
executada.
UIModalTransitionStyleFlipHorizontal
Desta forma, a view aparecerá com o efeito de flip, revelando como se a view estivesse contida
no verso.
UIModalTransitionStyleCrossDissolve
Mostra a view com um efeito de transição de fade in e fade out.
UIModalTransitionStylePartialCurl
Mostra a view abaixo, revelando apenas uma parte dela, com o efeito de uma folha de papel
sendo dobrada.
Mensagens de alerta (90)
Para invocar as mensagens de alerta, usa-se uma classe chamada UIAlertView. Para invocá-
los, usa-se a estrutura:
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Aqui entra o título"
message:@"Mensagem do box" delegate:self cancelButtonTitle:@"Título do botão cancelar"
otherButtonTitles:nil] autorelease];
[alert show];
Para adicionar mais um outro botão, coloque a linha antes do [alert show]
[alert addButtonWithTitle:@"titulo do botão"];
Pra receber a ação do botão, antes, no arquivo .h do objeto que estiver trabalhando, coloque o
protocolo UIAlertViewDelegate
Em seguida, siga o evento abaixo:
- (void)alertView:(UIAlertView *)alertView
didDismissWithButtonIndex:(NSInteger)buttonIndex {
// executa
}
Nesta função, reconhece-se qual botão foi clicado pela variável buttonIndex, sendo o 0 o botão
Cancelar, e os demais botões, seguindo a ordem numérica crescente.
Exemplo: se acrescentarmos um botão SIM, além do que já tem como padrão (que se chamaria
NÃO), o NÃO seria 0 e o SIM seria 1.
Na prática(91)
Neste exemplo prático, vamos demonstrar como incorporar todas as views estudadas
anteriormente para vermos melhor na prática como elas se comportam, e claro, como programá-
las:
Configurando o projeto baseado em Window
Crie um novo projeto, escolha Empty Application e dê o nome de TutorialView. A partir daí
você verá que não há nada no projeto senão o AppDelegate. Vamos agora adicionar a Window
que vai receber o tab bar controller.
Vá em File -> New -> File... e em User Interface, escolha Window. Escolha o Target Device:
iPhone e dê o nome de MainWindow
Inclua um Object no Window.
Atribua o Object ao AppDelegate, selecionando na lista de Objects e no Inspector, fazer esta
alteração
Selecione o File's Owner e mude a Class para UIApplication. A partir dela, vamos designar os
links entre as classes. Comece associando o outlet delegate para o AppDelegate.
Abra o arquivo AppDelegate.h e altera a linha de
@property (strong, nonatomic) UIWindow *window;
para
@property (strong, nonatomic) IBOutlet UIWindow *window;
Volte para o MainWindow.xib e clique no App Delegate, escolha o outlet Window e arraste
para a Window criada.
Em seguida, vamos associar esta Window para que seja a window principal. No menu a
esquerda, clique primeiro item (no caso TutorialView). Marque como Main Interface o
MainWindow
E por fim, modifique o método do didFinishLaunchingWithOptions no AppDelegate.m de
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
para
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return YES;
}
Adicionando o TabBarController
Abra o MainWindow.xib e inclua um Tab Bar Controller. Para isso, selecione-o na Library e
arraste-o para a janela de projeto.
Você verá algo similar a isso:
Configurando a Tab Bar(92)
Como você pode ver, há duas view controllers já inseridas por padrão. Quando for trabalhar
com este componente, não significa que você terá de trabalhar com elas necessariamente, mas
no nosso exemplo, vamos mantê-las e adicionaremos mais dois controllers, só que do tipo
Navigation Controller.
Arraste-os para dentro do Tab Bar Controller, mantendo-os abaixo dos dois primeiros
controllers. Tenha certeza de que eles estão contidos dentro do Tab Bar. No final você verá algo
assim:
Em cada aba, dê dois cliques sobre o título e Renomeie-os para respectivamente: Modal Views,
Alerts, View Padrão, Table View. O resultado deve ser algo similar a figura abaixo:
Incluindo a Tab Bar ao Window (93)
Mas se executarmos o nosso programa, você verá apenas uma tela branca. Isso é porque você
precisa adicionar este Tab Bar Controller a Window.
Vá no Interface Builder com o arquivo MainWindow.xib e segure o control e clique em
Window e arraste sobre o Tab Controller, marque a opção rootViewController.
Execute e verá algo assim:
Ícones (94)
Caso queira colocar alguns ícones para ilustrar os tabs, crie as imagens dentro do padrão
30x30px (60 para o retina display). A imagem dever ser em branco com fundo transparente,
podendo no desenho ter valores de transparência (alfa). Deixarei para download aqui os ícones
usados neste tutorial.
Download dos ícones
Para usá-los, arraste-os ao projeto para a pasta Resources (ou Supporting Files caso esteja no
XCode 4) e marque a opção “Copy if needed”.
Daí é só escolher no inspector na opção Image as imagens que desejar.
Modal Views (95)
Vamos criar a view para a primeira aba, que vai demonstrar o funcionamento das animações
para exibir as modal views.
Crie um novo arquivo do tipo UIViewController subclass, marque como subclass a opção
UIViewController e a opção de criar o XIB. Dê o nome de ModalViews.
Abra o ModalViews.xib no Interface Builder e crie 4 botões. No final, você terá algo parecido
com a imagem abaixo:
Subview a ser aberta (96)
Antes de declaramos as actions aos botões, vamos criar uma nova view com seus controllers,
mas desta vez chamada de AbreModalView, seguindo os mesmos procedimentos para criar o
ModalViews. Esta view será exibida na ação dos botões.
Abra o arquivo AbreModalView.xib e mude a cor do fundo para ficar visível a transição.
Coloque um botão no meio, com o título de Fechar. No final, você terá algo parecido com isso:
Vá no arquivo AbreModalView.h e declare o método:
-(IBAction)fechar:(id)sender;
No arquivo AbreModalView.m, implemente este método da seguinte forma:
-(IBAction)fechar:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}
E não esqueça de fazer a conexão do botão com o File’s Owner no Interface Builder,
vinculando o evento fechar.
Declarando os métodos do ModalViews.h (97)
Voltemos agora ao ModalViews.h. Antes de declararmos os métodos dos botões, vamos
importar a view que acabamos de criar:
#import "AbreModalView.h"
Declare em seguida no @interface a view. Faremos desta maneira para não ter que ficarmos
instanciando toda vez que implementamos os eventos dos botões.
AbreModalView *abreModal;
E agora, os métodos dos botões:
-(IBAction)abrePadrao:(id)sender;
-(IBAction)abreFlip:(id)sender;
-(IBAction)abreDissolve:(id)sender;
-(IBAction)abrePartialCurl:(id)sender;
Implementando os métodos no ModalViews.m (98)
Vamos agora ao arquivo ModalViews.m. Antes de implementarmos os métodos dos botões,
precisamos inicializar o objeto que contem a view abreModal. Vá no método viewDidLoad e
inicialize-a:
abreModal = [[AbreModalView alloc] init];
Agora sim, implementemos os métodos. Todos os quatro métodos têm códigos similares,
mudando apenas o parâmetro de transição. Começaremos com o abrePadrao.
-(IBAction)abrePadrao:(id)sender
{
[abreModal setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self presentModalViewController:abreModal animated:YES];
}
Apesar de por padrão a transição já ser a vertical, deixaremos reforçada esta propriedade, pois,
como esta view é instanciada no objeto para ser usada nos demais métodos, se não reforçamos o
tipo de transição, ele vai usar a última declarada.
Não entendeu? Bom, faz de conta que não colocamos nada. Ao executar o programa e clicar no
primeiro botão, você verá que a subview irá aparecer num movimento de baixo para cima. Mas
se em seguida você clicar no botão Dissolve e voltar, ao clicar no primeiro botão, ao invés do
movimento ser o de subida, será o último usado, ou seja, dissolve.
Os demais botões, sem grandes mistérios:
-(IBAction)abreFlip:(id)sender
{
[abreModal setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentModalViewController:abreModal animated:YES];
}
-(IBAction)abreDissolve:(id)sender
{
[abreModal setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentModalViewController:abreModal animated:YES];
}
-(IBAction)abrePartialCurl:(id)sender
{
[abreModal setModalTransitionStyle:UIModalTransitionStylePartialCurl];
[self presentModalViewController:abreModal animated:YES];
}
E não se esqueçam de fazer as devidas conexões dos métodos no Interface Builder.
Mas agora você precisa vincular esta View na primeira aba do Tab Bar. Na janela do projeto no
Interface Builder, selecione o ViewController da primeira aba, no Inspector, na aba Identity, no
campo Class, escolha ModalViews.
Execute o app e veja como fica :)
Alerts (99)
Para esta aba, crie uma nova view com o nome de Alerts. O procedimento para criar este
arquivo é o mesmo seguido nos anteriores. Abra o arquivo Alerts.xib no Interface Builder.
Coloque um label, um textfield e um botão. Configure-os da seguinte maneira:
No arquivo Alerts.h, declare o textfield para que receba seu valor.
IBOutlet UITextField *mensagem;
E o método do botão que será executado.
-(IBAction)mostrarMensagem:(id)sender;
No arquivo Alerts.m, faremos a implementação do método do botão. Quando o botão for
acionado, ele exibirá um alerta com a mensagem escrita no textfield. A implementação segue
abaixo:
-(IBAction)mostrarMensagem:(id)sender
{
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Sua mensagem aqui"
message:[mensagem text] delegate:self cancelButtonTitle:@"Fechar" otherButtonTitles:nil]
autorelease];
[alert show];
[mensagem resignFirstResponder];
}
Sendo:
initWithTitle:@"Sua mensagem aqui": O título do alerta
message:[mensagem text]: a mensagem do alerta, que seria o que está escrito no textfield.
cancelButtonTitle:@"Fechar": Título do botão para fechar.
E claro, no final, fechamos o teclado:
[mensagem resignFirstResponder];
Faça as devidas conexões com o textfield e o botão no Interface Builder. E no Tab Bar, no
segundo ViewController, associe a Custom Class para Alerts.
Simples, não é? Execute e veja como ficou.
View Padrão – SubView (100)
Nesta aba, começaremos a trabalhar com o navigation controller, sendo neste exemplo uma
navegação por views simples. Antes de começarmos a trabalhar nesta view, vamos criar a
subview que será aberta nos exemplos seguintes.
Crie uma nova view, seguindo os mesmos passos anteriores chamado SubView. Abra o
SubView.xib, mude a cor do fundo e coloque um label.
No SubView.h declare o label e uma string, que vai ser passada pelas views principais. Vamos
nos exemplos a seguir demonstrar como se passa um valor a uma outra view.
IBOutlet UILabel *saida;
NSString *titulo;
Tornaremos a string titulo pública, logo, vamos declará-la como propriedade:
@property (nonatomic, retain) NSString *titulo;
Agora no arquivo SubView.m, vamos em primeiro lugar tratar da propriedade titulo. Como já
se sabe:
@synthesize titulo;
Quando a view for carregada, queremos que o título do label seja alterado. No método
viewDidLoad:
[saida setText:titulo];
E para definirmos o título que vai ser exibido no Navigation Controller:
[self setTitle:@"Subview"];
Sendo Subview o texto que vai aparecer na barra azul. Pode ser qualquer coisa.
ViewPadrao(101)
Agora vamos ao View Padrão. Crie uma nova view chamada ViewPadrao, abra o
ViewPadrao.xib no Interface Builder e coloque um botão.
Abra agora o ViewPadrao.h e declare o método do botão:
-(IBAction)abreSubView:(id)sender;
E vamos implementá-lo no ViewPadrao.m. Antes disso, vamos importar a subview a esta
classe:
#import "SubView.h"
Em seguida, implementar o método do botão. O código deste método segue três passos
fundamentais:
Instância da classe SubView
Passagem do valor a propriedade titulo
Exibir o subview
-(IBAction)abreSubView:(id)sender
{
SubView *sub = [[[SubView alloc] init] autorelease];
[sub setTitulo:@"Alo!"];
[self.navigationController pushViewController:sub animated:YES];
}
Não se esqueça de fazer a conexão do botão no Interface Builder e de vincular ao terceiro
controller a Class ViewPadrao.
Table View (102)
Vamos agora ao último tab, para isso, crie uma nova view, mas desta vez, cuidado pois você
deverá marcar a opção Subclass of UITableViewController. Dê o nome de TableView.
No arquivo TableView.h, vamos declarar o array que conterá a lista exibida na tabela.
NSArray *lista;
Em numberOfSectionsInTableView, coloque:
return 1;
Pois neste caso, só teremos uma única seção na lista.
Em numberOfRowsInSection, coloque:
return [lista count];
Neste caso, ele pede para informar o número de linhas que a tabela possui. Demos o valor que
corresponde a quantidade de objetos no array lista.
Nas versões mais atuais do Xcode (4.3) tem havido algumas modificações na implementação
neste método, o que leva a necessidade de adicionar o seguinte bloco antes da personalização
da célula, para que nenhum erro ocorra:
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
}
Agora no método cellForRowAtIndexPath, vamos declarar os títulos das linhas da tabela.
Logo abaixo da linha comentada, insira:
cell.textLabel.text = [lista objectAtIndex:indexPath.row];
Sendo:
cell.textLabel.text: a propriedade de título da linha da tabela.
indexPath.row: o índice da linha que está no momento sendo trabalhada.
Mas para termos o que lista na tabela, precisamos de popular o array. Para isso, no método
viewDidLoad, inicializaremos o array e colocaremos alguns itens para ser listados:
lista = [[NSArray alloc] initWithObjects:@"Mensagem 1", @"Mensagem 2", nil];
Você pode colocar quantos itens que desejar, contanto que o último item do array seja o
terminador nil.
Agora só falta fazermos com que ao selecionarmos o item na tabela, seja aberto o subview.
Vamos no método didSelectRowAtIndexPath, que se encontra no final do arquivo.
Há um exemplo de declaração comentado para servir de exemplo. Não usaremos exatamente
aquele modelo, mas um que siga os seguintes passos:
Instanciaremos a classe que controla o subview
Passaremos o valor selecionado da tabela para a subview
Abriremos a view
O código de implementação será algo similar ao abaixo:
SubView *sub = [[SubView alloc] init];
[sub setTitulo:[lista objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:sub animated:YES];
Sendo:
indexPath.row: o indice da linha da tabela que foi selecionada.
Não se esqueça agora de associar na quarta tab o TableView e execute o app, veja como ficou.
Acesso a dados e sandbox (103)
Neste capítulo vamos trabalhar com formas de tratamento de dados e armazenamento, algo
essencial para o desenvolvimento de aplicativos. Vamos listar aqui os principais e mais usados
meios de acesso e armazenamento de dados usados pelo iOS.
NSUserDefaults (104)
O NSUserDefaults é uma classe do objC que fica responsável por guardar dados pequenos de
forma persistente. Muito conveniente para armazenar configurações, sem ter a necessidade de
criar bancos de dados ou gerar arquivos de configuração.
Seu uso é muito simples, para gravar dados:
NSUserDefaults *objeto = [NSUserDefaults standardUserDefaults];
[objeto setObject:@"valor desejado" forKey:@"valorChave"];
[objeto setObject:@"outro valor desejado" forKey:@"outroValorChave"];
[objeto synchronize];
E para abrí-los:
NSUserDefaults *objPrefs = [NSUserDefaults standardUserDefaults];
NSString *objString = [objPrefs stringForKey:@"valorChave"];
NSString *objOutraString = [objPrefs stringForKey:@"outroValorChave"];
Na prática (105)
Crie um novo projeto View-based Application, dê o nome de TesteDefaults. Abra o arquivo
TesteDefaultsViewController.xib e no interface builder, crie um TextField e um botão. Dê ao
botão o título Salvar. Algo parecido com isso:
Voltando ao Xcode, abra o arquivo TesteDefaultsViewController.h e declararemos os objetos
e métodos.
Dentro do @interface, declare o TextField:
IBOutlet UITextField *texto;
E logo após:
-(IBAction)salvar:(id)sender;
Volte ao Interface builder e associe o texto ao TextField e o método salvar ao botão Salvar.
Agora no arquivo TesteDefaultsViewController.m vamos declarar o método salvar:
-(IBAction)salvar:(id)sender
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:[texto text] forKey:@"textoPadrao"];
[prefs synchronize];
}
E para abrir os dados no TextField:
- (void)viewDidLoad {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[texto setText:[prefs stringForKey:@"textoPadrao"]];
[super viewDidLoad];
}
Explicando rapidamente o que acontece no programa acima, ao salvar, será salvo a propriedade
“textoPadrao” com o valor contido no TextField. Quando o usuário abrir o app, será consultado
se há algum valor para a chave textoPadrao e seu valor inserido no TextField.
Rode a aplicação e veja como funciona. Para ter certeza do funcionamento, salve alguma coisa
e feche o app. Abra-o novamente e veja.
Acesso aos arquivos do sandbox (106)
O sandbox é um diretório no qual você pode ter acesso dentro do seu app para armazenar
dados. É o único local onde você terá total liberdade de escrita e leitura, ao contrário das demais
pastas.
Para acessar o diretório do sandbox:
NSArray *objArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *caminho = [objArray objectAtIndex:0];
Para verificar a existência de um arquivo:
NSString *arquivo = [path stringByAppendingPathComponent:@"nomedoarquivo"];
if ([[NSFileManager defaultManager] fileExistsAtPath:arquivo])
{
// Algo a ser feito
}
Para listar os arquivos contidos no Documents:
NSArray *arrayConteudo = [[NSFileManager defaultManager]
contentsOfDirectoryAtPath:caminho error:NULL];
for (int Count = 0; Count < (int)[arrayConteudo count]; Count++)
{
NSLog(@"Arquivo %d: %@", (Count + 1), [arrayConteudo objectAtIndex:Count]);
}
Neste tópico não vamos nos aprofundar muito, pois a aplicação deste recurso é muito mais
visível dentro de um exemplo prático e será muito usado no projeto final.
PLists (107)
Uma PList (property list) é um meio de armazenamento de dados simples e estruturados.
Possui um funcionamento um pouco mais complexo do que o User Defaults, pois sua estrutura
é mais ramificada.
Uma PList pode tanto receber o tipo NSArray como NSDictionary e sendo seu carregamento
feito via arquivo. A diferença entre os dois tipos está no fato de que o NSArray só permite uma
de correspondência simples, bidimensional enquanto no Dictionary permite uma ramificação
maior.
Como usá-las
Para criar uma plist
Vá em New File, na aba Mac OS X > Resources > Property List. E dê o nome que desejar.
Na Property List, veja em Root, escolha na coluna do meio o tipo que desejar: NSArray ou
NSDictionary. Clique no botão que aparece ao fim da linha e insira um novo item.
Nas versões mais recentes do XCode (4.2), só tem permitido criar PLists do tipo NSDictionary,
logo, se estiver usando esta versão, desconsidere o tipo NSArray.
Para carregá-la
NSString *caminho = [[NSBundle mainBundle] pathForResource:@"NomeDoArquivo"
ofType:@"plist"];
NSMutableDictionary *objDictionary = [[NSMutableDictionary alloc]
initWithContentsOfFile:caminho];
Na prática (108)
Neste projeto não exploraremos a parte visual, apenas ilustraremos como funciona a listagem
de dados.
Crie um novo projeto View-based Application e dê o nome de TestePLists. Em seguida crie um
arquivo PList e dê o nome de cardapio.plist.
Abra o cardapio.plist e insira três linhas de registro: Drinks, Comidas e Sobremesas, todas as
três como o tipo Array. Repare que ao transformá-lo deste tipo, uma nova lista surgirá dentro
dos campos.
Dentro dos arrays, insira os dados ao seu gosto. O resultado deve ser algo similar à figura
abaixo:
Implementação (109)
Agora no arquivo TestePListsViewController.m, procure o método viewDidLoad e
implemente:
NSString *path = [[NSBundle mainBundle] pathForResource:@"cardapio" ofType:@"plist"];
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
A primeira linha retornará o caminho com a localização do arquivo no sistema de arquivos do
seu aparelho. E a segunda, criará um dicionário com os dados contidos na lista.
Em seguida faremos uma varredura nesta lista:
for(NSString *titulo in tmpDict)
{
NSLog(@"%@", titulo);
}
Ao executar do jeito que está, você verá apenas os nomes dos títulos dos dicionários, sem os
itens contidos neles. Agora vamos fazer uma varredura dentro dos arrays. Logo abaixo do
NSLog, insira o código:
for(NSString *item in [tmpDict objectForKey:titulo])
{
NSLog(@">> %@",item);
}
Explicando o que ele faz: os títulos do plist são arrays, logo, ao chamar por eles no dicionário,
teremos os ítens do array. Daí é só listá-los. O código na íntegra:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:@"cardapio"
ofType:@"plist"];
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc]
initWithContentsOfFile:path];
for(NSString *titulo in tmpDict)
{
NSLog(@"%@",titulo);
for(NSString *item in [tmpDict objectForKey:titulo])
{
NSLog(@">> %@",item);
}
}
}
Desafio
Usando seus conhecimentos de UITableViewController e de navegação, implemente este
código de forma que ao invés da saída ser no console, ela ser na tabela, de forma que ao
clicarmos no ítem, ele listar os sub-itens.
Core data(110)
O Core Data é um framework de gerenciamento de dados do SQLite3 que permite a
manipulação de registros num nível de abstração maior, sem a necessidade do uso de consultas
SQL. Seu uso é altamente recomendado pois o risco de haver alguma alteração na sintaxe na
programação em caso de atualização do SQLite é quase nulo.
Vamos demonstrar o funcionamento deste framework na melhor maneira: praticando!
Na prática (111)
Para ilustrar como funciona o Core Data, nada melhor do que fazermos na prática. O
funcionamento é simples e a forma de se trabalhar aqui ilustrada, representa a grande maioria
dos projetos que usam banco de dados.
Baseando-se na classe que criamos no primeiro projeto (classe Contatos), vamos criar um novo
projeto, só que agora com recurso de entrada e saída de dados, e com uso de um modo visual.
Começando
Crie um novo projeto, escolha o Empty Project. Dê o nome de TesteCoreData e certifique-se
que a opção Use Core Data esteja selecionada. Quando você escolhe esta opção, toda a
estrutura de uso do framework já fica automaticamente preparada no projeto.
É necessário seguir alguns passos para configurar a MainWindow do projeto, como já feito
anteriormente, caso haja dúvidas, reveja o tópico Configurando o projeto baseado em
Window localizado na referência (91) do curso.
.
No grupo Frameworks, veja que o CoreData está incluído.
Veja no arquivo AppDelegate.h algumas linhas automaticamente inseridas
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator
*persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
Cada uma das propriedades declaradas representam uma camada do framework como descrito
anteriormente. Há também métodos criados automaticamente. O primeiro (saveContext) salva
toda alteração que você fez no modelo em arquivo e o segundo
(applicationDocumentsDirectory) retorna o objeto NSURL contendo o caminho do diretório
Documents do app.
Criando a tabela (112)
No grupo Supporting Files, veja um arquivo chamado TesteCoreData.xcdatamodeld. Este
arquivo irá conter o modelo de dados padrão do app. Abra-o.
Crie uma entity e dê o nome de Contatos. Uma entity equivale a uma tabela, e cada atributo
equivale a um campo.
Crie dois atributos:
nome, com o tipo String;
idade, com o tipo Integer 16.
Em seguida, salve. Selecione o arquivo TesteCoreData.xcdatamodeld e vá em File > New
File e escolha Managed Object Class (se você usar o Xcode4, esta opção se encontra na guia
CoreData > NSManagedObject subclass). Selecione a entidade Contatos e next, finish. Neste
passo, serão criados dois arquivos, Contatos.h e Contatos.m, que são muito similares àquela
classe que criamos nos primeiros capítulos.
TableListView Controller(113)
Vá em File > New File e na aba iOS > Cocoa Touch escolha UIViewController subclass e
escolha a subclass UITableViewController e desmarque a opção de criar os arquivos XIB. Dê o
nome de ContatoListController.
Abra o arquivo ContatoListController.h e vamos configurar os cabeçalhos do controller.
Primeiro vamos importar a classe que representa o objeto NSObjectContext da entidade
Contatos, que acabamos de criar. Logo após o #import <UIKit/UIKit.h>, digite:
#import "Contatos.h"
Em seguida, vamos declarar duas variáveis de instância. Uma que representará a camada
ManagedObjectContext e a outra, um NSMutableArray que conterá os dados listados na
tabela.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *arrayLista;
E precisamos também torná-las acessíveis, logo, determinarmos um getter e um setter.
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSMutableArray *arrayLista;
Determinaremos agora dois métodos a princípio: um para adicionar um novo registro e um para
listar todos os registros na tabela.
-(void)listaRegistros;
-(void)addNovoRegistro:(id)sender;
Implementação da classe (114)
Agora no arquivo ContatoListController.m, faremos a implementação dos métodos declarados
no header, começando com as propriedades declaradas:
@synthesize managedObjectContext, arrayLista;
Antes de implementarmos os métodos, vamos configurar a nossa view. Faremos a
implementação no métodos viewDidLoad. Abaixo do [super viewDidLoad]:
Primeiro daremos um título para ser exibido na barra de navegação:
self.title = @"Contatos";
Colocaremos agora um botão de + na barra de título. A linha a seguir equivale a adicionarmos
um botão na barra de título e associarmos os métodos no IB:
UIBarButtonItem *addBotao = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self
action:@selector(addNovoRegistro:)];
Dissecando a linha:
UIBarButtonItem *addBotao: Declara a variável addBotao como objeto do tipo
UIBarButtonItem, e em seguida o objeto foi alocado.
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd: Inicializa o objeto como um
botão pré-definido no sistema, do tipo ItemAdd.
target:self: Define o alvo do objeto criado como ele mesmo.
action:@selector(addNovoRegistro:): Determina que o método padrão do objeto ao ser
executado para addNovoRegistro:.
Agora incluímos o botão na barra de navegação:
self.navigationItem.rightBarButtonItem = addBotao;
Configurando a TableListView(115)
Precisamos agora implementar alguns métodos obrigatórios da class UITableViewController,
que são numberOfSectionsInTableView e numberOfRowsInSection.
No primeiro (numberOfSectionsInTableView), temos apenas uma seção no nosso table view,
logo, o código deverá ficar assim:
return 1;
No caso do numberOfRowsInSection, o número de linhas, equivale ao número de linhas no
arrayLista, logo:
return [arrayLista count];
Além disso, precisamos também popular a tabela com os registros contidos no array. Procure o
método cellForRowAtIndexPath. Este método é responsável pela construção de cada célula da
tabela, como já vimos anteriormente. Configure o estilo da célula para “StyleValue1”, dentro de
if (cell == nil) altere o parâmetro de initWithStyle para UITableViewCellStyleValue1.
Agora, na área após o comentário “Configure the cell...”, primeiro instanciaremos o objeto
Contatos com o valor atual da célula listada. Lembrando que todas as instâncias do objeto
Contatos estão alocados no arrayLista.
Contatos *contato = [[self arrayLista] objectAtIndex:[indexPath row]];
Em seguida vamos colocar o campo nome como título da célula, e a idade como um detalhe a
ser exibido.
cell.textLabel setText:[contato nome]];
[cell.detailTextLabel setText:[NSString stringWithFormat:@"%@ anos",[contato idade]]];
Por enquanto é só. Voltaremos mais tarde para definirmos o método acionado ao clicarmos na
célula.
Listando os registros (116)
Agora vamos implementar o método listaRegistros
-(void)listaRegistros
{
}
A primeira coisa que temos que fazer é selecionar qual a entidade que vamos listar. Para isso,
declararemos um objeto do tipo NSEntityDescription.
NSEntityDescription *entContatos = [NSEntityDescription entityForName:@"Contatos"
inManagedObjectContext:managedObjectContext];
Dentro da declaração, em entityForName, passamos o valor Contatos que corresponde ao
nome da entidade criada, e em inManagedObjectContext, passamos o objeto declarado na
propriedade, logo no começo.
Em posse da entidade declarada, temos agora que chamar o método que vai listar estes
registros. Para isso, declaramos um objeto do tipo NSFetchRequest, que receberá os registros da
entidade.
// Declara o objeto "request"
NSFetchRequest *request = [[NSFetchRequest alloc] init];
// Define a entidade para o objeto "request"
[request setEntity:entContatos];
Para uma boa listagem, temos de definir um critério de ordenação. No caso, no código a seguir,
vamos ordenar por nome, em ordem alfabética. Primeiro declaramos um objeto do tipo
NSSortDescriptor, que vai definir qual é o campo a ser ordenado e qual o critério de
ordenação.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"nome"
ascending:YES];
Em seguida, declararemos um NSArray que irá conter a ordenação do objeto.
NSArray *arraySD = [NSArray arrayWithObject:sortDescriptor];
Passemos agora para o objeto request o critério de ordenação.
[request setSortDescriptors:arraySD];
Com tudo já definido, vamos finalmente fazer a listagem dos registros. Para isso, temos de nos
resguardar que alguma coisa errada poderá acontecer no processo, pelas mais diversas razões.
Pensando nisso, toda e qualquer ação de acesso a dados no Core Data está vinculada a um
objeto NSError, mais especificamente, ao ponteiro deste objeto. Ou seja, em caso de qualquer
pane, este objeto terá sido acionado.
NSError *error;
NSMutableArray *fetchResults = [[managedObjectContext executeFetchRequest:request
error:&error] mutableCopy];
Os resultados sempre vêm no objeto do tipo NSMutableArray. Em executeFetchRequest
passamos o objeto request, no qual estivemos trabalhando e no final, passamos o ponteiro do
objeto error.
Herdado do C, um ponteiro é o endereço da memória no qual uma variável está alocada. Toda
vez que declaramos um objeto com um asterisco antes do nome, deixamos claro por compilador
que o endereço desta variável poderá ser acessível. Para termos acesso a este endereço, usa-se o
& antes do nome da variáve
Se tudo deu certo, ótimo, senão, o código a seguir irá informar em algo que houve de errado.
if (!fetchResults)
{
NSLog(@"Algo de errado aconteceu!");
}
Geralmente este erro é muito grave o que pode levar ao fim forçado da execução do app.
Pesquisa feita. Agora vamos passar os resultados para o array que declaramos no cabeçalho
como propriedade.
[self setArrayLista:fetchResults];
Enfim, o código completo do método.
-(void)listaRegistros
{
NSEntityDescription *entContatos = [NSEntityDescription entityForName:@"Contatos"
inManagedObjectContext:managedObjectContext];
// Declara o objeto "request"
NSFetchRequest *request = [[NSFetchRequest alloc] init];
// Define a entidade para o objeto "request"
[request setEntity:entContatos];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"nome"
ascending:YES];
NSArray *arraySD = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:arraySD];
NSError *error;
NSMutableArray *fetchResults = [[managedObjectContext executeFetchRequest:request
error:&error] mutableCopy];
if (!fetchResults) {
NSLog(@"Algo de errado aconteceu!");
}
[self setArrayLista:fetchResults];
}
Chame esta função dentro do método viewDidLoad: do ContatoListController.m
-(void)viewDidLoad
{
[super viewDidLoad];
/*
Implementação anterior
*/
[self listaRegistros];
}
Implementando a navegação (117)
Vamos agora incluir o TableView na window principal, além de implementarmos a navegação.
Mas desta vez, não faremos no Interface Builder, criaremos em linhas de código e veremos
como é simples.
Primeiro vamos adicionar algumas propriedades ao delegate. Vá no arquivo AppDelegate.h e
adicione a seguinte linha nas variáveis de instância:
UINavigationController *navControl;
E logo abaixo:
@property (nonatomic, retain) UINavigationController *navControl;
Abra o arquivo AppDelegate.m e importe a classe ContatoListController
#import "ContatoListController.h"
E implementemos a propriedade do navControl
@synthesize navControl;
Procure o método didFinishLaunchingWithOptions para declarmos o ViewController para ser
inserido na window:
ContatoListController *tabCtrl = [[ContatoListController alloc]
initWithStyle:UITableViewStylePlain];
A linha acima cria um objeto com o estilo mais simples de lista. Precisamos agora informar a
esta view qual é o Managed Object Context do Core Data em vigência no app. Quem
“gerencia” este objeto é a classe delegate, entretanto, já criamos uma propriedade com este tipo
no ViewController, justamente para recebê-la quando for instanciada. Faremos isso!
tabCtrl.managedObjectContext = [self managedObjectContext];
Instancie agora o TableViewController, associado ao navControl
self.navControl = [[UINavigationController alloc] initWithRootViewController:tabCtrl];
Por fim, adicione a view na window.
[self.window addSubview:[self.navControl view]];
Se por curiosidade for dar um Run no programa para vê-lo no simulador, você verá uma lista
vazia com um botão + na barra azul. Se não tiver nada disso, confira os passos anteriores.
Formulário (118)
Vá em File > New File..., na guia iOS > Cocoa Touch > UIViewController subclass. Certifique-
se que a opção de criar um arquivo XIB esteja marcada e que a opção de herdar o
UITableViewController esteja desmarcada. Dê o nome de Formulario.
Hora de diagramar. Abra o arquivo Formulario.xib no IB, e crie 2 labels, 2 textfields e 3
botões. Esta etapa já podes fazer a seu gosto, o importante é conter estes elementos. No final
ficará algo parecido com isso:
Para o textfield de idade, no inspector, dentro do grupo Text Input Traits, localize a opção
Keyboard e selecione Number Pad.
O botão Excluir este registro deve estar ocultado por padrão, pois ao inserir um novo registro,
não faz sentido ele ser exibido. Para isso, selecione o botão, vá no Inspector e na área View,
marque a opção Hidden.
Salve e voltemos aos controllers deste form. No arquivo Formulario.h, declare os componentes:
#import "Contatos.h"
#import "ContatoListController.h"
@interface Formulario : UIViewController {
Contatos *contatoAtual;
ContatoListController *viewParent;
NSManagedObjectContext *managedObjectContext;
IBOutlet UITextField *txtNome;
IBOutlet UITextField *txtIdade;
IBOutlet UIButton *btnSave;
IBOutlet UIButton *btnCancelar;
IBOutlet UIButton *btnExcluir;
}
@property (nonatomic, retain) Contatos *contatoAtual;
@property (nonatomic, retain) ContatoListController *viewParent;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) IBOutlet UITextField *txtNome;
@property (nonatomic, retain) IBOutlet UITextField *txtIdade;
@property (nonatomic, retain) IBOutlet UIButton *btnSave;
@property (nonatomic, retain) IBOutlet UIButton *btnExcluir;
-(IBAction)doSave:(id)sender;
-(IBAction)doCancel:(id)sender;
-(IBAction)doDelete:(id)sender;
@end
Antes de mais nada, precisamos lembrar que devemos passar a instância do
ManagedObjectContext para o formulário para que nele possa ser feita ações no Core Data.
Veja também que foi importado e declarada a propriedade da classe ContatoListController.
Esta declaração é de extrema importância para que se estabeleça uma comunicação entre a view
original e a que está em uso.
Criamos uma propriedade para armazenar qual é o contato atual que está em vigência no
formulário. Significa que, quando o objeto estiver instanciado com algum valor, estamos
editando, senão, inserindo um novo (caso o contatoAtual seja null). Repare que desta vez
declaramos os componentes como propriedades, isso significa que queremos acessá-los pela
classe. E os eventos abaixo correspondem a ação dos botões. Faça as devidas correspondências
no IB (control + arrasta).
Faremos agora torná-la visível. Neste caso, chamaremos ela como uma view modal. As views
modais são aquelas que se sobrepõem a todas outras e com exclusividade na ação, sendo que as
demais views só se tornam disponíveis quando esta se fecha.
Implementando o formulário (119)
Votando agora ao Formulario.m, implementaremos primeiro as propriedades declaradas no
header:
@synthesize managedObjectContext, contatoAtual, txtNome, txtIdade, btnSave, btnExcluir,
viewParent;
Vamos antes implementar o método doCancel que é o mais simples de todos. Precisamos
apenas que o formulário se torne invisível.
-(IBAction)doCancel:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
Dados padrão nos campos (120)
A view Formulario serve para tanto inserir um novo registro como alterar. Logo, para
realizarmos alterações, precisamos que os dados ao serem lidos, estejam presentes nos campos.
Esta condição é definida pela instância da propriedade contatoAtual.
Se a instância da propriedade contatoAtual for nula (nil), então se trata de um novo registro.
Caso contrário, trata-se de uma alteração. Lembrando que a propriedade contatoAtual possui a
instância contato no qual foi selecionado na célula.
No arquivo Fomulario.m, procure pelo método viewDidLoad e caso esteja comentado, retire as
marcações (/* e */). O código ficará asism:
- (void)viewDidLoad {
[super viewDidLoad];
self.txtNome.text = [self.contatoAtual nome];
self.txtIdade.text = [NSString stringWithFormat:@"%d",[[self.contatoAtual idade]
intValue]];
if (self.contatoAtual !=nil)
{
[btnSave setTitle:@"Alterar" forState:UIControlStateNormal];
btnExcluir.hidden = NO;
}
}
Neste caso, as linhas que implementamos preenchem por padrão os valores dos campos do
formulário. O código para ler a idade está mais extenso, pois o valor da idade é do tipo
NSNumber, mas o TextField recebe valores NSString e necessita conversão de NSString para
NSNumber.
Dicas importantes
Há várias formas para convertemos os tipos de NSNumber para NSString e vice-versa, aqui
deixo uma que envolve menos código possível.
NSNumber -> NSString
[NSString stringWithFormat:@"%d",[NSNumberObj intValue]];
NSString -> NSNumber
[NSNumber numberWithInt:[NSStringObj intValue]];
E dentro da condição, verifica se tiver algum registro vinculado ao formulário, o botão Inserir
se chamará Alterar e o botão Excluir se tornará visível.
Salvando o registro (121)
Vamos agora implementar o método doSave, que vai executar a inserção do novo registro. Já
vimos que a condição de alterar ou inserir um novo é definido pela propriedade contatoAtual.
-(IBAction)doSave:(id)sender
{
Contatos *contato;
if (contatoAtual != nil) {
contato = contatoAtual;
}
else {
contato = (Contatos *)[NSEntityDescription
insertNewObjectForEntityForName:@"Contatos"
inManagedObjectContext:managedObjectContext];
}
//.....
}
Criamos um objeto chamado contato e verificamos a condição da propriedade contatoAtual. Se
não estiver definida, será declarado um novo objeto NSEntityDescription chamando o método
que insere um novo registro em branco na entidade. Para evitar conflitos no compilador, foi
reforçado que este NSEntityDescription é uma classe similar a Contatos.
Em seguida vamos definir os valores aos atributos (campos):
[contato setNome:[txtNome text]];
[contato setIdade:[NSNumber numberWithInt:[[txtIdade text] intValue]]];
E comitamos as modificações:
NSError *error;
if(![managedObjectContext save:&error]){
NSLog(@"houve um erro muito grave");
}
Feitas as alterações, precisamos atualizar a lista e claro, fechar a view. Primeiro vamos dar um
refresh na lista de registro, chamando o evento listaRegistros.
[viewParent listaRegistros];
Em seguida, atualizar o conteúdo da tabela
[viewParent.tableView reloadData];
E por fim, fechar a view modal.
[self dismissModalViewControllerAnimated:YES];
Veja o código na íntegra:
-(IBAction)doSave:(id)sender
{
Contatos *contato;
if (contatoAtual != nil) {
contato = contatoAtual;
}
else {
contato = (Contatos *)[NSEntityDescription
insertNewObjectForEntityForName:@"Contatos"
inManagedObjectContext:managedObjectContext];
}
[contato setNome:[txtNome text]];
[contato setIdade:[NSNumber numberWithInt:[[txtIdade text] intValue]]];
NSError *error;
if(![managedObjectContext save:&error]){
NSLog(@"houve um erro muito grave");
}
[viewParent listaRegistros];
[viewParent.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
}
Excluindo o registro (122)
A lógica para excluirmos um registro é bem simples. Lembrando que o botão Excluir só estará
habilitado se houver um registro aberto, logo, não é necessário fazer a verificação.
Primeiramente declaramos o objeto NSManagedObject com o valor do contatoAtual.
Em seguida, solicitamos a exclusão do objeto:
[managedObjectContext deleteObject:contato];
E por fim, comitamos as mudanças, reload na tabela e por fim, fechar o formulário, igual no
método doSave:
NSError *error;
if(![managedObjectContext save:&error]){
NSLog(@"houve um erro muito grave");
}
[viewParent listaRegistros];
[viewParent.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
O código na íntegra:
-(IBAction)doDelete:(id)sender
{
NSManagedObject *contato = contatoAtual;
[managedObjectContext deleteObject:contato];
NSError *error;
if(![managedObjectContext save:&error]){
NSLog(@"houve um erro muito grave");
}
[viewParent listaRegistros];
[viewParent.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
}
Tornando o formulário visível (123)
Voltemos ao ContatoListController.m e vamos implementar agora o addNovoRegistro.
Primeiro vamos importar o Formulario.h para termos acesso a classe.
#import "Formulario.h"
Em seguida, implementaremos o método
-(void)addNovoRegistro:(id)sender
{
Formulario *form = [[Formulario alloc] init];
[form setManagedObjectContext:[self managedObjectContext]];
[form setViewParent:self];
[self presentModalViewController:form animated:YES];
}
No código acima, primeiro declaramos o objeto form como do tipo Formulario, que é o view
controller da view que criamos agora pouco.
Formulario *form = [[Formulario alloc] init];
Em seguida passamos ao form o managedObjectContext e a quem é o view controller pai.
[form setManagedObjectContext:[self managedObjectContext]];
[form setViewParent:self];
E por fim, torná-lo visível de forma modal:
[self presentModalViewController:form animated:YES];
Hora de abrir o registro pela tabela (124)
Procure o método no ContatoListController.m chamado didSelectRowAtIndexPath. Este evento
é executado quando uma célula é selecionada.
A implementação do evento é muito similar ao do addNovoRegistro, a única diferença é que
temos de passar ao Formulario o objeto Contato que está atualmente selecionado, claro, antes
de invocar o método para tornar a view visível:
form.contatoAtual=[arrayLista objectAtIndex:[indexPath row]];
Veja o código na íntegra:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath {
// Navigation logic may go here. Create and push another view controller.
Formulario *form = [[Formulario alloc] init];
form.managedObjectContext = self.managedObjectContext;
form.viewParent = self;
form.contatoAtual=[arrayLista objectAtIndex:[indexPath row]];
[self presentModalViewController:form animated:YES];
}
Pronto! Execute o programa e tente manipular os dados. Se algo deu errado, verifique o código
novamente.
Resumindo (125)
Classes do core data
NSError
Classe que é acionada caso algum erro de execução no app ocorra. Não é uma classe exclusiva
do Core Data, mas toda e qualquer ação a ser executada deverá conter o ponteiro para uma
variável desta classe.
NSManagedObjectContext
Representa a área de dados da sua aplicação, contendo todas as entidades declaradas em um
único arquivo de modelo de dados (aquele no qual fizemos a entidade e os atributos).
Quando o projeto é criado com o Core Data embutido, este objeto já vem instanciado e não há
necessidade de redeclará-lo, apenas passar a instância deste objeto para os demais classes na
qual deseja usá-lo (fizemos isso com o uso das propriedades).
NSEntityDescription
Este objeto representa a entidade a ser usada. Toda vez que alguma ação for feita no banco,
precisa-se antes especificar qual é a entidade (tabela) que será trabalhada.
A declaração deste objeto segue o seguinte padrão:
NSEntityDescription *objeto = [NSEntityDescription entityForName:@"entidade"
inManagedObjectContext:managedObjectContext];
Ou seja, sempre que for usar o core data, antes de mais nada, temos de declarar o Entity
Description.
NSFetchRequest
Equivale a um SELECT do SQL, ou seja, este objeto quando instanciado, retornará uma lista
com os registros contidos numa entidade. Para instanciá-lo:
NSFetchRequest *objeto = [[NSFetchRequest alloc] init];
[objeto setEntity:objetoNSEntity];
Os critérios de busca e ordenação estão definidos pelos objetos NSPredicate e
NSSortDescriptor.
NSPredicate
Não foi usada no exemplo, mas esta classe é de grande importância. É usada para estabelecer
critérios de filtragem na pesquisa. Para usá-la:
NSPredicate *objeto = [NSPredicate predicateWithFormat:@"criterio"];
[objetoNSFetchRequest setPredicate:objeto];
Algumas formas de critério de pesquisa:
Pesquisa simples:
campo == "valor"
campo like "valor"
campo > valor
Operações lógicas:
(campo like "valor") OR (campo like "valor")
(campo like "valor") AND (campo like "valor")
NSSortDescriptor
Usado para ordenar uma pesquisa, de acordo com os critérios de campo e formas de ordenação
(ascendente ou descendente).
Para usá-lo:
NSSortDescriptor *objeto = [[NSSortDescriptor alloc] initWithKey:@"campo"
ascending:YESouNO];
NSArray *objetoArray = [NSArray arrayWithObject:objeto];
[objetoNSFetchRequest setSortDescriptors:objetoArray];
No parâmetro ascending, caso queira ordenar de forma ascendente, use YES senão, use NO
(para descendente).
NSManagedObject
Classe genérica que implementa todos os atributos básicos do modelo do Core Data. Em outras
palavras, é a representação da classe que criamos automaticamente quando criamos o modelo
de dados (no nosso exemplo, o Contatos.h/.m)
Para listar objetos
Cria-se um NSEntityDescription com a entidade na qual se trabalhará
NSEntityDescription *objEntidade = [NSEntityDescription entityForName:@"Entidade"
inManagedObjectContext:managedObjectContext];
Cria-se um objeto NSFetchRequest, que receberá as filtragens
NSFetchRequest *objRequest = [[NSFetchRequest alloc] init];
[objRequest setEntity: objEntidade];
Caso seja necessário, cria-se o NSPredicate e o NSSortDescriptor
NSPredicate *objPredicate = [NSPredicate predicateWithFormat:@"criterio"];
[objRequest setPredicate:objPredicate];
Executa a consulta atribuindo os resultados ao array
NSSortDescriptor *objSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"campo"
ascending:YESouNO];
NSArray *array = [NSArray arrayWithObject: objSortDescriptor];
[objSortDescriptor setSortDescriptors: array];
NSError *error;
NSMutableArray *arrayResultados = [[managedObjectContext executeFetchRequest:
objRequest error:&error] mutableCopy];
Para inserir um novo registro
Declara um NSEntityDescription com o método de inserção de um novo registro
ObjetoEntidade *objEntidade = (ObjetoEntidade *)[NSEntityDescription
insertNewObjectForEntityForName:@"entidade"
inManagedObjectContext:managedObjectContext];
Define os valores dos campos
[objEntidade setCampo1:@"valor1"];
[objEntidade setCampo2:@"valor2"];
Comita as alterações
NSError *error;
if(![managedObjectContext save:&error]){
// O que fazer quando deu um erro...
}
Pra editar um registro existente
O processo é similar ao anterior, só que ao invés de instarmos o objeto requerendo uma nova
linha, usaremos a instância do NSManagedObject com os dados abertos.
ObjetoEntidade *objEntidade = objNSManagedObjectInstanciado;
[objEntidade setCampo1:@"valor1"];
[objEntidade setCampo2:@"valor2"];
...
NSError *error;
if(![managedObjectContext save:&error]){
// O que fazer quando deu um erro...
}
Para excluir um registro
Instancie um NSManagedObject, igual feito no passo anterior
NSManagedObject *objeto = objNSManagedObjectInstanciado;
Solicite ao objeto managedObejectContext para que seja deletado aquela instância
[managedObjectContext deleteObject: objeto];
Comite as alterações
NSError *error;
if(![managedObjectContext save:&error]){
// O que fazer quando deu um erro...
}
XML (126)
Para encerrar a seqüencia deste capítulo, vamos tratar de dados em formato XML. Há vários
métodos diferentes para as mais diversas situações, mas vamos trabalhar com a principal delas,
que é a classe NSXMLParser.
Ao ser instanciado, o objeto varre linha a linha o código, identificando seus parâmetros,
atributos e até mesmo erros. E a cada linha descrita, um evento é acionado para fazer a leitura.
Para utilizar esta classe:
À classe que for trabalhar com o parser, declarar o protocolo
<NSXMLParserDelegate>, que vai importar os métodos relacionados.
Na própria classe, instancia-se o objeto NSXMLParser e define o delegate para ela
própria. Isto significa que todo o gerenciamento do xml será dado pela própria classe.
xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[xmlParser setDelegate:self];
Implementemos os métodos importados do protocolo.
Veremos o funcionamento com maiores detalhes no projeto final.
Métodos do NSXMLParserDelegate (127)
parserDidStartDocument:
Chamado quando o xml começa a ser lido.
-(void)parserDidStartDocument:(NSXMLParser *)parser
parserDidEndDocument:
Chamado quando o parser termina de ler o documento xml.
-(void)parserDidEndDocument:(NSXMLParser *)parser
parser:foundCharacters:
Chamado quando o parser encontra uma sequência de strings determinada.
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
Webview (128)
Webview (da classe UIWebView), é uma view na qual serve de suporte para conteúdo html.
Este conteúdo pode ser tanto carregado online, via url, quanto criado localmente. Possui suporte
para HTML5, CSS3 e Javascript, só não roda Flash, como já é de conhecimento geral o não
suporte pelos dispositivos móveis da Apple. É o mesmo objeto no qual funciona o Safari do
iOS (navegador padrão).
Para demonstrarmos o uso, crie um novo projeto e instancie uma UIWebView (nesta altura
você já deve saber como fazer, né? Caso ainda tenha dúvidas, vide o capítulo sobre Objetos de
interface com o usuário). Os testes abaixo serão realizados dentro do método viewDidLoad do
ViewController.
Operações com a webview (129)
Para abrir uma url de um site
NSString *url_endereco = @"http://suaurlaqui.com";
NSURL *url = [NSURL URLWithString:url_endereco];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[obj_web_view loadRequest:requestObj];
A sequência acima funciona da seguinte maneira:
Declaramos os o objeto NSURL, que servem para manipular URLs absolutas e relativas,
explicaremos melhor a seguir
Fazemos a requisição online desta URL. Esta requisição será armazenada no objeto
NSURLRequest.
Passamos a requisição para o webview.
Para passar valores html para o webview
NSString *html = @"conteudo";
[obj_web_view loadHTMLString:html baseURL:[NSURL fileURLWithPath:[[NSBundle
mainBundle] bundlePath]]];
O primeiro argumento é a string em si com o contúdo html. A segunda mostra qual é o diretório
raiz do documento, no qual os caminhos relativos irão tomar por base. Para entender melhor:
Importe uma imagem para a pasta resources do seu projeto
Na string do seu HTML, coloque <img src=”nome_do_arquivo_da_imagem”>
Use a estrutura acima para abrir o html
Significa que, sem o diretório base definido, a imagem não seria exibida, pois a view não usaria
a raiz do documento como referência.
Para abrir um arquivo html local
[obj_web_view loadRequest:[NSURLRequest requestWithURL:[NSURL
fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"nome_do_arquivo"
ofType:@"html"]isDirectory:NO]]];
Neste caso ele faz uma requisição local do arquivo que seja inserido na pasta Supporting Files
do seu aplicativo.
Classes relacionadas (130)
NSURL
Classe que cuida do gerenciamento de urls, seja elas locais (sistema de arquivos) como online.
Toda requisição de arquivos, seja online ou não, deve passar pela validação deste objeto. A
maneira mais usual de usá-la:
NSURL *url = [NSURL URLWithString:@"string da url"];
Desta forma, você transforma uma string em um objeto NSURL.
NSURLRequest
Representa a requisição de uma URL independente do seu protocolo. Os dados recebidos da url
ficam armazenados neste objeto, podendo ser passado para outros componentes que suportam
este objeto.
Desafio
Usando os seus conhecimentos de objetos de interface, experimente criar um app similar a um
navegador web, usando o campo TextField para indicar o endereço.
Projeto final (131)
Enfim, estamos na reta final do nosso curso. Chegou a hora de juntarmos tudo o que
aprendemos numa aplicação mais completa. Com certeza não usaremos 100% do que foi visto,
mas nada que impede que depois de concluído, você possa usá-los com o fim de enriquecer seu
aplicativo.
Como projeto final, iremos fazer um leitor de feeds RSS, de forma que você possa agregar seus
conteúdos preferidos num aplicativo simples.
Planejando o app (132)
Antes de colocarmos as mãos no código, vamos pensar um pouco no funcionamento do que
teremos no app e como melhor otimizar as telas para facilitar a navegação do usuário. E claro,
para que na hora de programar, termos um direcionamento mais preciso.
Neste app trabalharemos com duas divisões distintas: uma com as últimas postagens e uma com
o gerenciamento dos feeds. Pensando um pouco mais a frente, todas as abas cairão numa view
em comum, a qual que contem o conteúdo da postagem.
A estrutura pensada neste app segue o esquema abaixo:
Pense também que seria bom que o usuário pudesse ler offline os posts. Logo, a partir da leitura
do xml do RSS, tudo seria armazenado no banco de dados local. E, a partir disso, poder
manipular as preferências, como marcar como lido, marcar como favorito, enfim, as ideias são
infinitas.
O programa que iremos fazer neste projeto é apenas a base, o essencial para que você já tenha
uma noção do ciclo de criação de um aplicativo. Se você já tem experiência em outras áreas de
desenvolvimento, verá que não é muito diferente ou até mais simples do que já está
acostumado.
Mãos a obra! (133)
Crie um novo projeto Empty Application e dê o nome de LeitorRSS. Tenha certeza de que
deixou marcado que deseja usar o Core Data Framework.
Siga os passos para criar a MainWindow conforme descritas na referência (91).
Exemplo: Na prática (91)
No grupo Resources, você vai encontrar um arquivo chamado LeitorRSS.xcdatamodeld. Abra-o
e vamos criar nosso modelo de dados.
Modelo de dados (134)
Vamos pensar que temos dois tipos de entidades a serem criadas: uma contendo as urls dos
feeds cadastradas e uma outra com os posts em si, relacionados diretamente à url de onde foi
puxada.
Para as urls, vamos considerar dois campos: um contendo a url e outra contendo o nome do
site que virá ao puxarmos os dados do RSS. Já para os posts, precisaremos essencialmente dos
dados do título, conteúdo, data de publicação, além do guid e da identificação de origem do
feed, que no nosso exemplo usaremos a url de onde saiu.
No arquivo xcdatamodeld, crie uma entidade chamada Urls, com os seguintes atributos:
nome - String
url - String
Crie uma outra identidade com o nome de Posts, com os seguintes atributos:
feed_url - String
guid - String
titulo - String
conteudo - String
data_publicacao - Date
Caso tenha dúvida de como isso é feito, reveja este tópico no arquivo de Core Data.
No final, você terá uma estrutura similar a essa:
Temos agora de criar as classes para gerenciar essas entidades. Caso não se lembre, reveja aqui.
Faça estes passos para ambas as entidades. No final você terá dois pares de arquivos: Posts e
Urls.
Leitura dos feeds (135)
Trataremos a leitura de feeds num objeto a parte, para que esta funcionalidade se estenda a
outras partes do app. Este objeto irá ler o conteúdo XML da URL inserida e fará o rastreio no
arquivo inserindo no banco de dados.
Estrutura do RSS (136)
Antes de criarmos nosso objeto, veremos como funciona a estrutura do XML. O RSS (Really
Simple Syndication) é um formato de publicação em XML que é muito usada para compartilhar
conteúdo num formato aberto e comum.
O arquivo segue a seguinte estrutura:
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>titulo do site</title>
<description>Descrição do canal RSS</description>
<link>http://enderecoprosite.com.br</link>
<lastBuildDate>Mon, 01 Aug 2011 00:01:00 +0000 </lastBuildDate>
<pubDate>Mon, 01 Aug 2011 00:01:00 +0000 </pubDate>
<item>
<title>Titulo do post</title>
<description>Conteudo do feed</description>
<link>http://url.para.o.post</link>
<guid>string única identificadora</guid>
<pubDate>Mon, 06 Sep 2009 16:45:00 +0000 </pubDate>
</item>
</channel>
</rss>
No começo as declarações do XML e do RSS. Em seguida temos a tag CHANNEL, que inicia
as identificações do canal RSS, como título, endereço do site, descrição, data de publicação,
etc.
Abaixo, começam as séries das tags ITEM. Cada post publicado no feed estará dentro de um
grupo individual da tag ITEM. Basicamente seu conteúdo contem o TITLE (título),
DESCRIPTION (conteúdo do feed), LINK (endereço para o post diretamente), GUID
(identificador único do post no feed) e PUBDATE, com a data de publicação. Podem haver
outras tags dependendo do gerenciador de conteúdo for usado, mas basicamente são essas
informações.
Objetos relacionados (137)
Uma boa prática, para quando fizer armazenamento de dados bidimensionais, é criar uma classe
auxiliar para guardar estes valores. Desta forma, além de facilitar o acesso invocando de uma
única fonte, você pode incluir n outras funções para o tratamento dos dados.
Como o nosso parser vai ler uma série de grupos da tag item, logo, precisamos de um meio para
armazenar estes valores para que depois possamos processá-los no banco de dados.
Crie num novo arquivo do tipo NSObject e dê o nome de FeedPosts. Vamos declarar agora nos
cabeçalhos os campos nos quais precisamos armazenar no objeto:
NSString *titulo;
NSString *conteudo;
NSString *pubData;
NSString *guid;
Apesar do campo pubData ser armazenada no banco de dados como tipo Date, vamos a
princípio tratá-la como uma string, para depois cuidarmos das burocracias de conversão quando
precisarmos.
Esses valores têm de estar acessíveis a outras classes. Logo, vamos declará-los como
propriedades:
@property (nonatomic, retain) NSString *titulo;
@property (nonatomic, retain) NSString *conteudo;
@property (nonatomic, retain) NSString *pubData;
@property (nonatomic, retain) NSString *guid;
E por fim, precisamos criar um método inicializador, no qual vai receber todos estes valores na
hora instanciarmos o objeto. No nosso exemplo, ficaria algo como:
-(id)initWithTitulo:(NSString *)tit conteudo:(NSString *)cont pubData:(NSString *)pubd
guid:(NSString *)g;
Sendo que todos os valores dos campos estão contidos na mesma função.
Implementações (138)
Agora precisamos fazer a implementação da classe. No arquivo FeedPosts.m, implemente as
propriedades declaradas:
@synthesize titulo, conteudo, pubData, guid;
Vamos também declarar o nosso método inicializador, atribuindo para as variáveis de instância,
os valores passados:
-(id)initWithTitulo:(NSString *)tit conteudo:(NSString *)cont pubData:(NSString *)pubd
guid:(NSString *)g
{
if (self == [super init])
{
self.titulo = tit;
self.conteudo = cont;
self.pubData = pubd;
self.guid = g;
}
return self;
}
Parser (139)
Vamos agora à classe que vai fazer o tratamento do XML. Crie uma nova classe NSObject
chamada XMLParser, e vamos editar os cabeçalhos.
Primeiramente, precisamos de declarar que esta classe terá que responder a algumas funções do
NSXMLParser nativamente em sua estrutura. Para isso, teremos de declarar o protocolo
NSXMLParserDelegate.
@interface XMLParser : NSObject <NSXMLParserDelegate>
Em seguida vamos declarar as variáveis no qual vamos usar na leitura do feed. Em primeiro
lugar, precisamos de uma String que vá armazenar a URL atual que está sendo processada:
NSString *urlFeed;
Também a lista de itens listados no feed:
NSMutableArray *items;
Como o parser lê arquivo linha a linha e processa em lote, precisamos demarcar checkpoints
durante a leitura, para determinarmos quando começamos ou terminarmos ler uma seção do
feed. Para isso, vamos delimitar duas áreas em especial: o Channel e o Item. O Channel será
lido no começo e vai se encerrar quando iniciarmos a leitura do primeiro item. E a cada item,
terá o seu controle de início e fim. Veremos mais adiante o funcionamento mais claramente,
mas agora, vamos declarar estas variáveis:
BOOL channelElementInProgress;
BOOL itemElementInProgress;
Precisamos também de uma string global que dará acesso ao conteúdo presente dentro de uma
tag. Precisaremos que esta seja do tipo MutableString pois este valor irá se alterar
constantemente.
NSMutableString *dadoAtual;
E pro fim, algumas variáveis auxiliares que armazenarão os dados durante a leitura.
NSString *nomeFeed;
NSString *tempTitulo;
NSString *tempConteudo;
NSString *tempPubData;
NSString *tempGuid;
E não podemos esquecer do acessor ao Managed Object Context do Core data, que irá fazer as
devidas conexões ao banco de dados.
Sendo que o nomeFeed irá armazenar o valor do title dentro de channel e o bloco a seguir os
valores contidos no item em questão.
NSManagedObjectContext *moc;
Implementação e init (140)
Precismos declarar alguns deste itens como propriedades:
@property BOOL channelElementInProgress;
@property BOOL itemElementInProgress;
@property (nonatomic, retain) NSString *urlFeed;
E por fim, os métodos da classe:
-(BOOL)parse;
-(id)initWithUrlFeed:(NSString *)url;
Sendo que o primeiro será o que irá de fato fazer com que o processamento comece e o seguinte
o inicializador, passando na instância do objeto o valor da url.
Vamos agora à implementação da classe. Abra o arquivo XMLParser.m e vamos implementar
as propriedades:
@synthesize urlFeed, itemElementInProgress, channelElementInProgress;
Em seguida, vamos declarar o inicializador. Um detalhe diferente é que desta vez vamos
declarar puxando diretamente do AppDelegate, algo que não fizemos ainda durante o curso.
Para tal, primeiro vamos importar a classe:
#import "AppDelegate.h"
Para declararmos os AppDelegate, seguiremos a instrução abaixo:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Desta forma, o objeto appDelegate passará a conter as propriedades e métodos públicos do
delegate. Vamos agora pegar o valor do Managed Object Context:
moc = appDelegate.managedObjectContext;
E por fim, atribuir ao urlFeed o valor do método incializador e alocar o array items.
urlFeed = url;
items = [[NSMutableArray alloc] init];
No final, o código estará assim:
-(id)initWithUrlFeed:(NSString *)url
{
if (self == [super init])
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]
delegate];
moc = appDelegate.managedObjectContext;
urlFeed = url;
items = [[NSMutableArray alloc] init];
}
return self;
}
didStartElement: (141)
Agora vamos aos métodos herdados do protocolo NSXMLParserDelegate. O primeiro que
veremos será o didStartElement: que faz o tratamento da tag inicial.
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
}
Neste método, a lógica funcionará da seguinte forma:
Quando a tag <channel> for identificada, inicia-se o tratamento dos dados como channel.
if ([elementName isEqualToString:@"channel"])
{
[self setChannelElementInProgress:YES];
}
Quando a primeira tag <item> for identificada, significa que o tratamento do channel se encerra
e passa a tratar o conteúdo dos posts.
if ([elementName isEqualToString:@"item"])
{
[self setChannelElementInProgress:NO];
[self setItemElementInProgress:YES];
}
Sendo que setChannelElementInProgress e setItemElementInProgress são os setters das
propriedades do tipo BOOL antes declaradas e já comentadas.
No fim, o código do método completo.
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:@"channel"])
{
[self setChannelElementInProgress:YES];
}
if ([elementName isEqualToString:@"item"])
{
[self setChannelElementInProgress:NO];
[self setItemElementInProgress:YES];
}
}
foundCharacters: (142)
O método a seguir é o foundCharacters:. Este método pega os valores dos caracteres contidos
dentro da tag ativa, passando linha a linha.
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
}
A lógica envolvida neste método é simples. Declaramos nos cabeçalhos a mutable string
dadoAtual. Ela é quem vai receber o valor. Primeiro verificamos se ela já está alocada na
memória:
if(!dadoAtual)
{
dadoAtual = [[NSMutableString alloc] init];
}
E em seguida, passamos os valores registrados no evento a esta variável.
[dadoAtual appendString:string];
Simples! Os dados desta variável serão usadas no método a seguir, mas antes disso, o código do
método:
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if(!dadoAtual)
{
dadoAtual = [[NSMutableString alloc] init];
}
[dadoAtual appendString:string];
}
didEndElement: (143)
E agora vamos trabalhar com o método didEndElement:. Este é invocado toda vez que detecta
um encerramento de uma tag: </item>, </channel>, etc.
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
}
Esta implementação será um pouco mais longa, pois é nela que vai conter as demais instruções
de gravação no banco de dados, etc.
Título do feed (144)
Vamos começar com o tratamento do channel, detectando se a tag </title> foi invocada
enquanto o channelElementInProgress era YES. O que será feito é simplesmente atribuir o
valor de dadoAtual ao nomeFeed, que corresponde ao nome do site.
if (self.channelElementInProgress)
{
if ([elementName isEqualToString:@"title"])
{
nomeFeed = [dadoAtual stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
}
}
Sendo que o método stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet] retira os espaços excedentes. Equivale ao trim de
outras linguagens de programação.
Fechamento da tag item (145)
Agora vamos com o tratamento do fechamento da tag ITEM (</item>). Esta simboliza o fim do
carregamento das informações no post. O que temos de fazer neste bloco:
if ([elementName isEqualToString:@"item"])
{
}
Marcar que o item não está mais em progresso
[self setItemElementInProgress:NO];
Atribuir os valores das tags ao objeto
FeedPosts *feedP = [[FeedPosts alloc]initWithTitulo:tempTitulo conteudo:tempConteudo
pubData:tempPubData guid:tempGuid];
Os valores envolvidos serão declarados a seguir.
Inserir o objeto no array items.
[items addObject:feedP];
Este bloco ficará assim:
if ([elementName isEqualToString:@"item"])
{
[self setItemElementInProgress:NO];
FeedPosts *feedP = [[FeedPosts alloc]initWithTitulo:tempTitulo conteudo:tempConteudo
pubData:tempPubData guid:tempGuid];
[items addObject:feedP];
}
Dados dos posts (146)
Agora vamos tratar as tags do post individualmente, caso o itemElementInProgress esteja
YES. Em cada tag seguirá a mesma estrutura da que adotamos quando atribuimos os valores à
variável nomeFeed.
Verifique se o itemElementInProgress está como YES.
if (self.itemElementInProgress)
{
}
Dentro da condição, verificaremos se a tag foi fechada, e caso sim, atribuiremos o valor do
dadoAtual à sua variável temporária declarada no cabeçalho.
if ([elementName isEqualToString:@"title"])
{
tempTitulo = [dadoAtual stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
}
Repita esses passos às demais tags. No final, este bloco ficará assim:
if (self.itemElementInProgress)
{
if ([elementName isEqualToString:@"title"])
{
tempTitulo = [dadoAtual stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
}
if ([elementName isEqualToString:@"pubDate"])
{
tempPubData = [dadoAtual stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
}
if ([elementName isEqualToString:@"description"])
{
tempConteudo = [dadoAtual stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
}
if ([elementName isEqualToString:@"guid"])
{
tempGuid = [dadoAtual stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
}
}
Fechamento da tag RSS (147)
E por fim, o tratamento do fechamento da tag RSS, que é a última tag do xml. Ela simboliza o
fim do arquivo, ou seja, depois dela, todos os dados colhidos poderão ser processados. No
nosso exemplo, vamos inserir no banco de dados o que colhemos e guardamos nas variáveis
antes declaradas.
Antes de começarmos a condição, vamos declarar o objeto NSError que é requerido pelas
funções do core data. Em seguida, inciaremos as condições:
NSError *error;
if ([elementName isEqualToString:@"rss"])
{
}
Verificação do cadastro do feed (148)
O primeiro passo é verificar se o feed já está cadastrado no banco, para evitarmos dados
duplicados.
Declarar o objeto NSEntityDescription, chamando a entidade Urls.
NSEntityDescription *entCheckUrl = [NSEntityDescription entityForName:@"Urls"
inManagedObjectContext:moc];
Declarar o objeto NSFetchRequest, que buscará os dados contidos na entidade
NSFetchRequest *reqCheckUrl = [[NSFetchRequest alloc] init];
[reqCheckUrl setEntity:entCheckUrl];
Adicionar a condição na consulta, caso a url já esteja cadastrada no banco de dados.
NSPredicate *predCheckUrl = [NSPredicate predicateWithFormat:@"url == %@",urlFeed];
[reqCheckUrl setPredicate:predCheckUrl];
Criar um array contendo os dados da busca
NSMutableArray *arrayCheckUrl = [[moc executeFetchRequest:reqCheckUrl error:&error]
mutableCopy];
Cadastro do feed (149)
Agora, a lógica é a seguinte: caso o número de itens presentes arrayCheckUrl seja 0, significa
que o registro não existe, logo, podemos prosseguir com a adição dos dados.
if ([arrayCheckUrl count] == 0)
{
}
Declara a classe Urls, associada a classe NSEntityDescription.
Urls *entUrls = (Urls *)[NSEntityDescription insertNewObjectForEntityForName:@"Urls"
inManagedObjectContext:moc];
Atribui os valores do feed ao objeto
[entUrls setNome:nomeFeed];
[entUrls setUrl:urlFeed];
Comita as informações
if(![moc save:&error]){
}
Este bloco na íntegra:
NSEntityDescription *entCheckUrl = [NSEntityDescription entityForName:@"Urls"
inManagedObjectContext:moc];
NSFetchRequest *reqCheckUrl = [[NSFetchRequest alloc] init];
[reqCheckUrl setEntity:entCheckUrl];
NSPredicate *predCheckUrl = [NSPredicate predicateWithFormat:@"url == %@",urlFeed];
[reqCheckUrl setPredicate:predCheckUrl];
NSMutableArray *arrayCheckUrl = [[moc executeFetchRequest:reqCheckUrl error:&error]
mutableCopy];
if ([arrayCheckUrl count] == 0)
{
Urls *entUrls = (Urls *)[NSEntityDescription insertNewObjectForEntityForName:@"Urls"
inManagedObjectContext:moc];
[entUrls setNome:nomeFeed];
[entUrls setUrl:urlFeed];
if(![moc save:&error]){
}
}
Verificação dos posts do feed (150)
E faremos agora a varredura pelos posts do feed, seguindo o mesmo esquema anterior,
passando item por item contido no array.
for (FeedPosts *fp in items)
{
}
Dentro do loop, vamos verificar se o registro já está cadastrado, verificando pelo guid e pelo
feed_url.
NSEntityDescription *entChecPost = [NSEntityDescription entityForName:@"Posts"
inManagedObjectContext:moc];
NSFetchRequest *reqCheckPost = [[NSFetchRequest alloc] init];
[reqCheckPost setEntity:entChecPost];
NSPredicate *predCheckPost = [NSPredicate predicateWithFormat:@"guid == %@ AND
feed_url == %@",[fp guid], nomeFeed];
[reqCheckPost setPredicate:predCheckPost];
NSError *error;
NSMutableArray *arrayCheckPost = [[moc executeFetchRequest:reqCheckPost error:&error]
mutableCopy];
Cadastro dos posts (151)
Em seguida, caso o arrayCheckPost seja 0, prossiga. Mas antes, vamos falar de um tópico
especial que é a conversão da data do formato NSString para o formato NSDate.
Vejamos um exemplo de como é o valor da data de publicação no RSS:
Sun, 31 Jul 2011 14:36:38 +0000
Para que este valor seja aceito no objeto NSDate, precisamos antes determinar o parâmetro de
formatação da data no objeto NSDateFormater que, ao receber este valor, irá sem problemas
fazer esta conversão.
Primeiro declaramos o objeto do tipo NSDateFormater
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
Estabelecer o local no qual a data será calculada. Esta função é apenas uma formalidade
do objeto, pois o timezone já está definido na data (+0000).
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[dateFormatter setLocale: usLocale];
Estabelecer o formato da data
[dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss Z"];
Realiza a conversão
NSDate *pubdate = [dateFormatter dateFromString:[fp pubData]];
O código do bloco na íntegra (152)
for (FeedPosts *fp in items)
{
NSEntityDescription *entChecPost = [NSEntityDescription entityForName:@"Posts"
inManagedObjectContext:moc];
NSFetchRequest *reqCheckPost = [[NSFetchRequest alloc] init];
[reqCheckPost setEntity:entChecPost];
NSPredicate *predCheckPost = [NSPredicate predicateWithFormat:@"guid == %@
AND feed_url == %@",[fp guid], nomeFeed];
[reqCheckPost setPredicate:predCheckPost];
NSMutableArray *arrayCheckPost = [[moc executeFetchRequest:reqCheckPost
error:&error] mutableCopy];
if ([arrayCheckPost count] == 0)
{
Posts *entPosts = (Posts *)[NSEntityDescription
insertNewObjectForEntityForName:@"Posts" inManagedObjectContext:moc];
[entPosts setFeed_url:urlFeed];
[entPosts setGuid:[fp guid]];
[entPosts setTitulo:[fp titulo]];
[entPosts setConteudo:[fp conteudo]];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *usLocale = [[NSLocale alloc]
initWithLocaleIdentifier:@"en_US"];
[dateFormatter setLocale: usLocale];
[dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss Z"];
NSDate *pubdate = [dateFormatter dateFromString:[fp pubData]];
[entPosts setData_publicacao:pubdate];
if(![moc save:&error]){
}
}
}
E finalmente, zerar o objeto dataAtual que deve constar na última linha do método,antes de
fechar as chaves }
dadoAtual = nil;
parse: (153)
Agora finalmente o método parse:, que fará com que toda a seqüência acima ser executada.
-(BOOL)parse
{
}
Converter a url de string para NSURL
NSURL *url = [[NSURL alloc] initWithString:[self urlFeed]];
Instanciar o objeto NSXMLParser
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
Enviar o comando para executar a varredura
[parser parse];
Fazer o retorno do método
return YES;
A view principal (154)
Abra o arquivo MainWindow.xib e adicione um Tab Bar controller. Exclua os dois view
controllers que veio como padrão e no lugar coloque dois Navigation View Controller. Em
seguida conecte o Window ao rootViewController.
Aos tabs criados, dê respectivamente os títulos Novidades e Feeds.
Substitua o View Controller dos itens por TableViewController.
Em seguida, dê os mesmo títulos ao NavigationTitleBar dos respectivos views.
Na segunda view, adicione um Bar Button Item. No inspector, coloque a propriedade Identifier
para Add.
Adicione imagens ao seu gosto para ilustrar os tabs. As que eu usei neste exemplo, você pode
baixar aqui.
View EditFeeds (155)
Vamos criar um novo view para que possamos cadastrar os feeds. Crie um novo
ViewController com o nome de EditFeeds. Abra o EditFeeds.xib e coloque deixe-o assim:
A disposição acima é uma sugestão. Apenas ilustrando uma forma de dispor os elementos.
Controller EditFeeds (156)
Abra agora o EditFeeds.h, e vamos declarar os outlets e os métodos.
Primeiro, precisamos determinar qual vai ser a janela-pai desta view, para que possamos manter
a conexão entre as classes. Primeiro importe a classe FeedsController.
#import "FeedsController.h"
Em seguida, declarar os outlets e métodos
IBOutlet UITextField *txtFeed;
FeedsController *parent;
@property (nonatomic, retain) FeedsController *parent;
-(IBAction)doSave:(id)sender;
-(IBAction)fechar:(id)sender;
E agora no arquivo EditFeeds.m, iremos implementar a classe, primeiramente implementando a
propriedade parent
@synthesize parent;
E agora, vamos ao evento fechar:. Vale frisar que esta view será exibida como Modal
-(IBAction)fechar:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
E agora o evento de salvar o feed. Lembre-se que o responsável por isso é o objeto XMLParser.
Tratar a url inserida
NSString *feedURL = [[NSString alloc] initWithFormat:@"http://%@",[txtFeed text]];
Chamar o objeto XMLParser
XMLParser *xmlp = [[XMLParser alloc] initWithUrlFeed:feedURL];
Executar a verificação no xml
[xmlp parse];
E por fim, fechar a view
[self dismissModalViewControllerAnimated:YES];
O código completo
-(IBAction)doSave:(id)sender
{
NSString *feedURL = [[NSString alloc] initWithFormat:@"http://%@",[txtFeed text]]
XMLParser *xmlp = [[XMLParser alloc] initWithUrlFeed:feedURL];
[xmlp parse];
[self dismissModalViewControllerAnimated:YES];
}
Controller da lista de feeds (157)
Agora vamos trabalhar na lista de feeds. Crie uma nova classe TableViewController, mas sem o
arquivo da view chamada FeedsController. Associe esta class na TableView que colocamos no
Tab Bar.
Nos cabeçalhos, vamos declarar os objetos necessários para a nossa classe, começando pelo
nosso array que fará a listagem dos itens da tabela, além do objeto Managed Object Context.
NSMutableArray *listaFeed;
NSManagedObjectContext *moc;
Declare agora o array como propriedade para que ela se torne acessível a outras classes:
@property (nonatomic, retain) NSMutableArray *listaFeed;
E por fim, os eventos associados a classe. Primeiro o evento que vai responder ao botão + que
colocamos na view e também o evento que fará a leitura dos dados.
-(IBAction)novoFeed:(id)sender;
-(void)abrirDados;
Faça a conexão do botão + ao controller FeedsController.
Implementação do controller (158)
Agora na implementação, veremos as classes que estão associadas a este controller. Começando
pelo formulário de novo feeds, que será aberto ao clicarmos no botão +, além do AppDelegate,
das Urls que serão listadas:
#import "EditFeeds.h"
#import "AppDelegate.h"
#import "Urls.h"
Agora o método para chamar o formulário modal, passando a instância do view controller para
ele.
-(IBAction)novoFeed:(id)sender
{
EditFeeds *editFeeds = [[[EditFeeds alloc] init] autorelease];
[editFeeds setParent:self];
[self presentModalViewController:editFeeds animated:YES];
}
Até então passei rápido nestas declarações pois elas já não são novidade, são coisas já muito
exploradas nos capítulos anteriores.
abreDados: (159)
Iremos agora implementar o método abreDados: que fará a listagem do banco de dados no table
view.
-(void)abrirDados
{
}
No método, primeiro vamos declarar o objeto Managed Object Context para fazermos a
conexão com o banco e inicializarmos o array listaFeed.
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
moc = appDelegate.managedObjectContext;
listaFeed = [[NSMutableArray alloc] init];
Em seguida, objeto com a entidade Urls, invocando a listagem e a ordenação por ordem
alfabética:
NSEntityDescription *objUrls = [NSEntityDescription entityForName:@"Urls"
inManagedObjectContext:moc];
NSFetchRequest *urlReq = [[NSFetchRequest alloc] init];
[urlReq setEntity: objUrls];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"nome"
ascending:YES];
NSArray *arraySD = [NSArray arrayWithObject:sortDescriptor];
[urlReq setSortDescriptors:arraySD];
NSError *error;
NSMutableArray *arrayResultados = [[moc executeFetchRequest:urlReq error:&error]
mutableCopy];
Enfim, atribuir os resultados no array listaFeed, limpar o objeto NSFetchRequest da memória e
por fim, atualizar a lista.
listaFeed = arrayResultados;
[self.tableView reloadData];
Precisamos listar os dados já contidos no banco de dados assim que abrimos a view, logo, no
viewWillAppear:
[self abrirDados];
Configurações da tabela (160)
Agora para configurar os dados do table view, em numberOfSectionsInTableView:
return 1;
No evento numberOfRowsInSection:
return [listaFeed count];
E no evento cellForRowAtIndexPath, configuraremos a exibição dos dados. Logo abaixo da
linha comentada, primeiramente vamos colocar o indicador que ao clicarmos, há algo a mais a
ser visto:
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
E o título da célula, que será puxado diretamente da propriedade nome do objeto Urls contido
no array listaFeed.
cell.textLabel.text = [[listaFeed objectAtIndex:[indexPath row]] nome];
Por enquanto paramos por aqui neste arquivo. Voltaremos nele logo mais quando criamos as
demais views.
View para lista de posts (161)
Nesta view vamos listar os posts de um feed cadastrado. Assim como no FeedsController, crie
um TableViewController com o nome de PostsController, desmarcando a opção de criar o
arquivo XIB. Abra o arquivo PostsController.h e vamos editar os cabeçalhos.
Cabeçalhos (162)
Esta view vai receber um objeto do tipo Urls para que seja feita a filtragem. Logo, precisamos
importar o objeto Urls e o App Delegate, para termos acesso aos dados.
#import "Urls.h"
#import "AppDelegate.h"
Em seguida, declararemos três objetos: o array com as listas, o objeto Urls que vai ser recebido
pelo FeedsController e o Managed Object Context.
NSMutableArray *items;
Urls *urlAtual;
NSManagedObjectContext *moc;
Por fim, precisamos que tanto a lista quanto o objeto urlAtual sejam acessíveis. Logo vamos
declarar como propriedade:
@property (nonatomic, retain) NSMutableArray *items;
@property (nonatomic, retain) Urls *urlAtual;
Implementação (163)
Nesta view vamos precisar passar para a view de visualização, o objeto posts. Logo, vamos
importá-lo para a classe:
#import "Posts.h"
E implementaremos as propriedades.
@synthesize items, urlAtual;
Em seguida, assim como fizemos no FeedsController, vamos inicializar o objeto declarando o
Managed Object Context e o array items.
-(id)init
{
if (self == [super init])
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]
delegate];
moc = appDelegate.managedObjectContext;
items = [[NSMutableArray alloc] init];
}
return self;
}
Lista de posts (164)
Ao exibirmos a view, vamos fazer a listagem dos posts contidos. Logo, vamos proceder
diretamente no método viewDidLoad:. O processo vai ser similar ao que fizemos no método
abreDados: no FeedsController. A grande diferença é que faremos a filtragem da entidade
Posts, usando como critério o atributo feed_url, ordenado pela data de publicação do mais
novo para o mais antigo.
NSEntityDescription *objPosts = [NSEntityDescription entityForName:@"Posts"
inManagedObjectContext:moc];
NSFetchRequest *reqPosts = [[NSFetchRequest alloc] init];
[reqPosts setEntity:objPosts];
NSPredicate *predPosts = [NSPredicate predicateWithFormat:@"feed_url == %@",[urlAtual
url]];
[reqPosts setPredicate:predPosts];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@"data_publicacao" ascending:NO];
NSArray *arraySD = [NSArray arrayWithObject:sortDescriptor];
[reqPosts setSortDescriptors:arraySD];
NSError *error;
NSMutableArray *arrayResultados = [[moc executeFetchRequest:reqPosts error:&error]
mutableCopy];
if (!reqPosts) {
}
items = arrayResultados;
[reqPosts release];
E vamos colocar o título da view para o nome do feed url:
[self setTitle:[urlAtual nome]];
Configuração da tabela (165)
Configurando a tabela agora, vamos aos métodos:
numberOfSectionsInTableView:
return 1;
numberOfRowsInSection:
return [items count];
cellForRowAtIndexPath:
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [[items objectAtIndex:[indexPath row]] titulo];
Logo mais vamos implementar o método didSelectRowAtIndexPath. Mas antes disso, vamos
fazer esta view se tornar visível.
Abrindo esta view (166)
Voltaremos ao arquivo FeedsController.m e vamos implementar o método
didSelectRowAtIndexPath desta classe. Mas antes disso, importaremos a classe
PostsController.
#import "PostsController.h"
No método didSelectRowAtIndexPath vamos chamar esta view, passando a instância do
objeto Urls da linha selecionada ao PostsController.
PostsController *pcontrol = [[PostsController alloc] init];
[pcontrol setUrlAtual:[listaFeed objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:pcontrol animated:YES];
View de exibição do post (167)
Esta view será focada em exibir o post. Para isso, vamos usar o webview, que nos proporciona
maior mobilidade no tamanho do conteúdo. Crie uma nova classe ViewController, marcando a
opção de criar o arquivo XIB e dê o nome de Postagem.
Abra o arquivo Postagem.xib e inclua um Web View, cobrindo toda a área da view. Em
seguida vamos implementar a classe.
Cabeçalhos (168)
Primeiro os cabeçalhos. Precisamos declarar dois objetos: O outlet com o webview e o objeto
Posts, que vai receber a instância desta classe com os dados a serem exibidos. Logo, precisamos
importar o objeto Posts:
#import "Posts.h"
Em seguida os objetos e as propriedades:
IBOutlet UIWebView *webConteudo;
Posts *post;
@property (nonatomic, retain) Posts *post;
Implementação (169)
Na implementação do método, vamos instanciar a propriedade:
@synthesize post;
Faça as conexões dos objetos da classe com a interface. Em seguida, faremos que ao exibirmos
a view, os dados sejam carregados em formato html, no método viewDidLoad:
Declarar uma string com o html para o título:
NSString *html_titulo = [[NSString alloc] initWithFormat:@"<h1>%@</h1>",[post titulo]];
Declarar outra string com o conteúdo, que ficará dentro de uma DIV com a class
conteudo.
NSString *html_conteudo = [[NSString alloc] initWithFormat:@"<div
class=\"conteudo\">%@</div>", [post conteudo]];
Inserir ambas as strings para o objeto WebView.
[webConteudo loadHTMLString:[NSString stringWithFormat:@"%@%@", html_titulo,
html_conteudo] baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
Colocar o título da view com o título do post.
[self setTitle:[post titulo]];
Formatação CSS (170)
A exibição do html está sem formatação, com o estilo padrão. Uma das vantagens de
trabalharmos com o webview é a possibilidade de usarmos todos os recursos de formatação
web, entre eles javascript e CSS.
Crie um arquivo chamado postagem.css. Deixe-o no grupo Supporting Files para fins de
organização. O código do CSS usado no exemplo foi:
h1, div {
font-family:Helvetica;
}
div.conteudo {
color:#666;
}
Para importar o css para o webview, usaremos recursos html, com o artifício do código do
objective c.
Antes da string html_titulo, crie uma string chamada html_css, que conterá a tag <link> que
fará a importação do css ao html
NSString *html_css = @"<link href=\"postagem.css\" rel=\"stylesheet\" type=\"text/css\" />";
Na linha onde tem a configuração do webConteudo, adicione esta variável para ser exibida no
conteúdo, modificando para:
[webConteudo loadHTMLString:[NSString stringWithFormat:@"%@%@%@", html_css,
html_titulo, html_conteudo] baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]
bundlePath]]];
Exibindo a view pela lista de posts (171)
Voltemos ao PostsControler.m. Antes, precisamos importar a classe Postagem para ser
exibida.
#import "Postagem.h"
Em seguida, passar o objeto Posts ao selecionarmos uma célula e exibir a view com a
postagem. No método didSelectRowAtIndexPath:
Postagem *viewpost = [[Postagem alloc] init];
[viewpost setPost:[items objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:viewpost animated:YES];
A view da aba Novidades (172)
Para esta view, usaremos um Table View. Mas, iremos fazer uso de uma tabela com várias
seções. Cada seção corresponderá a um feed cadastrado e mostrará o último post cadastrado em
cada uma.
Crie uma classe TableViewController chamada Novidades. Desmarque a opção de criar um
arquivo XIB, pois usaremos a view anexada no MainWindow. Atribua esta classe ao
ViewController do primeiro item do tab bar do Main Window.
Nos cabeçalhos do Novidades.h, vamos declarar dois objetos: um array para a lista, um para os
cabeçalhos, que conterá o título da seção, e um objeto Managed Object Context, para o banco
de dados.
NSMutableArray *itens;
NSMutableArray *cabecalhos;
NSManagedObjectContext *moc;
Implementação do arquivo Novidades.m(173)
Nesta view, teremos de listar o último post de cada feed cadastrado e por seguinte exibir na
view de postagem. Logo, vamos ter de importar estas classes.
#import "Posts.h"
#import "Urls.h"
#import "Postagem.h"
#import "AppDelegate.h"
Implementaremos o método viewDidLoad: que fará a listagem tanto dos cabeçalhos quanto
dos posts dentro dos respectivos arrays.
Declarar o Managed Object Context
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
moc = appDelegate.managedObjectContext;
Fazer a busca na entidade Urls procurando por todos os endereços de feeds cadastrados e
inserí-los no array cabecalhos.
NSEntityDescription *objUrls = [NSEntityDescription entityForName:@"Urls"
inManagedObjectContext:moc];
NSFetchRequest *urlReq = [[NSFetchRequest alloc] init];
[urlReq setEntity: objUrls];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"nome"
ascending:YES];
NSArray *arraySD = [NSArray arrayWithObject:sortDescriptor];
[urlReq setSortDescriptors:arraySD];
NSError *error;
NSMutableArray *arrayFeeds = [[moc executeFetchRequest:urlReq error:&error]
mutableCopy];
cabecalhos = [[NSMutableArray alloc] initWithArray:arrayFeeds];
Varrer dentro do array gerado pela pesquisa arrayFeeds, e inserir o primeiro registro de cada
resultado no arrayPosts. Para que possamos pegar o mais novo no primeiro registro do array,
vamos ordernar a busca como ordem decrescente pela data. No final, atribuir o resultado da
consulta ao array itens.
NSMutableArray *arrayPosts = [[NSMutableArray alloc] init];
for (Urls *u in arrayFeeds)
{
NSEntityDescription *objPosts = [NSEntityDescription entityForName:@"Posts"
inManagedObjectContext:moc];
NSFetchRequest *reqPosts = [[NSFetchRequest alloc] init];
[reqPosts setEntity:objPosts];
NSPredicate *predPosts = [NSPredicate predicateWithFormat:@"feed_url == %@",[u url]];
[reqPosts setPredicate:predPosts];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@"data_publicacao" ascending:NO];
NSArray *arraySD = [NSArray arrayWithObject:sortDescriptor];
[reqPosts setSortDescriptors:arraySD];
NSError *error;
NSMutableArray *arrayTempPosts = [[moc executeFetchRequest:reqPosts error:&error]
mutableCopy];
[arrayPosts addObject:[arrayTempPosts objectAtIndex:0]];
}
itens = [[NSMutableArray alloc] initWithArray:arrayPosts];
Configurações da tabela (174)
Vamos às configurações das células. Em primeiro lugar, é bom lembrar que desta vez
trabalharemos com uma tabela multi-secionada. Logo, no método
numberOfSectionsInTableView, colocaremos o número de itens do array cabecalhos.
return [cabecalhos count];
E no numberOfRowsInSection, cada seção terá 1 item, então:
return 1;
E um novo método há de ser implementado, que determina o título das seções. Por padrão ele
não está inserido no código, tendo de inserir:
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section
{
return [[cabecalhos objectAtIndex:section] nome];
}
Em cada linha, o título do post, no método cellForRowAtIndexPath:
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [[itens objectAtIndex:indexPath.section] titulo];
E por fim, vamos chamar a view que abrirá o post quando escolhermos a linha, no método
didSelectRowAtIndexPath:
Postagem *viewpost = [[Postagem alloc] init];
[viewpost setPost:[itens objectAtIndex:indexPath.section]];
[self.navigationController pushViewController:viewpost animated:YES];
De código, isso é tudo que temos a fazer. Agora vamos cuidar de alguns aspectos de
apresentação do nosso app.
Refinamentos (175)
Ícones
Para identificar-mos o app no dispositivo, precisamos de um ícone que o remeta bem. Crie uma
imagem 57x57 pixels (114x 114 para o retina display). Todo o efeito de sombra, luz e
arredondamento é feito automaticamente pelo iPhone. Salve-os com o nome de Icon.png
(Icon@2x.png para o retina display). Importe-as para o projeto (grupo Resources).
Ícones usados neste app.
Tela de Splash
Agora vamos criar uma tela de apresentação do nosso app; aquela imagem que apresenta o
programa, empresa ou afins. Para isso, crie uma imagem nas dimensões 320x480 px
(640x960px para a versão retina display) e salve-as com o nome de Default.png e
Default@2x.png respectivamente. Importe-as para o projeto no grupo resources.
Imagens usadas neste app.
Desafios propostos para melhorias (176)
Como pode ver, há muitas possibilidades para expandir as funcionalidades, nas quais, com o
decorrer do curso, você com certeza deve ter aprendido. O objetivo agora é deixar em suas
mãos a implementação de alguns recursos, tais como:
Possibilidade do usuário favoritar um post
Excluir um feed
Excluir um post
Atualizar as postagens de feeds, forçando a leitura do xml parser
Marcar como lido ou não lido
E muitas outras que poderão vir de acordo com a sua criatividade. Tente implementá-las usando
seus conhecimentos. Estarei aqui para tirar as dúvidas. Mas, o mais importante é ter a segurança
de que aprendeu, seguindo seus passos de forma independente. Mãos a obra!
Considerações finais (177)
Enfim, chegamos ao fim deste curso do iMasters PRO! O objetivo foi apresentar a você parte
dos infinitos recursos que o iPhone pode oferecer. Infelizmente não conseguimos tratar de todas
as funcionalidades, até porque, muitas delas são muito extensas e nos mais diversos focos, mas
por agora, você está gabaritado para atender boa parte da demanda de apps para iPhone.
top related