09 Set, 2014 16:14

Formulários no iOS - parte 2

Notificações

O primeiro passo é monitorar quando um campo entra em edição e o teclado aparece. A forma mais rápida para isso é através de notificações.

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tecladoSeraExibido:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tecladoSeraEscondido:) name:UIKeyboardWillHideNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textInputIniciouEdicao:) name:UITextFieldTextDidBeginEditingNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textInputIniciouEdicao:) name:UITextViewTextDidBeginEditingNotification object:nil];
}

O que devemos fazer é, assim que a nossa view aparecer, registrar para receber as seguintes notificações:

  • UIKeyboardWillShowNotification: quando o teclado inicia a animação para tornar-se visível
  • UIKeyboardWillHideNotification: quando o teclado inicia a animação para esconder-se
  • UITextFieldTextDidBeginEditingNotification: quando algum UITextField iniciou a edição
  • UITextViewTextDidBeginEditingNotification: quando algum UITextView iniciou a edição (caso seu formulário possua alguma UITextView)

E para evitar dores de cabeça, remova o registro para todas as notificações quando sair da tela:

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Registradas as notificações, vamos implementar cada tratamento individual.

Primeiro, tratamos as notificações que avisam quando um campo entrou em edição.

@property (nonatomic, weak) UIView<UITextInput> * emEdicaoTextInput;

...

- (void)textInputIniciouEdicao:(NSNotification *)notification {
    self.emEdicaoTextInput = notification.object;
    [self ajustarOffsetTextInputEmEdicao];
}

Precisamos de uma propriedade emEdicaoTextInput para guardar uma referência ao UITextField ou UITextView que está sendo editado no momento. Em seguida ajustamos a posição do UIScrollView (offset) para alinhar com a posição do campo - veremos como mais a frente.

Em seguida, tratamos a entrada do teclado.

- (void)tecladoSeraExibido:(NSNotification *)notification {
    NSDictionary *userInfo = [notification userInfo];

    // 1 - frameEncoberto guarda a parte do scrollView que está encoberta pelo teclado
    CGRect frameTeclado = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    frameTeclado = [self.scrollView.superview convertRect:frameTeclado fromView:self.view.window];
    CGRect frameScroll = self.scrollView.frame;
    CGRect frameEncoberto = CGRectIntersection(frameScroll, frameTeclado);

    // 2 - configuramos a animação com os mesmos parametros da animação do teclado
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationBeginsFromCurrentState:YES];

    double duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    [UIView setAnimationDuration:duration];

    UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
    [UIView setAnimationCurve:curve];

    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(ajustarOffsetTextInputEmEdicao)];

    // 3 - animamos o contentInset para refletir o frameEncoberto
    self.scrollView.contentInset = UIEdgeInsetsMake(0, 0, frameEncoberto.size.height, 0);
    self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset;

    [UIView commitAnimations];
}

No método acima, o primeiro passo (1) é descobrir a área do UIScrollView que foi encoberta pelo teclado. Em seguida (2), configuramos uma animação com os mesmos parâmetros da animação do teclado (UIKeyboardAnimationDurationUserInfoKey e UIKeyboardAnimationCurveUserInfoKey). Dentro dessa animação (3), usamos a altura da área encoberta para definir o contentInset do UIScrollView, que é a propriedade que determina quanto de "respiro" o UIScrollView terá além da área ocupada pelo seu conteúdo. Veja mais na documentação da Apple.

Ainda no bloco acima, garantimos o posicionamento correto do scroll após a animação chamando ajustarOffsetTextInputEmEdicao.

Agora, vejamos o tratamento da saída do teclado.

- (void)tecladoSeraEscondido:(NSNotification *)notification {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationBeginsFromCurrentState:YES];
    self.scrollView.contentInset = UIEdgeInsetsZero;
    self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset;
    [UIView commitAnimations];
}

É apenas uma versão mais simples do tratamento de entrada. Apenas fazemos uma animação para retornar o contentInset para zero.

E finalmente, o método que garante que o UIScrollView exiba na posição correta o campo em edição.

- (void)ajustarOffsetTextInputEmEdicao {
    CGRect frameTextField = [[self.emEdicaoTextInput superview] convertRect:self.emEdicaoTextInput.frame toView:self.scrollView];

    [self.scrollView scrollRectToVisible:frameTextField animated:YES];
}

Nenhum mistério. Pegamos o frame do campo em edição e pedimos para o UIScrollView mudar seu contentOffset para exibir o campo.

Pronto! Agora, ao selecionar qualquer campo o formulário se ajusta corretamente para exibí-lo.

Exibição correta do formulário com teclado

O interessante dessa abordagem é que não é necessário conhecer muito sobre o seu formulário como dimensões, quantidade de campos, se há alguma outra view envolvida (ex. UITabBar). Esse código consegue se adaptar a todas as situações que testei até hoje.

No próximo post da série, pretendo ensinar alguns truques para melhorar seu formulário como adicionar uma toolbar para navegar entre os campos e maneiras de substituir o teclado por outras views como a UIPickerView. Fiquem ligados!

Ao navegar neste site, você consente o uso de cookies nossos e de terceiros, que coletam informações anônimas e são essenciais para melhorar sua experiência em nosso site.