Curso iosx

Page 1

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 reaproveitamento 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, usase 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. Certifiquese 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.


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.