Fazendo uma rolagem UITableView quando o campo de texto é selecionado

251

Depois de muita tentativa e erro, estou desistindo e fazendo a pergunta. Eu já vi muitas pessoas com problemas semelhantes, mas não consigo obter todas as respostas para funcionar corretamente.

Eu tenho um UITableViewque é composto de células personalizadas. As células são compostas por 5 campos de texto próximos um do outro (como uma grade).

Quando tento rolar e editar as células na parte inferior do UITableView, não consigo posicionar minhas células corretamente acima do teclado.

Eu já vi muitas respostas falando sobre a alteração do tamanho das visualizações, etc ... mas nenhuma delas funcionou muito bem até agora.

Alguém poderia esclarecer a maneira "certa" de fazer isso com um exemplo de código concreto?

Jonathan
fonte
11
Esta documentação do Applle descreve as etapas para implementar uma solução para esta pergunta. http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
ChrisP
@ChrisP Esse link afirma que não tenha sido atualizado para o iOS 4.0
Bae
Este código pode ser útil: gist.github.com/TimMedcalf/9505416
landonandrey
Siga o URL abaixo, ele funcionará: stackoverflow.com/questions/48922266/…
Venkatesh G

Respostas:

126

Se você usar UITableViewController em vez de UIViewController, ele fará isso automaticamente.

Sam Ho
fonte
13
Você tentou e descobriu que não estava funcionando? Ou a solução é simples demais para você acreditar? Apenas estenda o UITableViewController em vez de UIViewController e a célula que contém os campos de texto rolará acima do teclado sempre que os campos de texto se tornarem o primeiro respondedor. Não é necessário código extra.
Sam Ho
3
Sim, mas especialmente no iPad, precisamos de uma maneira de fazer isso que não envolva o UITableViewController.
Bob Spryn
13
Para esclarecer, não é uma resposta razoável dizer que toda vez que você usa uma tableview, ela precisa estar em tela cheia, especialmente em um iPad. Existem muitos exemplos de ótimos aplicativos que não fazem isso. Por exemplo, muitos dos da Apple, incluindo o aplicativo Contatos no iPad.
Bob Spryn
32
Não funcionará se você substituir [super viewWillAppear: YES]. Fora isso, deve funcionar.
Rambatino 16/05
18
Se você substituir o viewWillAppear: (BOOL) animado, não se esqueça de chamar [super viewWillAppear: animated]; :)
Médéric Petit
93

A função que faz a rolagem pode ser muito mais simples:

- (void) textFieldDidBeginEditing:(UITextField *)textField {
    UITableViewCell *cell;

    if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
    // Load resources for iOS 6.1 or earlier
        cell = (UITableViewCell *) textField.superview.superview;

    } else {
        // Load resources for iOS 7 or later
        cell = (UITableViewCell *) textField.superview.superview.superview; 
       // TextField -> UITableVieCellContentView -> (in iOS 7!)ScrollView -> Cell!
    }
    [tView scrollToRowAtIndexPath:[tView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

É isso aí. Sem cálculos.

user91083
fonte
2
E porque não?! Apenas substitua UITableViewScrollPositionTop por UITableViewScrollPositionMiddle. Você só precisa redimensionar o UITableView para ajustar a área visível, é claro.
11388 Mihai Damian
3
Parece não funcionar se um UITableViewController cuidou do redimensionamento da exibição da tabela quando o teclado é mostrado: o controlador reduz o tamanho visível com a contentInset, que aparentemente não é levado em consideração ao solicitar visibleRowsou indexPathsForVisibleRows.
Julian D.
16
Não funciona nas últimas linhas da exibição de tabela. O teclado ainda obscurecerá todas as linhas que não podem ser roladas acima do teclado.
Alex Zavatone
3
Para que o comportamento de rolagem automática funcione nas últimas linhas da tabela, detecte quando essas linhas começam a editar e inclua um rodapé no final da visualização de tabela com uma visualização em branco de uma certa altura. Isso permitirá que o tableview role as células para o local correto.
Sammio2
10
Chegar à célula através de uma cadeia de chamadas para a super visão não é confiável, a menos que você tenha certeza de que está realmente chegando à célula. Veja stackoverflow.com/a/17757851/1371070 e stackoverflow.com/a/17758021/1371070
Cezar
70

Estou fazendo algo muito semelhante, é genérico, não há necessidade de calcular algo específico para o seu código. Basta verificar as observações no código:

Em MyUIViewController.h

@interface MyUIViewController: UIViewController <UITableViewDelegate, UITableViewDataSource>
{
     UITableView *myTableView;
     UITextField *actifText;
}

@property (nonatomic, retain) IBOutlet UITableView *myTableView;
@property (nonatomic, retain) IBOutlet UITextField *actifText;

- (IBAction)textFieldDidBeginEditing:(UITextField *)textField;
- (IBAction)textFieldDidEndEditing:(UITextField *)textField;

-(void) keyboardWillHide:(NSNotification *)note;
-(void) keyboardWillShow:(NSNotification *)note;

@end

No MyUIViewController.m

@implementation MyUIViewController

@synthesize myTableView;
@synthesize actifText;

- (void)viewDidLoad 
{
    // Register notification when the keyboard will be show
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(keyboardWillShow:)
                                          name:UIKeyboardWillShowNotification
                                          object:nil];

    // Register notification when the keyboard will be hide
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(keyboardWillHide:)
                                          name:UIKeyboardWillHideNotification
                                          object:nil];
}

// To be link with your TextField event "Editing Did Begin"
//  memoryze the current TextField
- (IBAction)textFieldDidBeginEditing:(UITextField *)textField
{
    self.actifText = textField;
}

// To be link with your TextField event "Editing Did End"
//  release current TextField
- (IBAction)textFieldDidEndEditing:(UITextField *)textField
{
    self.actifText = nil;
}

-(void) keyboardWillShow:(NSNotification *)note
{
    // Get the keyboard size
    CGRect keyboardBounds;
    [[note.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds];

    // Detect orientation
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGRect frame = self.myTableView.frame;

    // Start animation
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:0.3f];

    // Reduce size of the Table view 
    if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
        frame.size.height -= keyboardBounds.size.height;
    else 
        frame.size.height -= keyboardBounds.size.width;

    // Apply new size of table view
    self.myTableView.frame = frame;

    // Scroll the table view to see the TextField just above the keyboard
    if (self.actifText)
      {
        CGRect textFieldRect = [self.myTableView convertRect:self.actifText.bounds fromView:self.actifText];
        [self.myTableView scrollRectToVisible:textFieldRect animated:NO];
      }

    [UIView commitAnimations];
}

-(void) keyboardWillHide:(NSNotification *)note
{
    // Get the keyboard size
    CGRect keyboardBounds;
    [[note.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds];

    // Detect orientation
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGRect frame = self.myTableView.frame;

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:0.3f];

    // Increase size of the Table view 
    if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
        frame.size.height += keyboardBounds.size.height;
    else 
        frame.size.height += keyboardBounds.size.width;

    // Apply new size of table view
    self.myTableView.frame = frame;

    [UIView commitAnimations];
}

@end

Versão Swift 1.2+:

class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var activeText: UITextField!
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: Selector("keyboardWillShow:"),
            name: UIKeyboardWillShowNotification,
            object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: Selector("keyboardWillHide:"),
            name: UIKeyboardWillHideNotification,
            object: nil)
    }

    func textFieldDidBeginEditing(textField: UITextField) {
        activeText = textField
    }

    func textFieldDidEndEditing(textField: UITextField) {
        activeText = nil
    }

    func keyboardWillShow(note: NSNotification) {
        if let keyboardSize = (note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            var frame = tableView.frame
            UIView.beginAnimations(nil, context: nil)
            UIView.setAnimationBeginsFromCurrentState(true)
            UIView.setAnimationDuration(0.3)
            frame.size.height -= keyboardSize.height
            tableView.frame = frame
            if activeText != nil {
                let rect = tableView.convertRect(activeText.bounds, fromView: activeText)
                tableView.scrollRectToVisible(rect, animated: false)
            }
            UIView.commitAnimations()
        }
    }

    func keyboardWillHide(note: NSNotification) {
        if let keyboardSize = (note.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
            var frame = tableView.frame
            UIView.beginAnimations(nil, context: nil)
            UIView.setAnimationBeginsFromCurrentState(true)
            UIView.setAnimationDuration(0.3)
            frame.size.height += keyboardSize.height
            tableView.frame = frame
            UIView.commitAnimations()
        }
    }
}
ZeLegolas
fonte
usar as notificações e obter a altura do teclado ao incorporar a orientação do dispositivo foi incrível, obrigado por isso! a parte de rolagem não trabalho para mim por alguma razão, então eu tive que usar isto:[tableView scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionMiddle animated: YES];
Taber
7
Esta é a melhor resposta aqui, eu acho. Muito limpo. Apenas duas coisas: 1) seu viewDidLoad não está chamando [super viewDidLoad] e 2) eu tive que ter em alguma matemática da barra de tabulação as linhas frame.size.height. Caso contrário, perfeito! Obrigado.
precisa saber é o seguinte
3
Aqui está a modificação que o toxaq descreve: MyAppDelegate *appDelegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate]; CGFloat tabBarHeight = appDelegate.tabBarController.tabBar.frame.size.height; Subtraia tabBarHeight da altura do teclado onde quer que você use a altura do teclado.
Steve N
1
Se o usuário tocar no campo de texto está funcionando perfeitamente. mas se o usuário tocar em outro campo de texto sem pressionar a tecla Enter, reduzirá o tamanho da visualização da tabela.
Bhavin Ramani
1
@BhavinRamani concordou. Adicionei uma propriedade booleana simples para lembrar se o teclado já está sendo exibido ou não, e pular a reexecução do código quando desnecessário.
Dirty Henry
46

A solução mais simples para o Swift 3 , baseada na solução Bartłomiej Semańczyk :

override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(self, selector: #selector(CreateEditRitualViewController.keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(CreateEditRitualViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
}

deinit {
    NotificationCenter.default.removeObserver(self)
}

// MARK: Keyboard Notifications

@objc func keyboardWillShow(notification: NSNotification) {
    if let keyboardHeight = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height {
        tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
    UIView.animate(withDuration: 0.2, animations: {
        // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here
        self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
    })
}
squall2022
fonte
Um pequeno detalhe ... Usando Notificationvez de NSNotificationseria mais "Swift 3-y" :-)
Nicolas Miari
Isso ajudará no reposicionamento, se houver um UIView.animate ao redor da barra de navegação, se let - if let frame = self.navigationController? .NavigationBar.frame {let y = frame.size.height + frame.origin.y}
Sean Dev
quando a rotação acontece há falha no carregamento e alguma célula desaparece quando tableview é rolada manully
jothikenpachi
Boa solução obrigado! Nota - não é mais necessário remover o removeObserver.
Nick McConnell
44

Eu tive o mesmo problema, mas notei que ele aparece apenas em uma exibição. Então comecei a procurar as diferenças nos controladores.

Eu descobri que o comportamento de rolagem é definido na - (void)viewWillAppear:(BOOL)animatedsuper instância.

Portanto, certifique-se de implementar assim:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // your code
}

E não importa se você usa UIViewControllerou UITableViewController; verifiquei colocando a UITableViewcomo uma subview de self.view no UIViewController. Foi o mesmo comportamento. A visualização não permitia rolar se a chamada [super viewWillAppear:animated];estava ausente.

phse
fonte
1
Isso funcionou excelentemente. Fiquei me perguntando por que as pessoas disseram que o UITableView faria isso por mim e isso resolveu. Obrigado!
precisa saber é o seguinte
5
Eu também tive esse problema, essa resposta deve chegar ao topo!
Amiel Martin
Eu perdi muito tempo tentando descobrir isso sozinho ... graças;)
budidino
O +1 estava começando a chorar um pouco, eu tinha essa linha, mas precisava também [tableViewController viewWillAppear: animated]; porque estou adicionando um UITableViewController a um UIViewController. sem mais lágrimas :)
Colin Lamarre
41

Eu posso ter perdido isso, pois não li o post inteiro aqui, mas o que eu criei parece enganosamente simples. Eu não passei por isso, testando em todas as situações, mas parece que deve funcionar muito bem.

simplesmente ajuste o contentInset da tableview pela altura do teclado e role a célula para baixo:

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

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
    self.myTableView.contentInset = contentInsets;
    self.myTableView.scrollIndicatorInsets = contentInsets;

    [self.myTableView scrollToRowAtIndexPath:self.currentField.indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

e claro

- (void)keyboardWasHidden:(NSNotification *)aNotification
{
    [UIView animateWithDuration:.3 animations:^(void) 
    {
        self.myTableView.contentInset = UIEdgeInsetsZero;
        self.myTableView.scrollIndicatorInsets = UIEdgeInsetsZero;
    }];
}

isso é muito simples? estou esquecendo de algo? até agora está funcionando bem para mim, mas como eu disse, não o coloquei no espremedor ...

mickm
fonte
IMO, esta é a melhor solução. A única coisa que eu mudaria é a sua duração codificada para[aNotification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
Andy
É muito simples. Mas um problema que acho é que ele não animará a alteração contentInsetnem alterará muito os limites da rolagem.
6773 Geek
Este funcionou melhor para mim, no entanto, algumas questões. 1) Não sei onde você pode obter "currentField.indexPath", então tive que salvar o indexPath.row como a tag do campo e criar o indexPath posteriormente. 2) Não funciona para as linhas na parte superior da tabela, elas as rolam para fora da tela. Teve que adicionar algum código para rolar apenas se o indexPath do currentField for maior do que o que pode caber na tela. 3) teve que usar kbSize.Width (em vez de altura) no iPad se da paisagem
Travis M.
desculpe, nós nos acostumamos tanto ao nosso próprio código que às vezes esquecemos, não é? currentField é o campo de texto atual que estou trabalhando com, e indexPath é uma extensão que eu adicionado à classe que simplesmente acrescenta um NSIndexPath então eu sei o celular esta se encontra.
mickm
Este é o caminho a seguir, não movendo quadros apenas modificando as propriedades da tabela.
Nextorlg
35

Acho que encontrei a solução para combinar com o comportamento dos aplicativos da Apple.

Primeiro, em seu viewWillAppear: assine as notificações do teclado para saber quando o teclado será exibido e oculto e o sistema informará o tamanho do teclado, mas não se esqueça de cancelar o registro em seu viewWillDisappear :.

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

Implemente os métodos semelhantes aos abaixo para ajustar o tamanho do seu tableView para corresponder à área visível assim que o teclado aparecer. Aqui, acompanho o estado do teclado separadamente para que eu possa escolher quando definir o tableView de volta à sua altura máxima, pois você recebe essas notificações a cada alteração de campo. Não se esqueça de implementar keyboardWillHide: e escolha um local apropriado para corrigir o tamanho do seu tableView.

-(void) keyboardWillShow:(NSNotification *)note
{
    CGRect keyboardBounds;
    [[note.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keyboardBounds];
    keyboardHeight = keyboardBounds.size.height;
    if (keyboardIsShowing == NO)
    {
        keyboardIsShowing = YES;
        CGRect frame = self.view.frame;
        frame.size.height -= keyboardHeight;

        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationBeginsFromCurrentState:YES];
        [UIView setAnimationDuration:0.3f];
        self.view.frame = frame;
        [UIView commitAnimations];
    }
}

Agora, aqui está o bit de rolagem: primeiro calculamos alguns tamanhos, depois vemos onde estamos na área visível e definimos o retângulo que queremos rolar para ser a meia vista acima ou abaixo do meio do campo de texto onde está a vista. Nesse caso, temos uma matriz de UITextFields e uma enumeração que os acompanha, portanto, multiplicar o rowHeight pelo número da linha nos fornece o deslocamento real do quadro nessa exibição externa.

- (void) textFieldDidBeginEditing:(UITextField *)textField
{
    CGRect frame = textField.frame;
    CGFloat rowHeight = self.tableView.rowHeight;
    if (textField == textFields[CELL_FIELD_ONE])
    {
        frame.origin.y += rowHeight * CELL_FIELD_ONE;
    }
    else if (textField == textFields[CELL_FIELD_TWO])
    {
        frame.origin.y += rowHeight * CELL_FIELD_TWO;
    }
    else if (textField == textFields[CELL_FIELD_THREE])
    {
        frame.origin.y += rowHeight * CELL_FIELD_THREE;
    }
    else if (textField == textFields[CELL_FIELD_FOUR])
    {
        frame.origin.y += rowHeight * CELL_FIELD_FOUR;
    }
    CGFloat viewHeight = self.tableView.frame.size.height;
    CGFloat halfHeight = viewHeight / 2;
    CGFloat midpoint = frame.origin.y + (textField.frame.size.height / 2);
    if (midpoint < halfHeight)
    {
        frame.origin.y = 0;
        frame.size.height = midpoint;
    }
    else
    {
        frame.origin.y = midpoint;
        frame.size.height = midpoint;
    }
    [self.tableView scrollRectToVisible:frame animated:YES];
}

Isso parece funcionar muito bem.

Michael Baltaks
fonte
Ótima solução. Obrigado por publicá-lo.
Alex Reynolds
2
UIKeyboardBoundsUserInfoKeyfoi descontinuado a partir do iOS 3.2. Veja minha solução abaixo que funciona em todas as versões atuais do iOS ≥ 3.0. / @ iPhoneDev
Ortwin Gentz
Isso foi mais complicado do que precisava ser. A resposta de @ user91083 foi simples e funciona.
Richard Brightwell
1
Há um pequeno problema nesta solução. keyboardWillShow é chamado de APÓS textFieldDidBeginEditing, então quando queremos rolagem para alguma célula, quadro do tableView ainda não mudou, por isso não vai funcionar
HiveHicks
35

Se você pode usar UITableViewController, você obtém a funcionalidade gratuitamente. Às vezes, no entanto, isso não é uma opção, especificamente se você precisar de várias visualizações, não apenas oUITableView .

Algumas das soluções apresentadas aqui não funcionam no iOS ≥4, outras não no iPad ou no modo paisagem, algumas não funcionam nos teclados Bluetooth (onde não queremos rolagem), outras não. funciona ao alternar entre vários campos de texto. Portanto, se você escolher qualquer solução, teste esses casos. Esta é a solução que usamos usada no InAppSettingsKit :

- (void)_keyboardWillShow:(NSNotification*)notification {
    if (self.navigationController.topViewController == self) {
        NSDictionary* userInfo = [notification userInfo];

        // we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0
        NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"];
        if (!keyboardFrameValue) {
            keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"];
        }

        // Reduce the tableView height by the part of the keyboard that actually covers the tableView
        CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
        if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
            windowRect = IASKCGRectSwap(windowRect);
        }
        CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
        if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
            viewRectAbsolute = IASKCGRectSwap(viewRectAbsolute);
        }
        CGRect frame = _tableView.frame;
        frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute);

        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
        [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
        _tableView.frame = frame;
        [UIView commitAnimations];

        UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview;
        NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell];

        // iOS 3 sends hide and show notifications right after each other
        // when switching between textFields, so cancel -scrollToOldPosition requests
        [NSObject cancelPreviousPerformRequestsWithTarget:self];

        [_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
    }
}

- (void) scrollToOldPosition {
  [_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)_keyboardWillHide:(NSNotification*)notification {
    if (self.navigationController.topViewController == self) {
        NSDictionary* userInfo = [notification userInfo];

        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
        [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
        _tableView.frame = self.view.bounds;
        [UIView commitAnimations];

        [self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1];
    }
}   

Aqui está o código completo da classe no InAppSettingsKit. Para testá-lo, use o painel filho "Lista Completa", onde você pode testar os cenários mencionados acima.

Ortwin Gentz
fonte
Não sei se é útil usar seqüências de caracteres em vez de constantes, porque se a Apple tiver a ideia de alterar a String internamente por alguns motivos, sua solução não estará mais funcionando. O problema é que você não está
@ iPortable: não é o ideal, eu sei. Você pode sugerir uma solução melhor que seja executada em todas as versões ≥3,0?
Ortwin Gentz
1
Funciona como charme, mas não para UIInterfaceOrientationPortraitUpsideDown. Em seguida, o cálculo da redução de altura também deve ser baseado de cabeça para baixo: CGFloat reduzaAltura = keyboardRect.size.height - (CGRectGetMinY (viewRectAbsolute) - CGRectGetMinY (windowRect));
Klaas
Isso tem falhas visuais muito visíveis no meu iPad e no Simulador (4.3). Perceptível de usar. :(
Bob Spryn
Gosto que esta solução seja responsável por uma barra de ferramentas na parte inferior da tela.
Pdemarest
24

A solução mais simples para Swift :

override func viewDidLoad() {
    super.viewDidLoad()

    searchBar?.becomeFirstResponder()
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillShow(_:)), name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.keyboardWillHide(_:)), name: UIKeyboardDidHideNotification, object: nil)
}

deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

func keyboardWillShow(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        if let keyboardHeight = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue.size.height {
            tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
        }
    }
}

func keyboardWillHide(notification: NSNotification) {
    UIView.animateWithDuration(0.2, animations: { self.table_create_issue.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) })
    // For some reason adding inset in keyboardWillShow is animated by itself but removing is not, that's why we have to use animateWithDuration here
    }
Bartłomiej Semańczyk
fonte
Funciona perfeitamente, são necessários cálculos mínimos. Adicionei um código que restaura as inserções da tabela para concluir esta resposta.
Vitalii
Melhor solução, obrigado. Eu
postei
Solução super perfeita já vista, tentei outras, mas tem alguns problemas. Sua solução funciona perfeitamente no ios 10.2.
Wangdu Lin
8

Espero que vocês já tenham uma solução lendo tudo isso. Mas encontrei minha solução da seguinte maneira. Estou esperando que você já tenha um celular UITextField. Portanto, na preparação, mantenha o índice da linha na tag do campo de texto.

cell.textField.tag = IndexPath.row;

Crie uma activeTextFieldinstância UITextFieldcom escopo global, como abaixo:

@interface EditViewController (){

    UITextField *activeTextField;

}

Então, agora você acabou de copiar e colar meu código no final. E também não se esqueça de adicionarUITextFieldDelegate

#pragma mark - TextField Delegation

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

    activeTextField = textField;

    return YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField{

    activeTextField = nil;

}

Registra o teclado notifications

#pragma mark - Keyboard Activity

- (void)registerForKeyboardNotifications

{

    [[NSNotificationCenter defaultCenter] addObserver:self

                                         selector:@selector(keyboardWasShown:)

                                             name:UIKeyboardDidShowNotification object:nil];



    [[NSNotificationCenter defaultCenter] addObserver:self

                                         selector:@selector(keyboardWillBeHidden:)

                                             name:UIKeyboardWillHideNotification object:nil];



}

Teclado de alças Notifications:

Chamado quando o UIKeyboardDidShowNotificationé enviado.

- (void)keyboardWasShown:(NSNotification*)aNotification

{

    NSDictionary* info = [aNotification userInfo];

    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);

    [self.tableView setContentInset:contentInsets];

    [self.tableView setScrollIndicatorInsets:contentInsets];

    NSIndexPath *currentRowIndex = [NSIndexPath indexPathForRow:activeTextField.tag inSection:0];

    [self.tableView scrollToRowAtIndexPath:currentRowIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];

}

Chamado quando o UIKeyboardWillHideNotificationé enviado

- (void)keyboardWillBeHidden:(NSNotification*)aNotification

{

    UIEdgeInsets contentInsets = UIEdgeInsetsZero;

    [self.tableView setContentInset:contentInsets];

    [self.tableView setScrollIndicatorInsets:contentInsets];

}

Agora, resta uma coisa: chame o registerForKeyboardNotificationsmétodo para o ViewDidLoadmétodo da seguinte maneira:

- (void)viewDidLoad {

    [super viewDidLoad];

    // Registering keyboard notification

    [self registerForKeyboardNotifications];

    // Your codes here...

}

Você está pronto, espero que sua textFieldsvontade não fique mais oculta pelo teclado.

Hussain KMR Behestee
fonte
6

Combinando e preenchendo os espaços em branco de várias respostas (em particular Ortwin Gentz, usuário 98013) e outra postagem, isso funcionará imediatamente para o SDK 4.3 em um iPad no modo Retrato ou Paisagem:

@implementation UIView (FindFirstResponder)
- (UIResponder *)findFirstResponder
{
  if (self.isFirstResponder) {        
    return self;     
  }

  for (UIView *subView in self.subviews) {
    UIResponder *firstResponder = [subView findFirstResponder];
    if (firstResponder != nil) {
      return firstResponder;
    }
  }

  return nil;
}
@end

@implementation MyViewController

- (UIResponder *)currentFirstResponder {
  return [self.view findFirstResponder];
}

- (IBAction)editingEnded:sender {
  [sender resignFirstResponder];
}

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

- (void)textFieldDidBeginEditing:(UITextField *)textField {
  UITableViewCell *cell = (UITableViewCell*) [[textField superview] superview];
  [_tableView scrollToRowAtIndexPath:[_tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)keyboardWillShow:(NSNotification*)notification {
  if ([self currentFirstResponder] != nil) {
    NSDictionary* userInfo = [notification userInfo];

    // we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0
    NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"];
    if (!keyboardFrameValue) {
      keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"];
    }

    // Reduce the tableView height by the part of the keyboard that actually covers the tableView
    CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
    CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
    CGRect frame = _tableView.frame;
    if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
      windowRect = CGRectMake(windowRect.origin.y, windowRect.origin.x, windowRect.size.height, windowRect.size.width);
      viewRectAbsolute = CGRectMake(viewRectAbsolute.origin.y, viewRectAbsolute.origin.x, viewRectAbsolute.size.height, viewRectAbsolute.size.width);
    }
    frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute);

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
    _tableView.frame = frame;
    [UIView commitAnimations];

    UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview;
    NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell];

    // iOS 3 sends hide and show notifications right after each other
    // when switching between textFields, so cancel -scrollToOldPosition requests
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    _topmostRowBeforeKeyboardWasShown = [[_tableView indexPathsForVisibleRows] objectAtIndex:0];
    [_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
  }
}

- (void) scrollToOldPosition {
  [_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

- (void)keyboardWillHide:(NSNotification*)notification {
  if ([self currentFirstResponder] != nil) {

    NSDictionary* userInfo = [notification userInfo];

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
    [UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
    _tableView.frame = self.view.bounds;
    [UIView commitAnimations];

    [self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1];
  }
}   

@end
Colin
fonte
Usei esse código no iOS 4.x, mas no iOS5 ele trava no scrollToOldPosition porque o _topmostRowBeforeKeyboardWasShown já está liberado. Ainda não sei qual é a solução. Provavelmente lembre-se do índice em vez do objeto.
Thomas Tempelmann
5

Se você usar uma visualização uitabl para colocar seus campos de texto ( de Jeff Lamarche ), basta rolar a visualização de tabela usando o método delegate dessa maneira.

(Nota: meus campos de texto são armazenados em uma matriz com o mesmo índice que a linha da tableview)

- (void) textFieldDidBeginEditing:(UITextField *)textField
    {

        int index;
        for(UITextField *aField in textFields){

            if (textField == aField){
                index = [textFields indexOfObject:aField]-1;
            }
        }

         if(index >= 0) 
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];

        [super textFieldDidBeginEditing:textField];
    }
Corey Floyd
fonte
Você não atualiza o quadro tableView. Em seguida, as barras de rolagem e o comportamento da rolagem estão incorretos quando o teclado é mostrado. Veja minha solução.
Ortwin Gentz
5

As notificações do teclado funcionam, mas o código de exemplo da Apple para isso pressupõe que a visualização de rolagem é a visualização raiz da janela. Geralmente não é esse o caso. Você precisa compensar as barras de guias, etc., para obter o deslocamento correto.

É mais fácil do que parece. Aqui está o código que eu uso em um UITableViewController. Possui duas variáveis ​​de instância, hiddenRect e keyboardShown.

// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification {
    if (keyboardShown)
        return;

    NSDictionary* info = [aNotification userInfo];

    // Get the frame of the keyboard.
    NSValue *centerValue = [info objectForKey:UIKeyboardCenterEndUserInfoKey];
    NSValue *boundsValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
    CGPoint keyboardCenter = [centerValue CGPointValue];
    CGRect keyboardBounds = [boundsValue CGRectValue];
    CGPoint keyboardOrigin = CGPointMake(keyboardCenter.x - keyboardBounds.size.width / 2.0,
                                         keyboardCenter.y - keyboardBounds.size.height / 2.0);
    CGRect keyboardScreenFrame = { keyboardOrigin, keyboardBounds.size };


    // Resize the scroll view.
    UIScrollView *scrollView = (UIScrollView *) self.tableView;
    CGRect viewFrame = scrollView.frame;
    CGRect keyboardFrame = [scrollView.superview convertRect:keyboardScreenFrame fromView:nil];
    hiddenRect = CGRectIntersection(viewFrame, keyboardFrame);

    CGRect remainder, slice;
    CGRectDivide(viewFrame, &slice, &remainder, CGRectGetHeight(hiddenRect), CGRectMaxYEdge);
    scrollView.frame = remainder;

    // Scroll the active text field into view.
    CGRect textFieldRect = [/* selected cell */ frame];
    [scrollView scrollRectToVisible:textFieldRect animated:YES];

    keyboardShown = YES;
}


// Called when the UIKeyboardDidHideNotification is sent
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
    // Reset the height of the scroll view to its original value
    UIScrollView *scrollView = (UIScrollView *) self.tableView;
    CGRect viewFrame = [scrollView frame];
    scrollView.frame = CGRectUnion(viewFrame, hiddenRect);

    keyboardShown = NO;
}
Donovan Voss
fonte
UIKeyboardCenterEndUserInfoKeye UIKeyboardBoundsUserInfoKeyestão obsoletos no iOS 3.2. Veja minha solução abaixo que funciona em todas as versões atuais do iOS ≥ 3.0.
Ortwin Gentz
5

Se você usar Three20, use a autoresizesForKeyboardpropriedade Basta definir no -initWithNibName:bundlemétodo do seu controlador de exibição

self.autoresizesForKeyboard = YES

Isso cuida de:

  1. Ouvindo notificações do teclado e ajustando o quadro da visualização da tabela
  2. Rolando para o primeiro respondedor

Feito e feito.

Pense de cima para baixo
fonte
o que é Three20 aqui? Você pode especificar isso?
Mubin Mall
5

Minha abordagem:

Subclasse primeiro UITextField e adiciono uma propriedade indexPath. No método cellFor ..., entrego a propriedade indexPath.

Então eu adiciono o seguinte código:

UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:textField.indexPath];

CGPoint cellPoint = [cell convertPoint:textField.center toView:self.tableView];
[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, cellPoint.y-50);}];

para o textFieldShould / WillBegin ... etc.

Quando o teclado desaparece, você deve invertê-lo com:

[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, 0);}];
Sven
fonte
4

Uma solução mais alinhada ao fluxo. Ele entra nos métodos de delegação UITextField, para que não exija alterações com notificações do UIKeyboard.

Notas de implementação:

kSettingsRowHeight - a altura de um UITableViewCell.

offsetTarget e offsetThreshold são baseados em kSettingsRowHeight. Se você usar uma altura de linha diferente, defina esses valores para a propriedade y do ponto. [alt: calcula o deslocamento da linha de uma maneira diferente.]

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
CGFloat offsetTarget    = 113.0f; // 3rd row
CGFloat offsetThreshold = 248.0f; // 6th row (i.e. 2nd-to-last row)

CGPoint point = [self.tableView convertPoint:CGPointZero fromView:textField];

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

CGRect frame = self.tableView.frame;
if (point.y > offsetThreshold) {
    self.tableView.frame = CGRectMake(0.0f,
                      offsetTarget - point.y + kSettingsRowHeight,
                      frame.size.width,
                      frame.size.height);
} else if (point.y > offsetTarget) {
    self.tableView.frame = CGRectMake(0.0f,
                      offsetTarget - point.y,
                      frame.size.width,
                      frame.size.height);
} else {
    self.tableView.frame = CGRectMake(0.0f,
                      0.0f,
                      frame.size.width,
                      frame.size.height);
}

[UIView commitAnimations];

return YES;

}

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

[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.2];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

CGRect frame = self.tableView.frame;
self.tableView.frame = CGRectMake(0.0f,
                  0.0f,
                  frame.size.width,
                  frame.size.height);

[UIView commitAnimations];

return YES;

}

Kelvin
fonte
4

Use o UITextField's delegatemétodo:

Rápido

func textFieldShouldBeginEditing(textField: UITextField) -> bool {
  let txtFieldPosition = textField.convertPoint(textField.bounds.origin, toView: yourTableViewHere)
  let indexPath = yourTablViewHere.indexPathForRowAtPoint(txtFieldPosition)
  if indexPath != nil {
     yourTablViewHere.scrollToRowAtIndexPath(indexPath!, atScrollPosition: .Top, animated: true)
  }
  return true
}

Objetivo-C

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
  CGPoint txtFieldPosition = [textField convertPoint:CGPointZero toView: yourTablViewHere];
  NSLog(@"Begin txtFieldPosition : %@",NSStringFromCGPoint(txtFieldPosition));
  NSIndexPath *indexPath = [yourTablViewHere indexPathForRowAtPoint:txtFieldPosition];

  if (indexPath != nil) {
     [yourTablViewHere scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
  }
  return YES;
}
Paresh Navadiya
fonte
Olá, Estou com problemas para que isso funcione no Swift. Meus UITextFields conectados ao UITableViewCell. Se eu implementar esse código dentro do meu UIViewController, não tenho acesso ao UITextFields. Alguma ideia?
Vetuka
4

Solução completa Swift 4.2

Eu criei o GIST com um conjunto de protocolos que simplifica o trabalho com a adição de espaço extra quando o teclado é mostrado, oculto ou alterado.

Características :

  • Funciona corretamente com as alterações no quadro do teclado (por exemplo, alterações na altura do teclado como emojii → teclado normal).
  • Suporte a TabBar e ToolBar para o exemplo UITableView (em outros exemplos, você recebe inserções incorretas).
  • Duração da animação dinâmica (não codificada).
  • Abordagem orientada a protocolo que pode ser facilmente modificada para você.

Uso

Exemplo de uso básico no controlador de exibição que contém alguma exibição de rolagem (a exibição de tabela também é suportada, é claro).

class SomeViewController: UIViewController {
  @IBOutlet weak var scrollView: UIScrollView!

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    addKeyboardFrameChangesObserver()
  }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    removeKeyboardFrameChangesObserver()
  }
}

extension SomeViewController: ModifableInsetsOnKeyboardFrameChanges {
  var scrollViewToModify: UIScrollView { return scrollView }
}

Núcleo: quadro muda observador

O protocolo KeyboardChangeFrameObserverdispara um evento toda vez que o quadro do teclado é alterado (incluindo exibição, ocultação, alteração do quadro).

  1. Ligue addKeyboardFrameChangesObserver()para viewWillAppear()ou método semelhante.
  2. Ligue removeKeyboardFrameChangesObserver()para viewWillDisappear()ou método semelhante.

Implementação: visualização de rolagem

ModifableInsetsOnKeyboardFrameChangesO protocolo adiciona UIScrollViewsuporte ao protocolo principal. Ele altera as inserções da exibição de rolagem quando o quadro do teclado é alterado.

Sua turma precisa definir a visualização de rolagem; as inserções de uma pessoa serão aumentadas / diminuídas nas alterações no quadro do teclado.

var scrollViewToModify: UIScrollView { get }
Vasily
fonte
3

Como você possui campos de texto em uma tabela, a melhor maneira é redimensionar a tabela - você precisa definir o tableView.frame para ter uma altura menor pelo tamanho do teclado (acho que cerca de 165 pixels) e expandi-lo novamente quando o teclado é descartado.

Opcionalmente, também é possível desativar a interação do usuário para o tableView nesse momento, se você não desejar que o usuário role.

Kendall Helmstetter Gelner
fonte
Eu apóio isso e me registro no UIKeyboardWillShowNotification para encontrar o tamanho do teclado dinamicamente.
benzado 03/03
O número retornado pelo objeto de notificação não funciona. Ou pelo menos não em 2,2, o número retornado estava incorreto e eu tive que codificar o valor 165 para ajustar a altura correcta (que estava fora por cinco a dez pixels)
Kendall Helmstetter Gelner
2

Isso funciona perfeitamente e também no iPad.

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

    if(textField == textfield1){
            [accountName1TextField becomeFirstResponder];
        }else if(textField == textfield2){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield3 becomeFirstResponder];

        }else if(textField == textfield3){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield4 becomeFirstResponder];

        }else if(textField == textfield4){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield5 becomeFirstResponder];

        }else if(textField == textfield5){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:3 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield6 becomeFirstResponder];

        }else if(textField == textfield6){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:4 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield7 becomeFirstResponder];

        }else if(textField == textfield7){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:5 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield8 becomeFirstResponder];

        }else if(textField == textfield8){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:6 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textfield9 becomeFirstResponder];

        }else if(textField == textfield9){
            [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:7 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
            [textField resignFirstResponder];
        }
WrightsCS
fonte
Por que você está pensando e usando casos especiais para cada campo de texto? Identifique cada campo de texto do NSIndexPath da célula e altere essa instrução if desagradável em 2 linhas de código. Você realmente deseja uma chamada cellForRowAtIndexPath e, em seguida, obtém o textField da célula.
Alex Zavatone #
Na verdade, considerando o quão inacreditável é essa situação no iOS, acho que é bom escrever um código "totalmente desenrolado, ridiculamente literal" para essa situação.
Fattie
Considerando que esta resposta foi dada há mais de 6 anos.
precisa
2

Tentei quase a mesma abordagem e criei um código mais simples e menor para a mesma. Eu criei um IBOutlet iTextView e associado ao UITextView no IB.

 -(void)keyboardWillShow:(NSNotification *)notification
    {
        NSLog(@"Keyboard");
        CGRect keyFrame = [[[notification userInfo]objectForKey:UIKeyboardFrameEndUserInfoKey]CGRectValue];

        [UIView beginAnimations:@"resize view" context:nil];
        [UIView setAnimationCurve:1];
        [UIView setAnimationDuration:1.0];
        CGRect frame = iTableView.frame;
        frame.size.height = frame.size.height -  keyFrame.size.height;
        iTableView.frame = frame;
        [iTableView scrollRectToVisible:frame animated:YES];
        [UIView commitAnimations];

    }
thesummersign
fonte
2

Então, depois de horas de trabalho exaustivo tentando usar essas soluções atuais (e totalmente falhas), finalmente consegui que as coisas funcionassem bem e as atualizei para usar os novos blocos de animação. Minha resposta é inteiramente baseada em resposta de Ortwin acima .

Portanto, por qualquer motivo, o código acima simplesmente não estava funcionando para mim. Minha configuração parecia bastante semelhante às outras, mas talvez porque eu estivesse em um iPad ou 4.3 ... não faço ideia. Estava fazendo algumas contas malucas e tirando minha foto da tela.

Veja o resultado final da minha solução: http://screencast.com/t/hjBCuRrPC (ignore a foto. :-P)

Então, eu fui com a essência do que Ortwin estava fazendo, mas mudei como estava fazendo algumas contas para adicionar a origem.y e tamanho. altura da minha exibição de tabela com a altura do teclado. Quando subtraio a altura da janela desse resultado, ele me diz quantas interseções eu tenho. Se for maior que 0 (também conhecido como sobreposição), realizo a animação da altura do quadro.

Além disso, houve alguns problemas de redesenho que foram resolvidos por 1) Aguardando a rolagem para a célula até a animação ser concluída e 2) usando a opção UIViewAnimationOptionBeginFromCurrentState ao ocultar o teclado.

Algumas coisas a serem observadas.

  • _topmostRowBeforeKeyboardWasShown e _originalFrame são variáveis ​​de instância declaradas no cabeçalho.
  • self.guestEntryTableView é o meu tableView (estou em um arquivo externo)
  • IASKCGRectSwap é o método de Ortwin para inverter as coordenadas de um quadro
  • Eu só atualizo a altura do tableView se pelo menos 50px serão exibidos
  • Como não estou em um UIViewController, não tenho self.view, por isso, apenas retorno o tableView ao seu quadro original

Novamente, eu não teria chegado perto dessa resposta se Ortwin não fornecesse o ponto crucial dela. Aqui está o código:

- (IBAction)textFieldDidBeginEditing:(UITextField *)textField
{
    self.activeTextField = textField;

    if ([self.guestEntryTableView indexPathsForVisibleRows].count) {
        _topmostRowBeforeKeyboardWasShown = (NSIndexPath*)[[self.guestEntryTableView indexPathsForVisibleRows] objectAtIndex:0];
    } else {
        // this should never happen
        _topmostRowBeforeKeyboardWasShown = [NSIndexPath indexPathForRow:0 inSection:0];
        [textField resignFirstResponder];
    }
}

- (IBAction)textFieldDidEndEditing:(UITextField *)textField
{
    self.activeTextField = nil;
}

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

    NSValue* keyboardFrameValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];

    // Reduce the tableView height by the part of the keyboard that actually covers the tableView
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
    CGRect viewRectAbsolute = [self.guestEntryTableView convertRect:self.guestEntryTableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
    CGRect keyboardFrame = [keyboardFrameValue CGRectValue];
    if (UIInterfaceOrientationLandscapeLeft == orientation ||UIInterfaceOrientationLandscapeRight == orientation ) {
        windowRect = IASKCGRectSwap(windowRect);
        viewRectAbsolute = IASKCGRectSwap(viewRectAbsolute);
        keyboardFrame = IASKCGRectSwap(keyboardFrame);
    }

    // fix the coordinates of our rect to have a top left origin 0,0
    viewRectAbsolute = FixOriginRotation(viewRectAbsolute, orientation, windowRect.size.width, windowRect.size.height);

    CGRect frame = self.guestEntryTableView.frame;
    _originalFrame = self.guestEntryTableView.frame;

    int remainder = (viewRectAbsolute.origin.y + viewRectAbsolute.size.height + keyboardFrame.size.height) - windowRect.size.height;

    if (remainder > 0 && !(remainder > frame.size.height + 50)) {
        frame.size.height = frame.size.height - remainder;
        float duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
        [UIView animateWithDuration: duration
                        animations:^{
                            self.guestEntryTableView.frame = frame;
                        }
                        completion:^(BOOL finished){
                            UITableViewCell *textFieldCell = (UITableViewCell*) [[self.activeTextField superview] superview];
                            NSIndexPath *textFieldIndexPath = [self.guestEntryTableView indexPathForCell:textFieldCell];
                            [self.guestEntryTableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
                        }];
    }

}

- (void)keyboardWillHide:(NSNotification*)notification {
    NSDictionary* userInfo = [notification userInfo];
    float duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    [UIView animateWithDuration: duration
                          delay: 0.0
                        options: (UIViewAnimationOptionBeginFromCurrentState)
                     animations:^{
                         self.guestEntryTableView.frame = _originalFrame;
                     }
                     completion:^(BOOL finished){
                         [self.guestEntryTableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
                     }];

}   

#pragma mark CGRect Utility function
CGRect IASKCGRectSwap(CGRect rect) {
    CGRect newRect;
    newRect.origin.x = rect.origin.y;
    newRect.origin.y = rect.origin.x;
    newRect.size.width = rect.size.height;
    newRect.size.height = rect.size.width;
    return newRect;
}

CGRect FixOriginRotation(CGRect rect, UIInterfaceOrientation orientation, int parentWidth, int parentHeight) {
    CGRect newRect;
    switch(orientation)
    {
        case UIInterfaceOrientationLandscapeLeft:
            newRect = CGRectMake(parentWidth - (rect.size.width + rect.origin.x), rect.origin.y, rect.size.width, rect.size.height);
            break;
        case UIInterfaceOrientationLandscapeRight:
            newRect = CGRectMake(rect.origin.x, parentHeight - (rect.size.height + rect.origin.y), rect.size.width, rect.size.height);
            break;
        case UIInterfaceOrientationPortrait:
            newRect = rect;
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            newRect = CGRectMake(parentWidth - (rect.size.width + rect.origin.x), parentHeight - (rect.size.height + rect.origin.y), rect.size.width, rect.size.height);
            break;
    }
    return newRect;
}
Bob Spryn
fonte
Adicionada minha função FixOriginRotation, que corrige o sistema de coordenadas da exibição antes de atualizar seu quadro, etc. Acho que isso é parte do motivo pelo qual eu estava tendo problemas no início. Não sabia que o sistema de coordenadas da janela do iOS girava com o dispositivo!
Bob Spryn
2

Este solução funciona para mim, observe a linha

[tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height+160) animated:YES];

Você pode alterar o valor 160 para combinar com o trabalho com você

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGRect bkgndRect = activeField.superview.frame;
                        bkgndRect.size.height += kbSize.height;
     [activeField.superview setFrame:bkgndRect];
     [tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height+160) animated:YES];
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
   activeField = textField;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
 {
     activeField = nil;
 }
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    tableView.contentInset = contentInsets;
    tableView.scrollIndicatorInsets = contentInsets;
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGRect bkgndRect = activeField.superview.frame;
    //bkgndRect.size.height += kbSize.height;
    [activeField.superview setFrame:bkgndRect];
    [tableView setContentOffset:CGPointMake(0.0, activeField.frame.origin.y-kbSize.height) animated:YES];
}
hiennt
fonte
2

Tópico de discussão muito interessante, eu também enfrentei o mesmo problema pode ser pior porque

  1. Eu estava usando uma célula personalizada e o campo de texto estava dentro disso.
  2. Eu tive que usar o UIViewController para atender aos meus requisitos, por isso não posso tirar proveito do UITableViewController.
  3. Eu tinha critérios de filtro / classificação em minha célula da tabela, ou seja, suas células continuam mudando e acompanhando o caminho do índice e tudo não vai ajudar.

Então, leia os tópicos aqui e implementei minha versão, o que me ajudou a aumentar meu conteúdo no iPad no modo paisagem . Aqui está o código (isso não é à prova de idiotas e tudo, mas corrigiu meu problema) Primeiro você precisa ter um delegado em sua classe de célula personalizada, que na edição começa, envia o campo de texto para o seu viewcontroller e define o activefield = theTextField

// IMPLEMENTADO PARA MANUSEAR SOMENTE O MODO PAISAGEM

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbValue = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGRect aRect = myTable.frame;

    CGSize kbSize = CGSizeMake(kbValue.height, kbValue.width);

    aRect.size.height -= kbSize.height+50;
// This will the exact rect in which your textfield is present
        CGRect rect =  [myTable convertRect:activeField.bounds fromView:activeField];
// Scroll up only if required
    if (!CGRectContainsPoint(aRect, rect.origin) ) {


            [myTable setContentOffset:CGPointMake(0.0, rect.origin.y) animated:YES];

    }


}

// Chamado quando o UIKeyboardWillHideNotification é enviado

- (void)keyboardWillHide:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    myTable.contentInset = contentInsets;
    myTable.scrollIndicatorInsets = contentInsets;
    NSDictionary* info = [aNotification userInfo];
    CGSize kbValue = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGSize kbSize = CGSizeMake(kbValue.height, kbValue.width);
    CGRect bkgndRect = activeField.superview.frame;
    bkgndRect.size.height += kbSize.height;
    [activeField.superview setFrame:bkgndRect];
    [myTable setContentOffset:CGPointMake(0.0, 10.0) animated:YES];
}

-anoop4real

anoop4real
fonte
2

Acabei de resolver esse problema sozinho depois de me referir a uma série de soluções encontradas via Google e Stack Overflow.

Primeiro, verifique se você configurou um IBOutlet do seu UIScrollView. Depois, verifique atentamente o Apple Doc: Keyboard Management . Por fim, se você pode rolar o plano de fundo, mas o teclado ainda cobre os campos de texto, consulte este trecho de código:

// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;

if (aRect.size.height < activeField.frame.origin.y+activeField.frame.size.height) {

    CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y+activeField.frame.size.height-aRect.size.height);

    [scrollView setContentOffset:scrollPoint animated:YES];

A principal diferença entre esta peça e a da Apple está na condição if. Acredito que o cálculo da Apple da distância de rolagem e da condição de se o campo de texto coberto pelo teclado não é preciso, então fiz minha modificação como acima.

Deixe-me saber se funciona

dumbfingers
fonte
2

Um exemplo no Swift, usando o ponto exato do campo de texto em Get indexPath of UITextField no UITableViewCell with Swift :

func textFieldDidBeginEditing(textField: UITextField) {
    let pointInTable = textField.convertPoint(textField.bounds.origin, toView: self.accountsTableView)
    let textFieldIndexPath = self.accountsTableView.indexPathForRowAtPoint(pointInTable)
    accountsTableView.scrollToRowAtIndexPath(textFieldIndexPath!, atScrollPosition: .Top, animated: true)
}
ginchly
fonte
1

Outro método fácil (funciona apenas com uma seção)

//cellForRowAtIndexPath
UItextField *tf;
[cell addSubview:tf];
tf.tag = indexPath.row;
tf.delegate = self;

//textFieldDidBeginEditing:(UITextField *)text
[[self.tableView scrollToRowsAtIndexPath:[NSIndexPath indexPathForRow:text.tag in section:SECTIONINTEGER] animated:YES];
JonasG
fonte
1

Se o seu UITableView for gerenciado por uma subclasse de UITableViewController e não UITableView, e o representante do campo de texto for o UITableViewController, ele deverá gerenciar toda a rolagem automaticamente - todos esses outros comentários são muito difíceis de implementar na prática.

Para um bom exemplo, consulte o projeto de código de exemplo da apple: TaggedLocations.

Você pode ver que ele rola automaticamente, mas não parece haver nenhum código que faça isso. Este projeto também possui células de exibição de tabela personalizadas; portanto, se você criar seu aplicativo com ele como guia, deverá obter o resultado desejado.

calço
fonte
1

Aqui está como eu fiz esse trabalho, que é uma mistura das respostas de Sam Ho e Marcel W, e algumas das minhas próprias correções de erros foram feitas no meu código de baixa qualidade. Eu estava usando um UITableViewController. A tabela agora é redimensionada corretamente quando o teclado é mostrado.

1) viewDidLoadAdicionei:

self.tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

2) Eu tinha esquecido de chamar os superequivalentes em viewWillAppeare awakeFromNib. Eu os adicionei de volta.

Danyal Aytekin
fonte
1

UITableViewControllerfaz a rolagem automaticamente, de fato. A diferença em relação ao uso de a UIViewControlleré que você precisa criar itens do Navbar-Button programaticamente usando o NavigationController, ao usar a TableViewController.

Tom01
fonte