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.
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!