Como posso fazer um UITextField subir quando o teclado está presente - ao começar a editar?

1691

Com o SDK do iOS:

Eu tenho um UIViewcom UITextFields que exibem um teclado. Eu preciso que ele seja capaz de:

  1. Permitir a rolagem do conteúdo do UIScrollViewpara ver os outros campos de texto assim que o teclado for aberto

  2. "Salte" automaticamente (rolando para cima) ou diminua

Eu sei que preciso de um UIScrollView. Tentei alterar a classe da minha UIViewpara uma, UIScrollViewmas ainda não consigo rolar as caixas de texto para cima ou para baixo.

Preciso de um UIViewe de um UIScrollView? Um entra no outro?

O que precisa ser implementado para rolar automaticamente para o campo de texto ativo?

Idealmente, o máximo possível da configuração dos componentes será feito no Interface Builder. Eu gostaria de escrever apenas o código para o que precisa.

Nota: o UIView(ou UIScrollView) com o qual estou trabalhando é apresentado por uma barra de tabulação ( UITabBar), que precisa funcionar normalmente.


Editar: estou adicionando a barra de rolagem apenas para quando o teclado aparecer. Mesmo que não seja necessário, sinto que ele fornece uma interface melhor, porque então o usuário pode rolar e alterar caixas de texto, por exemplo.

Eu tenho que trabalhar onde eu altero o tamanho do quadro UIScrollViewquando o teclado sobe e desce. Estou simplesmente usando:

-(void)textFieldDidBeginEditing:(UITextField *)textField { 
    //Keyboard becomes visible
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
                     scrollView.frame.origin.y, 
scrollView.frame.size.width,
scrollView.frame.size.height - 215 + 50);   //resize
}

-(void)textFieldDidEndEditing:(UITextField *)textField {
   //keyboard will hide
    scrollView.frame = CGRectMake(scrollView.frame.origin.x, 
       scrollView.frame.origin.y, 
     scrollView.frame.size.width,
      scrollView.frame.size.height + 215 - 50); //resize
}

No entanto, isso não "move para cima" ou centraliza automaticamente os campos de texto inferiores na área visível, o que eu realmente gostaria.

philfreo
fonte
6
Veja isso. Sem problemas para você. TPKeyboardAvoiding
Aruna
21
Está documentado pela Apple, acho que é o melhor caminho: developer.apple.com/library/ios/#documentation/StringsTextFonts/…
Maik639 31/07/2013
58
Use esse código. Você só precisa de 1 linha no arquivo appdelegate.m e ele funciona. github.com/hackiftekhar/IQKeyboardManager
Pradeep Mittal
9
A melhor maneira que eu encontrei até agora é esta fonte aberta TPKeyboardAvoiding
Mongi Zaidi
2
Outra maneira é adicionar esses campos de texto de conteúdo e tudo no TableViewController e deixar o tableview lidar com isso.
Vicky Dhas

Respostas:

1036
  1. Você só precisará de um ScrollViewse o conteúdo que você possui agora não couber na tela do iPhone. (Se você estiver adicionando a ScrollViewvisão geral dos componentes apenas para fazer a TextFieldrolagem para cima quando o teclado aparecer, isso não será necessário.)

  2. A maneira padrão de impedir que os TextFields sejam cobertos pelo teclado é mover a visualização para cima / baixo sempre que o teclado é mostrado.

Aqui está um exemplo de código:

#define kOFFSET_FOR_KEYBOARD 80.0

-(void)keyboardWillShow {
    // Animate the current view out of the way
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)keyboardWillHide {
    if (self.view.frame.origin.y >= 0)
    {
        [self setViewMovedUp:YES];
    }
    else if (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
    if ([sender isEqual:mailTf])
    {
        //move the main view, so that the keyboard does not hide it.
        if  (self.view.frame.origin.y >= 0)
        {
            [self setViewMovedUp:YES];
        }
    }
}

//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.3]; // if you want to slide up the view

    CGRect rect = self.view.frame;
    if (movedUp)
    {
        // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 
        // 2. increase the size of the view so that the area behind the keyboard is covered up.
        rect.origin.y -= kOFFSET_FOR_KEYBOARD;
        rect.size.height += kOFFSET_FOR_KEYBOARD;
    }
    else
    {
        // revert back to the normal state.
        rect.origin.y += kOFFSET_FOR_KEYBOARD;
        rect.size.height -= kOFFSET_FOR_KEYBOARD;
    }
    self.view.frame = rect;

    [UIView commitAnimations];
}


- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}
RPDP
fonte
3
O que faz _textField? Eu copiei no meu código, ele diz que _textField não foi declarado.
Cacau Dev
É o campo que você usa para dizer "quando o usuário está editando aqui, a visualização deve deslizar para cima" ou algo assim ... No entanto, você pode removê-lo se houver mais campos.
patrick
não é fácil chamar - (vazio) setViewMovedUp: (BOOL) moveUp nos eventos keyBoardWillSHow e KeyBoardWillHide !!
Abduliam Rehmanius
4
Não é particularmente útil se você estiver suportando rotações da vista principal.
FractalDoctor 02/12/12
2
Para fazer isso funcionar, tive que comentar a textFieldDidBeginEditingseção.
avance
445

Eu também estava tendo muitos problemas com a UIScrollViewcomposição de múltiplos UITextFields, dos quais, um ou mais deles seriam obscurecidos pelo teclado durante a edição.

Aqui estão algumas coisas a considerar se o seu UIScrollViewnão estiver rolando corretamente.

1) Verifique se o contentSize é maior que o UIScrollViewtamanho do quadro. A maneira de entender UIScrollViewsé que UIScrollViewé como uma janela de visualização no conteúdo definido no contentSize. Portanto, quando for UIScrollviewpara rolar para qualquer lugar, o contentSize deve ser maior que o UIScrollView. Senão, não há rolagem necessária, já que tudo definido no contentSize já está visível. BTW, contentSize padrão = CGSizeZero.

2) Agora que você entende que UIScrollViewrealmente é uma janela para o seu "conteúdo", a maneira de garantir que o teclado não oculte sua UIScrollView's"janela" de visualização seria redimensioná-la UIScrollViewpara que, quando o teclado estiver presente, você tenha a UIScrollViewjanela dimensionado para apenas o UIScrollViewframe.size.height original menos a altura do teclado. Isso garantirá que sua janela seja apenas aquela pequena área visível.

3) Aqui está o problema: quando implementei isso pela primeira vez, imaginei que teria que obter o campo CGRectde UIScrollView'stexto editado e chamar o método scrollRecToVisible. Eu implementei o UITextFieldDelegatemétodo textFieldDidBeginEditingcom a chamada para o scrollRecToVisiblemétodo Isso realmente trabalhou com um efeito colateral estranho que a rolagem iria tirar o UITextFieldna posição. Durante muito tempo, não consegui descobrir o que era. Então eu comentei o textFieldDidBeginEditingmétodo Delegate e tudo funcionou !! (???). Como se viu, acredito que, na UIScrollViewverdade, implica implicitamente o atualmente editado UITextFieldna janela visível. Minha implementação do UITextFieldDelegatemétodo e a chamada subsequente ao scrollRecToVisibleforam redundantes e foram a causa do estranho efeito colateral.

Então, aqui estão os passos para rolar adequadamente o seu UITextFieldem um UIScrollViewno lugar quando o aparece teclado.

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad 
{
    [super viewDidLoad];

    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillShow:) 
                                                 name:UIKeyboardWillShowNotification 
                                               object:self.view.window];
    // register for keyboard notifications
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(keyboardWillHide:) 
                                                 name:UIKeyboardWillHideNotification 
                                               object:self.view.window];
    keyboardIsShown = NO;
    //make contentSize bigger than your scrollSize (you will need to figure out for your own use case)
    CGSize scrollContentSize = CGSizeMake(320, 345);
    self.scrollView.contentSize = scrollContentSize;
}

- (void)keyboardWillHide:(NSNotification *)n
{
    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;


    // resize the scrollview
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height += (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];

    keyboardIsShown = NO;
}

- (void)keyboardWillShow:(NSNotification *)n
{
    // This is an ivar I'm using to ensure that we do not do the frame size adjustment on the `UIScrollView` if the keyboard is already shown.  This can happen if the user, after fixing editing a `UITextField`, scrolls the resized `UIScrollView` to another `UITextField` and attempts to edit the next `UITextField`.  If we were to resize the `UIScrollView` again, it would be disastrous.  NOTE: The keyboard notification will fire even when the keyboard is already shown.
    if (keyboardIsShown) {
        return;
    }

    NSDictionary* userInfo = [n userInfo];

    // get the size of the keyboard
    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

    // resize the noteView
    CGRect viewFrame = self.scrollView.frame;
    // I'm also subtracting a constant kTabBarHeight because my UIScrollView was offset by the UITabBar so really only the portion of the keyboard that is leftover pass the UITabBar is obscuring my UIScrollView.
    viewFrame.size.height -= (keyboardSize.height - kTabBarHeight);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [self.scrollView setFrame:viewFrame];
    [UIView commitAnimations];
    keyboardIsShown = YES;
}
  1. Registre-se para receber as notificações do teclado em viewDidLoad
  2. Cancele o registro das nofiticações do teclado em viewDidUnload
  3. Garantir que o contentSizeestá definido e maior do que a UIScrollViewdeviewDidLoad
  4. Encolher o UIScrollViewquando o teclado está presente
  5. Reverta o UIScrollViewquando o teclado desaparecer.
  6. Use um ivar para detectar se o teclado já é mostrado na tela uma vez que as notificações de teclado são enviados cada vez que um UITextFieldé tabulado mesmo se o teclado já está presente para evitar encolhendo o UIScrollViewquando ele já está encolhido

Uma coisa a ser observada é que ela UIKeyboardWillShowNotificationserá acionada mesmo quando o teclado já estiver na tela quando você tabular outra UITextField. Eu cuidei disso usando um ivar para evitar o redimensionamento do UIScrollViewquando o teclado já está na tela. Redimensionar inadvertidamente UIScrollViewquando o teclado já está lá seria desastroso!

Espero que este código poupe muita dor de cabeça a alguns de vocês.

Shiun
fonte
3
Ótimo, mas dois problemas: 1. UIKeyboardBoundsUserInfoKeyestá obsoleto. 2. keyboardSize está em "coordenadas da tela", portanto seus cálculos do viewFrame falharão se o quadro for girado ou dimensionado.
Martin Wickman
21
@Martin Wickman - Use em CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;vez dos obsoletos #UIKeyboardBoundsUserInfoKey
Sottenad
1
Olá, fiz o mesmo, mas a exibição de texto só sobe quando o usuário começa a digitar? É o comportamento esperado ou estou faltando alguma coisa?
3
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].sizedeveria ser [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size. Ótima solução!
J7nn7k
1
Gosto da sua solução, mas acho que posso torná-la ainda mais simples: não se preocupe com o material do Notification Observer; em vez disso, chame as rotinas de animação corretas nos métodos de delegação apropriados - para UITextView, eles são textViewDidBeginEditing e textViewDidEndEditing.
AlexChaffee
270

Na verdade, é melhor usar a implementação da Apple, conforme fornecido nos documentos . No entanto, o código que eles fornecem está com defeito. Substitua a parte encontrada keyboardWasShown:logo abaixo dos comentários pelo seguinte:

NSDictionary* info = [aNotification userInfo];
CGRect keyPadFrame=[[UIApplication sharedApplication].keyWindow convertRect:[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:self.view];
CGSize kbSize =keyPadFrame.size;
CGRect activeRect=[self.view convertRect:activeField.frame fromView:activeField.superview];
CGRect aRect = self.view.bounds;
aRect.size.height -= (kbSize.height);

CGPoint origin =  activeRect.origin;
origin.y -= backScrollView.contentOffset.y;
if (!CGRectContainsPoint(aRect, origin)) {
    CGPoint scrollPoint = CGPointMake(0.0,CGRectGetMaxY(activeRect)-(aRect.size.height));
    [backScrollView setContentOffset:scrollPoint animated:YES];
}

Os problemas com o código da Apple são os seguintes: (1) Eles sempre calculam se o ponto está dentro do quadro da visualização, mas é um ScrollView, portanto, ele já pode ter sido rolado e você precisa contabilizar esse deslocamento:

origin.y -= scrollView.contentOffset.y

(2) Eles mudam o contentOffset pela altura do teclado, mas queremos o oposto (queremos mudar contentOffsetpela altura que é visível na tela, não o que não é):

activeField.frame.origin.y-(aRect.size.height)
DK_
fonte
1
Em situações onde a vista de rolagem não está preenchendo a tela, aRect deve ser definido como quadro da exibição da rolagem
mblackwell8
2
Você não deseja que o CGPoint origin = activeField.frame.origin + activeField.frame.size.height?, Porque você deseja que todo o campo de texto seja exibido e, se houver apenas alguns pixels visíveis, o código não entrará no campo doença.
Htafoya 15/04
1
Esta solução não funciona na orientação Paisagem - o campo de texto sai da parte superior da porta de visualização. iPad com iOS 7.1.
Andrew
4
Para um melhor suporte ao iOS 8, sugiro que você use o tamanho do teclado em UIKeyboardFrameEndUserInfoKeyvez de UIKeyboardFrameBeginUserInfoKeyobter o tamanho do teclado, pois isso inclui itens como alterações personalizadas do teclado e ativar / desativar o texto previsto.
Endareth
1
@Egor: Seu correção faz com que funcione melhor maneira - mas a última linha deve ser inversa:self.scrollView.contentOffset = self.currentSVoffset;
Morten Holmgaard
244

Na chamada textFieldDidBeginEdittinge na textFieldDidEndEditingchamada, a função [self animateTextField:textField up:YES]é a seguinte:

-(void)textFieldDidBeginEditing:(UITextField *)textField 
{ 
    [self animateTextField:textField up:YES]; 
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField:textField up:NO];
}

-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
    const int movementDistance = -130; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? movementDistance : -movementDistance); 

    [UIView beginAnimations: @"animateTextField" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}

Espero que este código o ajude.

Em Swift 2

func animateTextField(textField: UITextField, up: Bool) 
{
     let movementDistance:CGFloat = -130
     let movementDuration: Double = 0.3

     var movement:CGFloat = 0
     if up 
     {
         movement = movementDistance
     }
     else 
     {
         movement = -movementDistance
     }
     UIView.beginAnimations("animateTextField", context: nil)
     UIView.setAnimationBeginsFromCurrentState(true)
     UIView.setAnimationDuration(movementDuration)
     self.view.frame = CGRectOffset(self.view.frame, 0, movement)
     UIView.commitAnimations()
}


func textFieldDidBeginEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:true)
}

func textFieldDidEndEditing(textField: UITextField) 
{
    self.animateTextField(textField, up:false)
}

SWIFT 3

 func animateTextField(textField: UITextField, up: Bool)
    {
        let movementDistance:CGFloat = -130
        let movementDuration: Double = 0.3

        var movement:CGFloat = 0
        if up
        {
            movement = movementDistance
        }
        else
        {
            movement = -movementDistance
        }
        UIView.beginAnimations("animateTextField", context: nil)
        UIView.setAnimationBeginsFromCurrentState(true)
        UIView.setAnimationDuration(movementDuration)
        self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)
        UIView.commitAnimations()
    }


    func textFieldDidBeginEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:true)
    }

    func textFieldDidEndEditing(textField: UITextField)
    {
        self.animateTextField(textField: textField, up:false)
    }
sumanthkodi
fonte
1
por que não usar [UIView animateWithDuration: animations:^{ }];?
Andre Cytryn 8/08/12
2
isso funciona bem, embora const int movementDistance = -130; // emenda como necessidades precisava ser alterado para mais flexível
Martelo
7
Incrivelmente simples em pequenas implementações. Não há problemas com ScrollViews e problemas de layout automático ambíguo.
James Perih
4
Erm ... você não usa o parâmetro textField. Por que, então, tê-lo como um parâmetro de função? Além disso, você também pode usar o operador ternário no Swift. Torna o código menos instável.
Stk
1
Se a cor de plano de fundo da Visualização for diferente de preto, defina a cor da Janela para corresponder à sua visualização, para que o usuário não veja por trás dela. ie self.window.backgroundColor = [UIColor whiteColor];
precisa saber é o seguinte
134

Apenas usando TextFields:

1a) Usando Interface Builder: Selecionar todos os campos de texto => Editar => Incorporar => ScrollView

1b) Incorporar manualmente TextFields no UIScrollView chamado scrollView

2) Definir UITextFieldDelegate

3) Defina cada textField.delegate = self;(ou faça as conexões Interface Builder)

4) Copiar / colar:

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    CGPoint scrollPoint = CGPointMake(0, textField.frame.origin.y);
    [scrollView setContentOffset:scrollPoint animated:YES];
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    [scrollView setContentOffset:CGPointZero animated:YES];
}
DAS
fonte
8
Mas também aumenta a exibição quando textFieldjá está visível.
TheTiger 5/05
1
Precisa mudar CGPointMake(0, textField.frame.origin.y);paraCGPointMake(0, textField.frame.origin.y + scrollView.contentInset.top);
Fury
@ Egor Mesmo depois do seu comentário, ele não funciona. Como o "TheTiger" mencionado, ele move a visualização mesmo depois que o campo de texto é visível.
Rak appdev 03/04
Altere para o XCode 10: "Selecionar todos os campos de texto => Editor => Incorporar => Exibir rolagem"
tibalt 7/19/19
116

Para a Universal Solution , aqui estava minha abordagem para implementar o IQKeyboardManager .

insira a descrição da imagem aqui

Passo 1: - Eu adicionei notificações globais de UITextField, UITextViewe UIKeyboardem uma única classe. Eu chamo de IQKeyboardManager .

Etapa 2: - Se encontrado UIKeyboardWillShowNotification, UITextFieldTextDidBeginEditingNotificationou UITextViewTextDidBeginEditingNotificationnotificações, tento obter topMostViewControllerinstância da UIWindow.rootViewControllerhierarquia. A fim de adequadamente uncover UITextField/ UITextViewsobre ele, topMostViewController.view's quadro precisa ser ajustado.

Etapa 3: - Calculei a distância esperada de movimento topMostViewController.viewem relação à primeira resposta UITextField/ UITextView.

Passo 4: - Mudei para topMostViewController.view.framecima / baixo de acordo com a distância de movimento esperada.

Etapa 5: - Se encontrado UIKeyboardWillHideNotification, UITextFieldTextDidEndEditingNotificationou UITextViewTextDidEndEditingNotificationnotificação, tento novamente obter topMostViewControllerinstância da UIWindow.rootViewControllerhierarquia.

Passo 6: - Calculei a distância perturbada da topMostViewController.viewqual precisa ser restaurada para sua posição original.

Passo 7: - Eu restaurei de topMostViewController.view.frameacordo com a distância perturbada.

Etapa 8: - Instanciei a instância da classe IQKeyboardManager de singleton na carga do aplicativo, para que todos UITextField/ UITextViewno aplicativo se ajustem automaticamente de acordo com a distância de movimento esperada.

Isso é tudo o que o IQKeyboardManager faz por você com NENHUMA LINHA DE CÓDIGO realmente !! só é necessário arrastar e soltar o arquivo de origem relacionado ao projeto. O IQKeyboardManager também suporta Orientação a dispositivos , Gerenciamento automático da barra de ferramentas do UIT , KeybkeyboardDistanceFromTextField e muito mais do que você pensa.

Mohd Iftekhar Qurashi
fonte
Adicione o diretório IQKeyBoardManagerSwift ao meu projeto e não funcione. Não pode permitir que seu primo não reconhece em AppDelegate ...
user3722523
2
parece phishing, a solução real não é mostrada, mas vemos um comercial para essa conta do GitHub.
Brian
101

Eu coloquei um universal, drop-in UIScrollView, UITableViewe até mesmo UICollectionViewsubclasse que cuida de mover todos os campos de texto dentro dele fora do caminho do teclado.

Quando o teclado estiver prestes a aparecer, a subclasse encontrará a subvisão que está prestes a ser editada e ajustará o quadro e o deslocamento do conteúdo para garantir que a visualização seja visível, com uma animação para corresponder ao pop-up do teclado. Quando o teclado desaparece, ele restaura seu tamanho anterior.

Ele deve funcionar basicamente com qualquer configuração, seja uma UITableViewinterface baseada em ou uma que consiste em visualizações colocadas manualmente.

Aqui está: solução para mover os campos de texto para fora do caminho do teclado

Michael Tyson
fonte
É isso! Esta é a melhor, mais eficiente e perfeita solução! Ele também lida com as rotações corretamente para as visualizações de rolagem. Se estiver girando, certifique-se de redimensionar automaticamente verticalmente, mas não ancore na parte inferior. Eu adicionei um UITextView à exibição de rolagem no meu caso. Graças cachos!
Christopher
Muito bom trabalho! Claro, estou sendo preguiçoso usando sua solução em vez da de DIY, mas meu chefe está mais feliz, então sim! Mesmo se alguém quiser fazer o que é, gosto da sua abordagem de subclasse, em vez de adicionar código a cada controlador. Fiquei chocado iOS não fez isso por padrão, como o Android fez -, novamente, eu estou encontrando um monte de coisas faltando no iOS e MacOS :(
eselk
Coisas estranhas como o meu scrollview se encaixam na tela, portanto não podem ser roladas. Após abrir e fechar o teclado, o conteúdo agora é maior (parece que algo invisível foi adicionado e não removido na parte inferior da página) e pode ser rolado.
Almo 23/11
91

Para programadores Swift :

Isso fará tudo por você, basta colocá-los na sua classe de controlador de exibição e implementar o UITextFieldDelegateno seu controlador de exibição e definir o delegado do textField paraself

textField.delegate = self // Setting delegate of your UITextField to self

Implemente os métodos de retorno de chamada delegados:

func textFieldDidBeginEditing(textField: UITextField) {
    animateViewMoving(true, moveValue: 100)
}

func textFieldDidEndEditing(textField: UITextField) {
    animateViewMoving(false, moveValue: 100)
}

// Lifting the view up
func animateViewMoving (up:Bool, moveValue :CGFloat){
    let movementDuration:NSTimeInterval = 0.3
    let movement:CGFloat = ( up ? -moveValue : moveValue)
    UIView.beginAnimations( "animateView", context: nil)
    UIView.setAnimationBeginsFromCurrentState(true)
    UIView.setAnimationDuration(movementDuration )
    self.view.frame = CGRectOffset(self.view.frame, 0,  movement)
    UIView.commitAnimations()
}

Para Swift 4, 4.2, 5: Alterar

self.view.frame = CGRectOffset(self.view.frame, 0,  movement)

para

self.view.frame = self.view.frame.offsetBy(dx: 0, dy: movement)

Última observação sobre esta implementação: Se você colocar outro controlador de exibição na pilha enquanto o teclado for mostrado, isso criará um erro no qual a exibição retornará ao quadro central, mas o deslocamento do teclado não será redefinido. Por exemplo, o teclado é o primeiro a responder ao nameField, mas você pressiona um botão que coloca o Help View Controller na pilha. Para corrigir o erro de deslocamento, chame nameField.resignFirstResponder () antes de sair do controlador de exibição, garantindo que o método delegado textFieldDidEndEditing também seja chamado. Eu faço isso no método viewWillDisappear.

Satnam Sync
fonte
3
SwiftLint não gostou self.view.frame = CGRectOffset(self.view.frame, 0, movement)então eu mudei essa linha paraself.view.frame.offsetInPlace(dx: 0, dy: movement)
levibostian
2
Swift 4 muda self.view.frame = CGRectOffset (self.view.frame, 0, movimento) para self.view.frame.offsetBy (dx: 0, dy: motion)
Asinox
Para sua informação, para que isso funcione, você deve colocar. self.view.frame = self.view.frame.offsetBy (dx: 0, dy: movimento)
Josh Wolff
64

Já existem muitas respostas, mas ainda assim nenhuma das soluções acima tinha todo o material de posicionamento necessário para uma animação "perfeita" livre de bugs, compatível com versões anteriores e sem tremulação. (erro ao animar quadros / limites e contentOffset juntos, diferentes orientações de interface, teclado dividido para iPad, ...)
Deixe-me compartilhar minha solução:
(supondo que você tenha configurado UIKeyboardWill(Show|Hide)Notification)

// Called when UIKeyboardWillShowNotification is sent
- (void)keyboardWillShow:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = [notification userInfo];

    CGRect keyboardFrameInWindow;
    [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameInWindow];

    // the keyboard frame is specified in window-level coordinates. this calculates the frame as if it were a subview of our view, making it a sibling of the scroll view
    CGRect keyboardFrameInView = [self.view convertRect:keyboardFrameInWindow fromView:nil];

    CGRect scrollViewKeyboardIntersection = CGRectIntersection(_scrollView.frame, keyboardFrameInView);
    UIEdgeInsets newContentInsets = UIEdgeInsetsMake(0, 0, scrollViewKeyboardIntersection.size.height, 0);

    // this is an old animation method, but the only one that retains compaitiblity between parameters (duration, curve) and the values contained in the userInfo-Dictionary.
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    _scrollView.contentInset = newContentInsets;
    _scrollView.scrollIndicatorInsets = newContentInsets;

    /*
     * Depending on visual layout, _focusedControl should either be the input field (UITextField,..) or another element
     * that should be visible, e.g. a purchase button below an amount text field
     * it makes sense to set _focusedControl in delegates like -textFieldShouldBeginEditing: if you have multiple input fields
     */
    if (_focusedControl) {
        CGRect controlFrameInScrollView = [_scrollView convertRect:_focusedControl.bounds fromView:_focusedControl]; // if the control is a deep in the hierarchy below the scroll view, this will calculate the frame as if it were a direct subview
        controlFrameInScrollView = CGRectInset(controlFrameInScrollView, 0, -10); // replace 10 with any nice visual offset between control and keyboard or control and top of the scroll view.

        CGFloat controlVisualOffsetToTopOfScrollview = controlFrameInScrollView.origin.y - _scrollView.contentOffset.y;
        CGFloat controlVisualBottom = controlVisualOffsetToTopOfScrollview + controlFrameInScrollView.size.height;

        // this is the visible part of the scroll view that is not hidden by the keyboard
        CGFloat scrollViewVisibleHeight = _scrollView.frame.size.height - scrollViewKeyboardIntersection.size.height;

        if (controlVisualBottom > scrollViewVisibleHeight) { // check if the keyboard will hide the control in question
            // scroll up until the control is in place
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y += (controlVisualBottom - scrollViewVisibleHeight);

            // make sure we don't set an impossible offset caused by the "nice visual offset"
            // if a control is at the bottom of the scroll view, it will end up just above the keyboard to eliminate scrolling inconsistencies
            newContentOffset.y = MIN(newContentOffset.y, _scrollView.contentSize.height - scrollViewVisibleHeight);

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        } else if (controlFrameInScrollView.origin.y < _scrollView.contentOffset.y) {
            // if the control is not fully visible, make it so (useful if the user taps on a partially visible input field
            CGPoint newContentOffset = _scrollView.contentOffset;
            newContentOffset.y = controlFrameInScrollView.origin.y;

            [_scrollView setContentOffset:newContentOffset animated:NO]; // animated:NO because we have created our own animation context around this code
        }
    }

    [UIView commitAnimations];
}


// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillHide:(NSNotification*)notification
{
    // if we have no view or are not visible in any window, we don't care
    if (!self.isViewLoaded || !self.view.window) {
        return;
    }

    NSDictionary *userInfo = notification.userInfo;

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];

    // undo all that keyboardWillShow-magic
    // the scroll view will adjust its contentOffset apropriately
    _scrollView.contentInset = UIEdgeInsetsZero;
    _scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;

    [UIView commitAnimations];
}
Martin Ullrich
fonte
Grandes melhorias na resposta do @Shiun. Mas depois que o teclado se foi, a exibição não volta à 1ª posição. É ainda um grande trabalho :)
Lucien
2
Obrigado, esta é a melhor solução para mim em 2017. Observe que você não precisa acompanhar o focusControl, pode determinar isso com UIApplication.shared.sendAction(...). Aqui está a versão Swift 3 de sua resposta (menos parte willHide), com a sendActionimplementação: gist.github.com/xaphod/7aab1302004f6e933593a11ad8f5a72d
xaphod
@xaphod no meu caso, eu precisava focar mais controles - por exemplo, um botão abaixo de um campo de entrada. mas sim, esse código agora tem 4 anos e pode se beneficiar de melhorias.
Martin Ullrich
Esta é provavelmente a solução adequada. A notificação do teclado carrega dados de animação, as delegações do campo de texto não sabem sobre a duração da animação, seria apenas um trabalho de adivinhação.
XY
62

Shiun disse: "Como se viu, acredito que o UIScrollView realmente traz implicitamente o UITextField atualmente editado na janela visível implicitamente" Isso parece ser verdade para o iOS 3.1.3, mas não 3.2, 4.0 ou 4.1. Eu tive que adicionar um scrollRectToVisible explícito para tornar o UITextField visível no iOS> = 3.2.

cbranch
fonte
Não é o UIScrollView que rola implicitamente o UITextField editado para exibição; é o UITextField que chama um [UITextField scrollTextFieldToVisibleIfNecessary]método privado que, por sua vez, chama [UIScrollView scrollRectToVisible]quando [UITextField becomeFirstResponder]é chamado. Consulte github.com/leopatras/ios_textfields_on_scrollview . Se as restrições e os controladores de exibição estiverem configurados corretamente, não será necessário chamar scrollRectToVisibleexplicitamente (pelo menos desde o IOS 11).
29519 Leo
48

Uma coisa a considerar é se você deseja usar um UITextFieldpor conta própria. Não encontrei aplicativos para iPhone bem projetados que realmente sejam usados UITextFieldsfora dele UITableViewCells.

Será um trabalho extra, mas eu recomendo que você implemente todas as visualizações de entrada de dados em uma tabela. Adicione um UITextViewao seu UITableViewCells.

Jonathan Sterling
fonte
1
Um dos meus aplicativos precisa permitir que os usuários adicionem notas de forma livre; portanto, às vezes é útil usar um UITextField.
Peter Johnson
1
Eu concordo com este método. Zero trabalho ou código dessa maneira. Mesmo se você precisar de uma nota de forma livre você ainda pode com uma célula de tabela
RJH
UITableViewinfelizmente é o único caminho a percorrer. As notificações do teclado são frágeis e mudaram horas extras. Código de exemplo no estouro de pilha: stackoverflow.com/a/32390936/218152
SwiftArchitect
Esta resposta está desatualizada em cinco anos . A única solução moderna é algo assim ... stackoverflow.com/a/41808338/294884
Fattie
47

Este documento detalha uma solução para este problema. Veja o código-fonte em 'Movendo o conteúdo localizado sob o teclado'. É bem direto.

EDIT: Notei que há uma pequena falha no exemplo. Você provavelmente vai querer ouvir em UIKeyboardWillHideNotificationvez de UIKeyboardDidHideNotification. Caso contrário, a visualização de rolagem atrás do teclado será cortada durante a animação de fechamento do teclado.

Mihai Damian
fonte
32

Solução mais fácil encontrada

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    [self animateTextField: textField up: YES];
}


- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField: textField up: NO];
}

- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
    const int movementDistance = 80; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? -movementDistance : movementDistance);

    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}
Jawad Zeb
fonte
A tela se move para cima, mesmo que não esteja na parte inferior. ou seja, se o campo de texto estiver na parte superior, ele sairá da tela. Como controlar esse caso?
MELWIN 13/11
@MELWIN Basta adicionar após esta linha: int movement = (up ? -movementDistance : movementDistance); if (textField.frame.origin.y < self.view.frame.size.height - keyboard.height) { movementDistance = 0 }Por favor, não que a keyboardvariável é o CGRect do teclado que aparece que você começa fazendo:let keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue())!
CapturedTree
31

Pequena correção que funciona para muitos UITextFields

#pragma mark UIKeyboard handling

#define kMin 150

-(void)textFieldDidBeginEditing:(UITextField *)sender
{
   if (currTextField) {
      [currTextField release];
   }
   currTextField = [sender retain];
   //move the main view, so that the keyboard does not hide it.
   if (self.view.frame.origin.y + currTextField.frame.origin. y >= kMin) {
        [self setViewMovedUp:YES]; 
   }
}



//method to move the view up/down whenever the keyboard is shown/dismissed
-(void)setViewMovedUp:(BOOL)movedUp
{
   [UIView beginAnimations:nil context:NULL];
   [UIView setAnimationDuration:0.3]; // if you want to slide up the view

   CGRect rect = self.view.frame;
   if (movedUp)
   {
      // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 
      // 2. increase the size of the view so that the area behind the keyboard is covered up.
      rect.origin.y = kMin - currTextField.frame.origin.y ;
   }
   else
   {
      // revert back to the normal state.
      rect.origin.y = 0;
   }
   self.view.frame = rect;

   [UIView commitAnimations];
}


- (void)keyboardWillShow:(NSNotification *)notif
{
   //keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately

   if ([currTextField isFirstResponder] && currTextField.frame.origin.y + self.view.frame.origin.y >= kMin)
   {
      [self setViewMovedUp:YES];
   }
   else if (![currTextField isFirstResponder] && currTextField.frame.origin.y  + self.view.frame.origin.y < kMin)
   {
      [self setViewMovedUp:NO];
   }
}

- (void)keyboardWillHide:(NSNotification *)notif
{
   //keyboard will be shown now. depending for which textfield is active, move up or move down the view appropriately
   if (self.view.frame.origin.y < 0 ) {
      [self setViewMovedUp:NO];
   }

}


- (void)viewWillAppear:(BOOL)animated
{
   // register for keyboard notifications
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) 
                                                name:UIKeyboardWillShowNotification object:self.view.window]; 
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) 
                                                name:UIKeyboardWillHideNotification object:self.view.window]; 
}

- (void)viewWillDisappear:(BOOL)animated
{
   // unregister for keyboard notifications while not visible.
   [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 
}
tt.Kilew
fonte
rect.origin.y=+currTextField.frame.origin.yfuncionando bem obrigado
u.gen
30

O código do RPDP move com sucesso o campo de texto para fora do caminho do teclado. Mas quando você rolar para o topo depois de usar e descartar o teclado, o topo será rolado para cima para fora da visualização. Isso é verdade para o simulador e o dispositivo. Para ler o conteúdo na parte superior dessa visualização, é necessário recarregar a visualização.

O código a seguir não deveria trazer a visualização de volta?

else
{
    // revert back to the normal state.
    rect.origin.y += kOFFSET_FOR_KEYBOARD;
    rect.size.height -= kOFFSET_FOR_KEYBOARD;
}
Steve
fonte
23

Não tenho certeza se mover a visualização para cima é a abordagem correta, fiz isso de maneira diferente, redimensionando o UIScrollView. Expliquei em detalhes em um pequeno artigo

Jose Muanis
fonte
O link para o artigo está morto.
Teo
22

Para retornar ao estado original da visualização, adicione:

-(void)textFieldDidEndEditing:(UITextField *)sender

{
    //move the main view, so that the keyboard does not hide it.
    if  (self.view.frame.origin.y < 0)
    {
        [self setViewMovedUp:NO];
    }
}
Nicolas Marchand
fonte
20

Tente este pequeno truque.

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    [self animateTextField: textField up: YES];
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self animateTextField: textField up: NO];
}

- (void) animateTextField: (UITextField*) textField up: (BOOL) up
{
    const int movementDistance = textField.frame.origin.y / 2; // tweak as needed
    const float movementDuration = 0.3f; // tweak as needed

    int movement = (up ? -movementDistance : movementDistance);

    [UIView beginAnimations: @"anim" context: nil];
    [UIView setAnimationBeginsFromCurrentState: YES];
    [UIView setAnimationDuration: movementDuration];
    self.view.frame = CGRectOffset(self.view.frame, 0, movement);
    [UIView commitAnimations];
}
Sourabh Sharma
fonte
19

Existem muitas soluções, mas passo algumas horas antes de começar a funcionar. Então, coloquei este código aqui (basta colar no projeto, nenhuma modificação precisa):

@interface RegistrationViewController : UIViewController <UITextFieldDelegate>{
    UITextField* activeField;
    UIScrollView *scrollView;
}
@end

- (void)viewDidLoad
{
    [super viewDidLoad];

    scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];

    //scrool view must be under main view - swap it
    UIView* natView = self.view;
    [self setView:scrollView];
    [self.view addSubview:natView];

    CGSize scrollViewContentSize = self.view.frame.size;
    [scrollView setContentSize:scrollViewContentSize];

    [self registerForKeyboardNotifications];
}

- (void)viewDidUnload {
    activeField = nil;
    scrollView = nil;
    [self unregisterForKeyboardNotifications];
    [super viewDidUnload];
}

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShown:)
                                                 name:UIKeyboardWillShowNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];

}

-(void)unregisterForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillShowNotification
                                                  object:nil];
    // unregister for keyboard notifications while not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];
}

- (void)keyboardWillShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    CGRect frame = self.view.frame;
    frame.size.height -= kbSize.height;
    CGPoint fOrigin = activeField.frame.origin;
    fOrigin.y -= scrollView.contentOffset.y;
    fOrigin.y += activeField.frame.size.height;
    if (!CGRectContainsPoint(frame, fOrigin) ) {
        CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y + activeField.frame.size.height - frame.size.height);
        [scrollView setContentOffset:scrollPoint animated:YES];
    }
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
     [scrollView setContentOffset:CGPointZero animated:YES];
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    activeField = textField;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    activeField = nil;
}

-(BOOL) textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}

PS: Espero que o código ajude alguém a fazer o efeito desejado rapidamente. (Xcode 4.5)

HotJard
fonte
Oi Hotjard, estou recebendo EXE_BAD_ACCESS no [self.view addSubview: natView];
Bala
18

@ user271753

Para recuperar a visualização original, adicione:

-(BOOL)textFieldShouldReturn:(UITextField *)textField{
   [textField resignFirstResponder];
   [self setViewMovedUp:NO];
   return YES;
}
user436179
fonte
16

Não requer uma visualização de rolagem para poder mover o quadro de visualização. Você pode alterar o quadro de uma viewcontroller'svisualização para que a visualização inteira se mova apenas o suficiente para colocar o campo de texto do responsável pela resposta acima do teclado. Quando me deparei com esse problema, criei uma subclasse UIViewControllerque faz isso. Ele observa que o teclado exibirá uma notificação e localizará a subvisão da primeira resposta e (se necessário) anima a visualização principal para cima apenas o suficiente para que a primeira resposta fique acima do teclado. Quando o teclado se oculta, ele anima a exibição de volta onde estava.

Para usar esta subclasse, transforme seu controlador de exibição personalizado em uma subclasse de GMKeyboardVC e ele herda esse recurso (apenas certifique-se de implementar viewWillAppeare viewWillDisappeareles devem chamar super). A classe está no github .

progrmr
fonte
Qual licença? Alguns de seus arquivos possuem uma licença de código aberto e outros não.
jaime
Aviso: este código não é amigável com projetos ARC.
Almo 23/11
Você acabou de adicionar a opção de construção para especificar que esses arquivos não são ARC ou pode convertê-lo em ARC e enviar uma solicitação de recebimento.
progrmr
14

Swift 4 .

Você pode mover-se facilmente cima e para baixo UITextFieldOu UIViewCom UIKeyBoardComAnimation insira a descrição da imagem aqui

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var textField: UITextField!
    @IBOutlet var chatView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: .UIKeyboardWillChangeFrame, object: nil)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        textField.resignFirstResponder()
    }

    @objc func keyboardWillChange(notification: NSNotification) {

        let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
        let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
        let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let deltaY = targetFrame.origin.y - curFrame.origin.y
        print("deltaY",deltaY)

        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
            self.chatView.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
        },completion: nil)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

}
ZAFAR007
fonte
2
Quase perfeito. No iPhone X, você tem uma lacuna estranha entre o teclado e o campo de texto.
Houman
12

Aqui está a solução de hack que eu criei para um layout específico. Essa solução é semelhante à solução de Matt Gallagher, na qual é exibida uma seção. Ainda sou novo no desenvolvimento do iPhone e não estou familiarizado com o funcionamento dos layouts. Assim, este hack.

Minha implementação precisava oferecer suporte à rolagem ao clicar em um campo e também à rolagem quando o usuário seleciona o próximo no teclado.

Eu tinha um UIView com uma altura de 775. Os controles estão espalhados basicamente em grupos de 3 em um espaço grande. Acabei com o seguinte layout IB.

UIView -> UIScrollView -> [UI Components]

Aí vem o hack

Defino a altura do UIScrollView para 500 unidades maiores que o layout real (1250). Criei uma matriz com as posições absolutas para as quais preciso rolar e uma função simples para obtê-las com base no número da etiqueta IB.

static NSInteger stepRange[] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 140, 140, 140, 140, 410
};

NSInteger getScrollPos(NSInteger i) {
    if (i < TXT_FIELD_INDEX_MIN || i > TXT_FIELD_INDEX_MAX) {
        return 0 ;
    return stepRange[i] ;
}

Agora, tudo o que você precisa fazer é usar as duas linhas de código a seguir em textFieldDidBeginEditing e textFieldShouldReturn (a última se você estiver criando uma próxima navegação de campo)

CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
[self.scrollView setContentOffset:point animated:YES] ;

Um exemplo.

- (void) textFieldDidBeginEditing:(UITextField *)textField
{
    CGPoint point = CGPointMake(0, getScrollPos(textField.tag)) ;
    [self.scrollView setContentOffset:point animated:YES] ;
}


- (BOOL)textFieldShouldReturn:(UITextField *)textField {

    NSInteger nextTag = textField.tag + 1;
    UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];

    if (nextResponder) {
        [nextResponder becomeFirstResponder];
        CGPoint point = CGPointMake(0, getScrollPos(nextTag)) ;
        [self.scrollView setContentOffset:point animated:YES] ;
    }
    else{
        [textField resignFirstResponder];
    }

    return YES ;
}

Este método não 'retrocede' como outros métodos. Isso não era um requisito. Novamente, isso foi para uma UIView razoavelmente "alta" e eu não tive dias para aprender os mecanismos de layout interno.

Steve McFarlin
fonte
12

De acordo com os documentos , no iOS 3.0, a UITableViewControllerclasse redimensiona e reposiciona automaticamente sua exibição de tabela quando há edição em linha de campos de texto. Eu acho que não é suficiente colocar o campo de texto dentro de um, UITableViewCellcomo alguns indicaram.

Dos documentos :

Um controlador de exibição de tabela suporta edição embutida de linhas de exibição de tabela; se, por exemplo, as linhas tiverem campos de texto incorporados no modo de edição, ela rolará a linha que está sendo editada acima do teclado virtual exibido.

Dheeraj VS
fonte
Encontrei o mesmo comentário. Sim, é verdade. O estranho é que ele está trabalhando em um UITabelViewController e em um segundo não. Mas não encontrei diferenças na minha implementação.
Morpheus78
11

Aqui encontrei a solução mais simples para lidar com o teclado.

Você precisa apenas copiar e colar o código de exemplo abaixo e alterar seu campo de texto ou qualquer visualização que queira mover para cima.

Passo 1

Basta copiar e colar abaixo de dois métodos no seu controlador

- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];
}

- (void)deregisterFromKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

Passo 2

register & Deregister teclado Notificações em viewWillAppear e viewWillDisappear métodos respectivamente.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self registerForKeyboardNotifications];
}

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

Etapa 3

Aí vem a parte da alma, basta substituir seu campo de texto e alterar a altura quanto você deseja mover de cabeça para baixo.

- (void)keyboardWasShown:(NSNotification *)notification
{
    NSDictionary* info = [notification userInfo];
    CGSize currentKeyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    //you need replace your textfield instance here
    CGPoint textFieldOrigin = self.tokenForPlaceField.frame.origin;
    CGFloat textFieldHeight = self.tokenForPlaceField.frame.size.height;

    CGRect visibleRect = self.view.frame;
    visibleRect.size.height -= currentKeyboardSize.height;

    if (!CGRectContainsPoint(visibleRect, textFieldOrigin))
    {
        //you can add yor desired height how much you want move keypad up, by replacing "textFieldHeight" below

        CGPoint scrollPoint = CGPointMake(0.0, textFieldOrigin.y - visibleRect.size.height  + textFieldHeight); //replace textFieldHeight to currentKeyboardSize.height, if you want to move up with more height
        [self.scrollView setContentOffset:scrollPoint animated:YES];
    }
}

- (void)keyboardWillBeHidden:(NSNotification *)notification
{
    [self.scrollView setContentOffset:CGPointZero animated:YES];
}

Referência : bem, por favor , aprecie esse cara , que compartilhou esse belo código, solução limpa.

Espero que isso seja útil para alguém que está por aí.

swiftBoy
fonte
Eu não acho que isso seja o melhor. Ithink @Dheeraj VS está certo: isso pode ser feito facilmente e automaticamente se esse campo de texto estiver na célula de uma tabela (mesmo quando table.scrollable = NO). Observe que: a posição e o tamanho da tabela devem ser razoáveis. por exemplo: - se a posição y da tabela for 100 contada a partir da parte inferior da visualização, o teclado de 300 alturas sobreporá a tabela inteira. - se a altura da tabela = 10 e o campo de texto deve ser rolado para cima 100 quando o teclado aparecer para ficar visível, esse campo de texto ficará fora do limite da tabela.
precisa
@ samthui7 A resposta Dheeraj só funciona se você estiver usando um TableViewController, não apenas uma tableview. Isso torna uma restrição que às vezes não é adequada.
Ben G
10

Procurando um bom tutorial para iniciantes sobre o assunto, encontrei o melhor tutorial aqui .

No MIScrollView.hexemplo na parte inferior do tutorial, certifique-se de colocar um espaço em

@property (nonatomic, retain) id backgroundTapDelegate;

como você vê.

savagenoob
fonte
Olá savagenoob, obrigado pelo link fornecido e bem-vindo ao stackoverflow. Tente fornecer o máximo de informações possível ao responder a perguntas (futuras) - links simples são um pouco quebradiços. Dito isto, se a resposta for um link para um bom tutorial que pode ser esquecido.
Maarten Bodewes 01/01
10

Quando UITextFieldestá em uma UITableViewCellrolagem deve ser configurado automaticamente.

Caso contrário, é provavelmente por causa do código / configuração incorretos da visualização da tabela.

Por exemplo, quando recarreguei minha mesa longa com uma UITextFieldna parte inferior, como a seguir,

-(void) viewWillAppear:(BOOL)animated
{
   [self.tableview reloadData];
}

então meu campo de texto na parte inferior foi obscurecido pelo teclado que apareceu quando eu cliquei dentro do campo de texto.

Para consertar isso, eu tive que fazer isso -

-(void) viewWillAppear:(BOOL)animated
{
    //add the following line to fix issue
    [super viewWillAppear:animated];
    [self.tableview reloadData];
}
lakersgonna3peat
fonte
Estou confuso para que serve esse código? Quando o teclado é mostrado, viewWillAppearnão é chamado. E reloadDatanão faz as linhas obscurecidas se tornarem visíveis.
Adam Johns
10

Use esse terceiro que você não precisa escrever nem uma linha

https://github.com/hackiftekhar/IQKeyboardManager

faça o download do projeto e arraste e solte IQKeyboardManagerno seu projeto. Se você encontrar algum problema, leia o READMEdocumento.

Caras realmente é remover dor de cabeça para gerenciar o teclado.

Arvind Kumar
fonte
8

Nota : esta resposta assume que seu campo de texto está em um scrollView.

Prefiro lidar com isso usando scrollContentInset e scrollContentOffset em vez de mexer nos quadros da minha exibição.

Primeiro, vamos ouvir as notificações do teclado

//call this from viewWillAppear
-(void)addKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification
                                               object:nil];
}
//call this from viewWillDisappear
-(void)removeKeyboardNotifications{
    [[NSNotificationCenter default
    Center] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

O próximo passo é manter uma propriedade que represente o primeiro atendedor atual (UITextfield / UITextVIew que atualmente possui o teclado).

Usamos os métodos delegados para definir essa propriedade. Se você estiver usando outro componente, precisará de algo semelhante.

Observe que, para o campo de texto, o definimos em didBeginEditing e para textView em shouldBeginEditing. Isso ocorre porque o textViewDidBeginEditing é chamado após UIKeyboardWillShowNotification por algum motivo.

-(BOOL)textViewShouldBeginEditing:(UITextView * )textView{
    self.currentFirstResponder = textView;
    return YES;
}

-(void)textFieldDidBeginEditing:(UITextField *)textField{
    self.currentFirstResponder = textField;
}

Finalmente, aqui está a mágica

- (void)keyboardWillShow:(NSNotification*)aNotification{
    NSDictionary* info = [aNotification userInfo];
    CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];


    /*if currentFirstResponder is overlayed by the keyboard, move it so it bottom ends where the keyboard begins*/
    if(self.currentFirstResponder){

        //keyboard origin in currentFirstResponderFrame
        CGPoint keyboardOrigin = [self.currentFirstResponder convertPoint:kbFrame.origin fromView:nil];

        float spaceBetweenFirstResponderAndKeyboard = abs(self.currentFirstResponder.frame.size.height-keyboardOrigin.y);

        //only scroll the scrollview if keyboard overlays the first responder
        if(spaceBetweenFirstResponderAndKeyboard>0){
            //if i call setContentOffset:animate:YES it behaves differently, not sure why
            [UIView animateWithDuration:0.25 animations:^{
                [self.scrollView setContentOffset:CGPointMake(0,self.scrollView.contentOffset.y+spaceBetweenFirstResponderAndKeyboard)];
            }];
        }
    }

    //set bottom inset to the keyboard height so you can still scroll the whole content

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbFrame.size.height, 0.0);
    _scrollView.contentInset = contentInsets;
    _scrollView.scrollIndicatorInsets = contentInsets;

}

- (void)keyboardWillHide:(NSNotification*)aNotification{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    _scrollView.contentInset = contentInsets;
    _scrollView.scrollIndicatorInsets = contentInsets;
}
Juraj Petrik
fonte
8

Esta é a solução usando o Swift.

import UIKit

class ExampleViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var scrollView: UIScrollView!

    @IBOutlet var textField1: UITextField!
    @IBOutlet var textField2: UITextField!
    @IBOutlet var textField3: UITextField!
    @IBOutlet var textField4: UITextField!
    @IBOutlet var textField5: UITextField!

    var activeTextField: UITextField!

    // MARK: - View
    override func viewDidLoad() {
        super.viewDidLoad()
        self.textField1.delegate = self
        self.textField2.delegate = self
        self.textField3.delegate = self
        self.textField4.delegate = self
        self.textField5.delegate = self
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.registerForKeyboardNotifications()
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        self.unregisterFromKeyboardNotifications()
    }

    // MARK: - Keyboard

    // Call this method somewhere in your view controller setup code.
    func registerForKeyboardNotifications() {
        let center:  NSNotificationCenter = NSNotificationCenter.defaultCenter()
        center.addObserver(self, selector: "keyboardWasShown:", name: UIKeyboardDidShowNotification, object: nil)
        center.addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
    }

    func unregisterFromKeyboardNotifications () {
        let center:  NSNotificationCenter = NSNotificationCenter.defaultCenter()
        center.removeObserver(self, name: UIKeyboardDidShowNotification, object: nil)
        center.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
    }

    // Called when the UIKeyboardDidShowNotification is sent.
    func keyboardWasShown (notification: NSNotification) {
        let info : NSDictionary = notification.userInfo!
        let kbSize = (info.objectForKey(UIKeyboardFrameBeginUserInfoKey)?.CGRectValue() as CGRect!).size

        let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
        scrollView.contentInset = contentInsets;
        scrollView.scrollIndicatorInsets = contentInsets;

        // If active text field is hidden by keyboard, scroll it so it's visible
        // Your app might not need or want this behavior.
        var aRect = self.view.frame
        aRect.size.height -= kbSize.height;
        if (!CGRectContainsPoint(aRect, self.activeTextField.frame.origin) ) {
            self.scrollView.scrollRectToVisible(self.activeTextField.frame, animated: true)
        }
    }

    // Called when the UIKeyboardWillHideNotification is sent
    func keyboardWillBeHidden (notification: NSNotification) {
        let contentInsets = UIEdgeInsetsZero;
        scrollView.contentInset = contentInsets;
        scrollView.scrollIndicatorInsets = contentInsets;
    }

    // MARK: -  Text Field

    func textFieldDidBeginEditing(textField: UITextField) {
        self.activeTextField = textField
    }

    func textFieldDidEndEditing(textField: UITextField) {
        self.activeTextField = nil
    }

}
Homam
fonte
Resposta correta, mas estou tendo nenhum problema ao usar o TextField e o TextView. Qualquer ajuda?
Thiha Aung
@Thiha Aung, Suas variáveis ​​do IBOutlet no seu código-fonte estão conectadas ao IB?
Homam
Sim, eles são conectados como well.Just ter esse erro ao usar UITextView em que a linha: if (CGRectContainsPoint (aRect, self.activeTextField.frame.origin)!) {
Thiha Aung
Significa self.activeTextField é nulo
Thiha Aung