Прокрутка UITableView при выборе текстового поля
После долгих проб и ошибок я сдаюсь и задаю вопрос. Я видел много людей с похожими проблемами, но не мог найти правильных ответов на все вопросы.
У меня UITableView
есть файл, состоящий из пользовательских ячеек. Ячейки состоят из 5 текстовых полей, расположенных рядом друг с другом (что-то вроде сетки).
Когда я пытаюсь прокрутить и редактировать ячейки внизу UITableView
, мне не удается правильно расположить ячейки над клавиатурой.
Я видел много ответов, в которых говорилось об изменении размеров представления и т. Д., Но пока ни один из них не работал хорошо.
Может ли кто-нибудь пояснить «правильный» способ сделать это на конкретном примере кода?
Ответов (25)25
Я только что снова просмотрел справочник по библиотеке iOS 5.0 и нашел этот раздел под названием «Перемещение содержимого, расположенного под клавиатурой»: TextAndWebiPhoneOS KeyboardManagement
Возможно, это новинка со времен iOS 5? Я еще не читал об этом, так как сейчас занимаюсь чем-то другим, но, возможно, другие знают больше и могут просветить меня и других здесь.
Заменяет ли документ Apple то, что здесь обсуждалось, или информация здесь по-прежнему полезна для пользователей iOS 5 SDK?
Более простое решение. Он вставляется в методы делегата UITextField, поэтому не требует беспорядка с уведомлениями UIKeyboard.
Примечания по реализации:
kSettingsRowHeight - высота UITableViewCell.
offsetTarget и offsetThreshold отключены от kSettingsRowHeight. Если вы используете другую высоту строки, установите эти значения в свойство точки y. [alt: вычислить смещение строки другим способом.]
- (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;
}
Возможно, я пропустил это, так как я не читал здесь весь пост, но то, что я придумал, кажется обманчиво простым. Я не проверял это во всех ситуациях, но, похоже, все должно работать нормально.
просто отрегулируйте contentInset tableview по высоте клавиатуры, а затем прокрутите ячейку вниз:
- (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];
}
и конечно
- (void)keyboardWasHidden:(NSNotification *)aNotification
{
[UIView animateWithDuration:.3 animations:^(void)
{
self.myTableView.contentInset = UIEdgeInsetsZero;
self.myTableView.scrollIndicatorInsets = UIEdgeInsetsZero;
}];
}
это слишком просто? я что-то упускаю? пока у меня все работает нормально, но, как я уже сказал, я не отжимал его ...
Я думаю, что нет "правильного" способа сделать это. Вы должны выбрать наиболее подходящее решение для вашего случая использования. В моем приложении для iPad у меня есть файл, UIViewController
который представлен как модальный UIModalPresentationFormSheet
и состоит из файла UITableView
. Эта таблица содержит по два UITextFields
на ячейку. Просто позвонив scrollToRowAtIndexPath:atScrollPosition:animated:
в textFieldDidBeginEditing:
метод не работает для меня. Поэтому я создал tableFooterView
:
- (void)viewDidLoad
{
[super viewDidLoad];
m_footerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, m_tableView.frame.size.width, 300.0f)];
[m_footerView setBackgroundColor:[UIColor clearColor]];
[m_tableView setTableFooterView:m_footerView];
[m_footerView release];
}
Идея заключается в том , что клавиатура скрывает tableFooterView
и не UITextFields
. Значит, tableFooterView
должно быть достаточно высоко. После этого вы можете использовать scrollToRowAtIndexPath:atScrollPosition:animated:
в textFieldDidBeginEditing:
методе.
Я думаю, что также можно отображать и скрывать tableFooterView
динамически, добавляя наблюдателей для уведомлений с клавиатуры, но я еще не пробовал:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification
{
[m_tableView setTableFooterView:m_footerView];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
[m_tableView setTableFooterView:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
Мой подход:
Сначала я создаю подкласс UITextField и добавляю свойство indexPath. В методе cellFor ... я передаю свойство indexPath.
Затем я добавляю следующий код:
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);}];
в textFieldShould / WillBegin ... и т. д.
Когда клавиатура исчезнет, вам нужно отменить ее с помощью:
[UIView animateWithDuration:0.3 animations:^(void){self.tableView.contentOffset = CGPointMake(0, 0);}];
Если пользуетесь Three20
, то пользуйтесь autoresizesForKeyboard
свойством. Просто установите в вашей точке зрения контроллера -initWithNibName:bundle
метода
self.autoresizesForKeyboard = YES
Это касается:
- Прослушивание уведомлений с клавиатуры и настройка рамки представления таблицы
- Прокрутка до первого респондента
Готово и сделано.
Я думаю, что лучший способ - через UITableViewController.
Если вам нужен UITableView в UIViewController , просто создайте ContentView со встроенным UITableViewController и поместите следующие строки в viedDidLoad UIViewController:
self.tableView = ((UITableViewController*)self.childViewControllers[0]).tableView;
self.tableView.delegate = self;
self.tableView.dataSource = self;
Легкий ;)
Я надеюсь, что вы, ребята, уже нашли решение, прочитав все это. Но я нашел свое решение следующим образом. Я ожидаю, что у вас уже есть сотовый с UITextField
. Поэтому при подготовке просто сохраните индекс строки в теге текстового поля.
cell.textField.tag = IndexPath.row;
Создайте activeTextField
экземпляр UITextField
с глобальной областью действия, как показано ниже:
@interface EditViewController (){
UITextField *activeTextField;
}
Итак, теперь вы просто копируете и вставляете мой код в конец. А также не забудьте добавить UITextFieldDelegate
#pragma mark - TextField Delegation
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
activeTextField = textField;
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField{
activeTextField = nil;
}
Регистрирует клавиатуру 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];
}
Обрабатывает клавиатуру Notifications
:
Вызывается при UIKeyboardDidShowNotification
отправке.
- (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];
}
Вызывается при UIKeyboardWillHideNotification
отправке
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
[self.tableView setContentInset:contentInsets];
[self.tableView setScrollIndicatorInsets:contentInsets];
}
Теперь осталось одно: вызовите registerForKeyboardNotifications
метод в ViewDidLoad
method следующим образом:
- (void)viewDidLoad {
[super viewDidLoad];
// Registering keyboard notification
[self registerForKeyboardNotifications];
// Your codes here...
}
Вы закончили, надеюсь, ваша textFields
воля больше не скрывается за клавиатурой.
Самое простое решение для 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
}
Для Swift 4.2 или выше
override func viewDidLoad() {
super.viewDidLoad()
searchBar?.becomeFirstResponder()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardDidHideNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
let keyboardHeight = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue.size.height
accountSettingsTableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
}
}
@objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.2, animations: { self.accountSettingsTableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) })}
}
Посмотрите мою версию :)
- (void)keyboardWasShown:(NSNotification *)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect bkgndRect = cellSelected.superview.frame;
bkgndRect.size.height += kbSize.height;
[cellSelected.superview setFrame:bkgndRect];
[tableView setContentOffset:CGPointMake(0.0, cellSelected.frame.origin.y-kbSize.height) animated:YES];
}
- (void)keyboardWasHidden:(NSNotification *)aNotification
{
[tableView setContentOffset:CGPointMake(0.0, 0.0) animated:YES];
}
Вот мое решение, вдохновленное экраном «Редактировать событие» из приложения «Календарь» iOS7.
Одним из ключевых моментов этого решения является то, что клавиатура закрывается, когда пользователь прокручивает таблицу.
Реализация:
1) Добавить свойство, в котором будет храниться выбранное текстовое поле:
@property (strong) UITextField *currentTextField;
и переменная BOOL, которую мы будем использовать, чтобы проверить, нужно ли нам скрывать клавиатуру, когда пользователь прокручивает таблицу.
BOOL hideKeyboardOnScroll;
2) Обработка обратных вызовов делегата UITextField:
#pragma mark - UITextFieldDelegate
- (void) textFieldDidBeginEditing: (UITextField *) textField {
self.currentTextField = textField;
}
- (void) textFieldDidEndEditing: (UITextField *) textField {
self.currentTextField = nil;
}
- (BOOL) textFieldShouldReturn: (UITextField *) textField {
[textField resignFirstResponder];
CGPoint newContentOffset = CGPointZero;
if (tableView.contentSize.height > tableView.frame.size.height) {
newContentOffset.y = MIN(tableView.contentOffset.y, tableView.contentSize.height - tableView.frame.size.height);
}
[tableView setContentOffset: newContentOffset animated: YES];
return YES;
}
3) Обработайте метод UIScrollViewDelegate, чтобы проверить это представление прокрутки пользователя.
#pragma mark - UIScrollViewDelegate
- (void) scrollViewDidScroll: (UIScrollView *) scrollView {
if (hideKeyboardOnScroll == YES) {
[self.currentTextField resignFirstResponder];
}
}
4) Подпишитесь на уведомления клавиатуры в методе viewcontroller [viewWillAppear] и откажитесь от подписки в методе [viewWillDisappear].
- (void) viewWillAppear: (BOOL) animated {
[super viewWillAppear: animated];
[ [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];
[ [NSNotificationCenter defaultCenter] removeObserver: self name: UIKeyboardDidShowNotification object: nil];
[ [NSNotificationCenter defaultCenter] removeObserver: self name: UIKeyboardWillHideNotification object: nil];
}
5) Обработка уведомлений с клавиатуры:
- (void) keyboardWillShow: (NSNotification *) notification {
CGRect keyboardFrame = [ [ [notification userInfo] objectForKey: UIKeyboardFrameBeginUserInfoKey] CGRectValue];
// Find cell with textfield.
CGRect textFieldFrame = [tableView convertRect: self.currentTextField.frame fromView: self.currentTextField];
NSIndexPath *indexPath = [tableView indexPathForRowAtPoint: textFieldFrame.origin];
UITableViewCell *cell = [tableView cellForRowAtIndexPath: indexPath];
//
// Shrink tableView size.
CGRect tableViewFrame = tableView.frame;
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width,
self.view.frame.size.height - tableView.frame.origin.y - keyboardFrame.size.height);
//
// Check if cell is visible in shrinked table size.
BOOL cellIsFullyVisible = YES;
if ( cell.frame.origin.y < tableView.contentOffset.y ||
(cell.frame.origin.y + cell.frame.size.height) > (tableView.contentOffset.y + tableView.frame.size.height) ) {
cellIsFullyVisible = NO;
}
//
// If cell is not fully visible when scroll table to show cell;
if (cellIsFullyVisible == NO) {
CGPoint contentOffset = CGPointMake(tableView.contentOffset.x, CGRectGetMaxY(cell.frame) - tableView.frame.size.height);
if (cell.frame.origin.y < tableView.contentOffset.y) {
contentOffset.y = cell.frame.origin.y;
}
contentOffset.y = MAX(0, contentOffset.y);
// For some reason [setContentOffset] is called without delay then
// this code may not work for some cells. That why we call it with brief delay.
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView animateWithDuration: 0.5 animations:^{
[tableView setContentOffset: contentOffset animated: NO];
} completion: ^(BOOL finished) {
hideKeyboardOnScroll = YES;
}];
});
} else {
hideKeyboardOnScroll = YES;
}
//
// Finally restore original table frame.
tableView.frame = tableViewFrame;
//
}
- (void) keyboardWillHide: (NSNotification *) notification {
[super keyboardWillHide: notification];
hideKeyboardOnScroll = NO;
}
Решение для Swift 3-4 с анимацией и сменой рамки клавиатуры:
Сначала создайте Bool:
// MARK: - Private Properties
private var isKeyboardShowing = false
Во-вторых, добавьте наблюдателей в уведомления системной клавиатуры:
// MARK: - Overriding ViewController Life Cycle Methods
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: .UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: .UIKeyboardWillChangeFrame, object: nil)
}
В-третьих, подготовьте функцию анимации:
func adjustTableViewInsets(keyboardHeight: CGFloat, duration: NSNumber, curve: NSNumber){
var extraHeight: CGFloat = 0
if keyboardHeight > 0 {
extraHeight = 20
isKeyboardShowing = true
} else {
isKeyboardShowing = false
}
let contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight + extraHeight, right: 0)
func animateFunc() {
//refresh constraints
//self.view.layoutSubviews()
tableView.contentInset = contentInset
}
UIView.animate(withDuration: TimeInterval(duration), delay: 0, options: [UIViewAnimationOptions(rawValue: UInt(curve))], animations: animateFunc, completion: nil)
}
Затем добавьте методы цели / действия (вызываемые наблюдателями):
// MARK: - Target/Selector Actions
func keyboardWillShow(notification: NSNotification) {
if !isKeyboardShowing {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardSize.height
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
let curve = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber
adjustTableViewInsets(keyboardHeight: keyboardHeight, duration: duration, curve: curve)
}
}
}
func keyboardWillHide(notification: NSNotification) {
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
let curve = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber
adjustTableViewInsets(keyboardHeight: 0, duration: duration, curve: curve)
}
func keyboardWillChangeFrame(notification: NSNotification) {
if isKeyboardShowing {
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
let curve = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber
if let newKeyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = newKeyboardSize.height
adjustTableViewInsets(keyboardHeight: keyboardHeight, duration: duration, curve: curve)
}
}
}
Наконец, не забудьте удалить наблюдателей в deinit или viewWillDisappear:
deinit {
NotificationCenter.default.removeObserver(self)
}
Думаю, я придумал решение, соответствующее поведению приложений Apple.
Во-первых, в вашем viewWillAppear: подпишитесь на уведомления клавиатуры, чтобы вы знали, когда клавиатура будет отображаться и скрываться, и система сообщит вам размер клавиатуры, но не забудьте отменить регистрацию в вашем viewWillDisappear :.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
Реализуйте методы, аналогичные приведенным ниже, чтобы настроить размер tableView в соответствии с видимой областью после отображения клавиатуры. Здесь я отслеживаю состояние клавиатуры отдельно, поэтому я могу сам выбрать, когда вернуть tableView на полную высоту, поскольку вы получаете эти уведомления при каждом изменении поля. Не забудьте реализовать keyboardWillHide: и выбрать подходящее место, чтобы исправить размер 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];
}
}
Теперь вот бит прокрутки, мы сначала прорабатываем несколько размеров, затем мы видим, где мы находимся в видимой области, и устанавливаем прямоугольник, который мы хотим прокрутить, чтобы быть либо половинным видом выше или ниже середины текстового поля на основе от того, где он находится в поле зрения. В этом случае у нас есть массив UITextFields и перечисление, которое отслеживает их, поэтому умножение rowHeight на номер строки дает нам фактическое смещение фрейма внутри этого внешнего представления.
- (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];
}
Кажется, это неплохо работает.
Функция, выполняющая прокрутку, могла бы быть намного проще:
- (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];
}
Вот и все. Никаких расчетов.
Если вы используете uitableview для размещения ваших текстовых полей ( от Джеффа Ламарша ), вы можете просто прокручивать табличное представление, используя такой метод делегата.
(Примечание: мои текстовые поля хранятся в массиве с тем же индексом, что и строка в табличном представлении)
- (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];
}
Уведомления с клавиатуры работают, но пример кода Apple для этого предполагает, что представление прокрутки является корневым представлением окна. Обычно это не так. Вы должны компенсировать панели вкладок и т. Д., Чтобы получить правильное смещение.
Это проще, чем кажется. Вот код, который я использую в UITableViewController. Он имеет две переменные экземпляра: hiddenRect и 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;
}
Я делаю что-то очень похожее, это общее, нет необходимости вычислять что-то конкретное для вашего кода. Просто проверьте примечания к коду:
В 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
В 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
Версия 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()
}
}
}
Если вы можете использовать UITableViewController
, вы получите функциональность бесплатно. Однако иногда это не вариант, особенно если вам нужно несколько представлений, а не только UITableView
.
Некоторые из представленных здесь решений не работают на iOS ≥4, некоторые не работают на iPad или в ландшафтном режиме, некоторые не работают с клавиатурами Bluetooth (где нам не нужна прокрутка), некоторые нет. работать при переключении между несколькими текстовыми полями. Поэтому, если вы выберете какое-либо решение, обязательно протестируйте эти случаи. Это решение , которое мы используем используется в 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];
}
}
Вот полный код класса в InAppSettingsKit. Чтобы проверить это, используйте дочернюю панель «Полный список», где вы можете протестировать сценарии, упомянутые выше.
У меня была такая же проблема, но я заметил, что она появляется только в одном представлении. Я начал искать отличия в контроллерах.
Я обнаружил, что поведение прокрутки задано в - (void)viewWillAppear:(BOOL)animated
супер-экземпляре.
Так что обязательно реализуйте вот так:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// your code
}
И неважно, используете ли вы UIViewController
или UITableViewController
; проверил это, поместив a UITableView
как часть self.view в UIViewController
. Было такое же поведение. Вид не позволял прокручивать, если звонок [super viewWillAppear:animated];
отсутствовал.
Комбинируя и заполняя пробелы из нескольких ответов (в частности, Ортвина Генца, пользователь 98013) и другого сообщения, это будет работать из коробки для SDK 4.3 на iPad в портретном или ландшафтном режиме:
@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
Полное решение Swift 4.2
Я создал GIST с набором протоколов, который упрощает работу с добавлением дополнительного места, когда клавиатура отображается, скрыта или изменена.
Особенности :
- Правильно работает с изменением рамки клавиатуры (например, изменение высоты клавиатуры как emojii → нормальная клавиатура).
- Поддержка TabBar и ToolBar для примера UITableView (в других примерах вы получаете неправильные вставки).
- Продолжительность динамической анимации (жестко не запрограммирована).
- Протоколно-ориентированный подход, который можно легко изменить для ваших целей.
использование
Базовый пример использования в контроллере представления, который содержит некоторое представление прокрутки (конечно, также поддерживается табличное представление).
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 }
}
Ядро: наблюдатель смены кадра
Протокол KeyboardChangeFrameObserver
будет запускать событие каждый раз при изменении кадра клавиатуры (включая отображение, скрытие, изменение кадра).
- Позвоните
addKeyboardFrameChangesObserver()
по телефонуviewWillAppear()
или аналогичным способом. - Позвоните
removeKeyboardFrameChangesObserver()
по телефонуviewWillDisappear()
или аналогичным способом.
Реализация: просмотр с прокруткой
ModifableInsetsOnKeyboardFrameChanges
Протокол добавляет UIScrollView
поддержку основного протокола. Он изменяет вставки прокрутки при смене рамки клавиатуры.
Ваш класс должен установить режим прокрутки, вставки будут увеличиваться / уменьшаться при изменении рамки клавиатуры.
var scrollViewToModify: UIScrollView { get }
Самое простое решение для Swift 3 , основанное на решении 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)
})
}