Mostrando postagens com marcador FilterResults. Mostrar todas as postagens
Mostrando postagens com marcador FilterResults. Mostrar todas as postagens

terça-feira, 9 de outubro de 2012

NSPredicate no iOS

Olá povo,

Na última Campus Party Recife, assisti uma palestra do professor Fernando Castor da UFPE, e uma coisa que ele falou me chamou a atenção (veja minuto 29:58): "Java é igual ao Marlon Brando, quando chegou era bonitão, mas hoje envelheceu"(e o ator já morreu).
Estou atualmente em um projeto com iOS, e recentemente fiz um curso de Adobe Air e outro de Windows Phone. O que pude notar, é que em todos esses ambientes, as linguagens evoluíram e estão trazendo dinamismos que facilitam bastante a vida do desenvolvedor. O conceito de Binding utilizado pelo Air e pelo WP7 é um exemplo desse tipo de coisa.
Depois dessa introdução poética :) hoje quero falar de um conceito bem bacana do Objective-C, linguagem utilizada para criar aplicativos para o iOS: os predicates. Eles permitem fazer buscas em listas de objetos sem ter que iterar sobre eles. Felipe Vasconcelos me mostrou isso e achei bem bacana! :)

#define KEY_NOME  @"nome"
#define KEY_IDADE @"idade"
#define KEY_PAI   @"pai"

@interface MinhaPessoa : NSObject
@property (strong, nonatomic) NSString *nome;
@property (assign, nonatomic) int idade;
@property (strong, nonatomic) MinhaPessoa *pai;
@end

@implementation MinhaPessoa

@end

int main(int argc, char *argv[])
{
  NSMutableArray *complexList = 
    [NSMutableArray new];

  // Adicionando um NSDictionary
  [complexList addObject: @{
    KEY_NOME  : @"Antônio",
    KEY_IDADE : @10,
    KEY_PAI   : @{ KEY_NOME : @"Fulano" }
  }];

  // Outro NSDictionary
  [complexList addObject: @{
    KEY_NOME  : @"Francisco",
    KEY_IDADE : @20,
    KEY_PAI   : @{ KEY_NOME : @"Fulano"}
  }];

  // Agora um objeto Pessoa
  MinhaPessoa *p  = [MinhaPessoa new];
  p.nome     = @"João";
  p.idade    = 30;
  p.pai      = [MinhaPessoa new];
  p.pai.nome = @"Cicrano";
  [complexList addObject:p];

  // De novo um NSDicionary
  [complexList addObject: @{
    KEY_NOME  : @"Nelson",
    KEY_IDADE : @28,
    KEY_PAI   : @{ KEY_NOME : @"Beltrano"}
  }];
  // E pra finalizar, outra Pessoa
  p          = [MinhaPessoa new];
  p.nome     = @"Glauber";
  p.idade    = 28;
  p.pai      = [MinhaPessoa new];
  p.pai.nome = @"Beltrano";
  [complexList addObject:p];

  // pai.nome (nome do pai que contenha B)
  NSString *attribute = [NSString 
    stringWithFormat:@"%@.%@", KEY_PAI, KEY_NOME];
  // predicado para a busca
  NSPredicate* predicate = [NSPredicate 
    predicateWithFormat:
      @"%K contains[cd] %@", attribute, @"B"];
  NSArray *resultado = [complexList 
    filteredArrayUsingPredicate:predicate];

  for (id item in resultado) {
    NSLog(@"%@", [item description]);
  }
}
O código acima mostra bem o dinamismo no Objective-C. Primeiro, criamos um array misturando dois tipos de objetos: NSDictionary e MinhaPessoa. Essas classes têm em comum que os atributos da classe MinhaPessoa são iguais às chaves usadas no dicionário.
Uma vez alimentado o array complexList, criamos um objeto NSPredicate que fará o filtro "pai.nome contains[cd] 'B'", ou seja, todos os objetos onde o nome do pai contenha "B". O 'cd' significa que a busca será case insensitive e que serão ignorados os acentos. Através do método filteredArrayUsingPredicate obtemos um array resultado com os objetos filtrados. Por fim, fazemos um foreach para percorrer os resultados.

Saiba mais sobre predicados aqui.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

terça-feira, 8 de novembro de 2011

AutoCompleteTextView personalizado

Olá povo,

Estou com um monte de posts legais pra publicar, mas infelizmente (pra variar) estou sem tempo. São coisas que precisei fazer nos projetos que trabalhei, e que gosto de compartilhar com quem lê o blog. Além do mais, me serve como fonte de consulta para uma uso posterior. Afinal de contas, é pra isso que serve o blog (pelo menos pra mim).

Hoje vou mostrar como criar uma consulta personalizada para o componente AutoCompleteTextView. Esse componente é utilizado quando você tem uma lista de valores, e ao invés de selecioná-lo em uma lista, você digita parte do texto e os resultados vão sendo exibidos em uma lista (estilo drop-down) abaixo do componente. Este componente já conta com um mecanismo de busca padrão, entretanto a necessidade de personalizá-lo surgiu quando precisei realizar a busca por nomes de cidades. O cliente solicitou que a acentuação fosse ignorada, ou seja, quando eu digitasse "sao" era para aparecer "São Paulo" nos resultados.

Vamos à implementação! Primeiro vou mostrar o método que substitui os caracteres acentuados pelos mesmos não acentuados.

public static String[] REPLACES = 
{ "a", "e", "i", "o", "u", "c" };

public static Pattern[] PATTERNS = null;

public static void compilePatterns() {
PATTERNS = new Pattern[REPLACES.length];
PATTERNS[0] = Pattern.compile(
"[âãáàä]", Pattern.CASE_INSENSITIVE);
PATTERNS[1] = Pattern.compile(
"[éèêë]", Pattern.CASE_INSENSITIVE);
PATTERNS[2] = Pattern.compile(
"[íìîï]", Pattern.CASE_INSENSITIVE);
PATTERNS[3] = Pattern.compile(
"[óòôõö]", Pattern.CASE_INSENSITIVE);
PATTERNS[4] = Pattern.compile(
"[úùûü]", Pattern.CASE_INSENSITIVE);
PATTERNS[5] = Pattern.compile(
"[ç]", Pattern.CASE_INSENSITIVE);
}

public static String removeAcentos(String text) {
if (PATTERNS == null) {
compilePatterns();
}

String result = text;
for (int i = 0; i < PATTERNS.length; i++) {
Matcher matcher = PATTERNS[i].matcher(result);
result = matcher.replaceAll(REPLACES[i]);
}
return result.toUpperCase();
}

O código acima, utiliza as classes Pattern e Matcher para checar a presença de caracteres acentuados e substitui-los pelos caracteres correspondentes sem acentuação. Observe que só são sendo tratadas as vogais e o cedilha, para textos em outros idiomas você deve fazer os ajustes necessários (como o 'ñ' do espanhol).

Para que um AutoCompleteTextView faça a busca, é necessário definir um Adapter com os dados que ele irá filtrar para exibir. Para fazer nossa busca personalizada (ignorando acentuação) devemos criar nosso Adapter e sobrescrever o método getFilter(). Esse método deve retornar um objeto da classe android.widget.Filter que representa o resultado da busca.

public class MeuAutoCompleteAdapter
extends ArrayAdapter<String>
implements Filterable {

private List<String> listaCompleta;
private List<String> resultados;
private Filter meuFiltro;

public MeuAutoCompleteAdapter(
Context ctx, int layout,
List<String> textos) {

super(ctx, layout, textos);
this.listaCompleta = textos;
this.resultados = listaCompleta;
this.meuFiltro = new MeuFiltro();
}

@Override
public int getCount() {
return resultados.size();
}

@Override
public String getItem(int position) {
if (resultados != null
&& resultados.size() > 0
&& position < resultados.size()){
return resultados.get(position);
} else {
return null;
}
}

@Override
public Filter getFilter() {
return meuFiltro;
}

private class MeuFiltro extends Filter {
@Override
protected FilterResults performFiltering(
CharSequence constraint) {

FilterResults filterResults =
new FilterResults();

ArrayList<String> temp =
new ArrayList<String>();

if (constraint != null) {
String term = removeAcentos(
constraint.toString().trim().toLowerCase());

String placeStr;
for (String p : listaCompleta) {
placeStr = removeAcentos(p.toLowerCase());

if ( placeStr.indexOf(term) > -1){
temp.add(p);
}
}
}
filterResults.values = temp;
filterResults.count = temp.size();
return filterResults;
}

@SuppressWarnings("unchecked")
@Override
protected void publishResults(
CharSequence contraint,
FilterResults filterResults) {

resultados = (ArrayList<String>)
filterResults.values;

notifyDataSetChanged();
}
}
}

A classe acima tem duas listas de strings: a original e a que serve como resultado da busca. Note que os métodos getCount e getItem trabalha em cima da lista dos resultados. O outro atributo da classe é da classe MeuFiltro, e esse atributo é retornado no método getFilter.
A classe interna MeuFiltro faz a nossa busca personalizada. O filtro é realizado no método performFiltering, que retorna um objeto da classe android.widget.Filter.FilterResults que contém o resultado da busca e o total de registros encontrados. Esse resultado é passado para o método publishResults que seta o resultado da busca no atributo resultados, e logo após atualiza o adapter através do método notifyDatasetChanged.

Agora vamos ver como usar esse nosso adapter.


List<String> cidade = new ArrayList<String>();
cidade.add("Recife");
cidade.add("São Paulo");
cidade.add("Santos");
cidade.add("Santa Cruz");

MeuAutoCompleteAdapter adapter =
new MeuAutoCompleteAdapter(
contexto,
android.R.layout.simple_dropdown_item_1line,
cidade);

AutoCompleteTextView actv = (AutoCompleteTextView)
findViewById(R.id.autoCompleteTextView1);
actv.setAdapter(adapter);


O resultado é exibido na figura abaixo:


Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber