iPhone: За замовчуванням сховати рядок пошуку UITableView


85

Я використав Interface Builder для створення подання таблиці, до якої додав панель пошуку бібліотеки та контролер дисплея пошуку, щоб додати функціональність пошуку. Однак IB налаштував його так, щоб панель була видно у верхній частині екрана при першому відображенні подання.

Я хотів би знати, як за замовчуванням приховати рядок пошуку, але все ще прокручувати його з поданням таблиці (для прикладу див. Програму Apple Mail). Я намагався додзвонитися scrollRectToVisible:animated:в viewDidLoadпрокрутити вид таблиці вниз, але безрезультатно. Який найкращий спосіб приховувати рядок пошуку за замовчуванням?

Відповіді:


116

Спочатку переконайтеся, що додали UISearchBar до tableHeaderView UITableView, щоб він прокручувався разом із вмістом таблиці і не фіксувався у верхній частині подання.

Рядок пошуку не враховується як рядок у поданні таблиці, тому якщо ви прокрутите верхній частині перегляду таблиці до першого рядка, він "приховає" панель пошуку:

[yourTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];

або в Swift:

yourTableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)

Переконайтеся, що не прокручуєте табличне представлення до того, як воно буде містити дані ( scrollToRowAtIndexPathвикличе виняток, якщо заданий indexPath не вказує на дійсний рядок (тобто, якщо табличний порожній)


12
Насправді, у мене виникає проблема, коли подання таблиці не прокручується, якщо для заповнення екрана недостатньо рядків - у таблиці з одним або двома рядками цей код нічого не робить. Це очікувана поведінка? Чи можу я це якось обійти?
Тім

18
Я знайшов спосіб: замість прокрутки до шляху до індексу змініть зміщення змісту подання прокрутки таблиці на CGRect 0,0, 44,0
Тім

107
Тім, це шлях! Я використовував self.tableView.contentOffset = CGPointMake (0, self.searchDisplayController.searchBar.frame.size.height);
Джо Д'Андреа

1
До речі - коли я вводив цей код після виклику [tableView reloadData], що призвело до того, що моя таблиця перейшла з 2 рядків на достатню кількість рядків, щоб дозволити нормальну прокрутку, я виявив, що використання методу contentOffset викликало різке відображення та приховування пошуку поле під час використання методу scrollToRowAtIndexPath не мав однакової стрімкості.
Chris R

1
Це єдиний остаточний спосіб, який працює при БУДЬ-ЯКИХ обставинах. Налаштування contentOffsetз будь-яким значенням порушиться, якщо ви використовуєте складний макет контролера перегляду. Не рекомендую.
eonil

45

Не додайте UISearchBar як підпрогляд UITableView, це не потрібно.

UITableView має властивість tableHeaderView, яке ідеально для цього підходить:

- (void) viewDidLoad {
    [super viewDidLoad]; 
    self.searchBar = [[[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)] autorelease];
    self.searchBar.showsCancelButton = YES;    
    self.searchBar.delegate = self;
    self.tableView.tableHeaderView = self.searchBar;     
}

Якщо ви не хочете бачити його за замовчуванням:

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.tableView setContentOffset:CGPointMake(0, 44)];
}

Я також отримую кнопку скасування, щоб приховати її знову .... (і видалити клавіатуру)

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    [self.tableView setContentOffset:CGPointMake(0, 44) animated:YES];
    [self.searchBar resignFirstResponder];
}

Для мене це було єдиним рішенням, яке створило приховану реалізацію UISearchBar, яка здавалася такою, що відповідає HIG. Інші рішення, де воно "застрягло" у верхній частині таблиці, виглядають неприродно.
kurtzmarc

1
Це рішення працювало найкраще. На мою думку, це має бути відповіддю.
darwindeeds

Це рішення приємне (+1). Однак у деяких випадках tableView не приховує рядок пошуку належним чином (у моєму випадку: коли контролер tableview відсувається від іншого контролера подання). З цим питанням виникає запитання: stackoverflow.com/questions/15222186/... Я думаю, що надана відповідь на це питання не зовсім хороша. @bandejapaisa, ти маєш ідею?
Брайан

3
Це добре працювало для мене, хоча замість жорсткого кодування 44 я пропоную використовувати self.tableView.tableHeaderView.frame.size.height in viewWillAppear, щоб дозволити зміни, які Apple може в майбутньому внести до висоти рядка пошуку.
Джон Стівен,

додавати це viewWillAppear- погана ідея, оскільки воно буде запускатися кожного разу, коли ви повернетесь до свого перегляду. Це означає, що ваш перегляд таблиці буде повільно зменшуватися. Дратівлива помилка!
Sirens

25

ContentOffset було відзначено в коментарях Тімом та Джо Д'Андреа, але це трохи розширено, додавши анімацію, щоб приховати рядок пошуку. Я помітив, що для панелі пошуку просто несподівано просто зникнути.

Невелике оновлення для тих, хто хоче націлити iOS 4.0 і новіших версій, спробуйте наступне:

[UIView animateWithDuration:0.4 animations:^{
    self.tableView.contentOffset = CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height);
 } completion:nil];

Попередня відповідь:

[UIView beginAnimations:@"hidesearchbar" context:nil];
[UIView setAnimationDuration:0.4];
[UIView setAnimationBeginsFromCurrentState:YES];

self.tableView.contentOffset = CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height);

[UIView commitAnimations];


9
Я виявив, що анімація є ключем до цього рішення. Спроба встановити зміщення вмісту без анімації, здається, нічого не робить. Замість того, щоб використовувати методи анімації (або блоки) у цьому зразку, я виявляю, що просто виклик [self.tableView setContentOffset: CGPointMake (0,44) animated: TRUE] працює досить добре.
quickthyme

Здається, це взагалі не працює без анімації. @quickthyme зробив те саме спостереження.
Тіло

5
Я мав успіх у цьому, не потребуючи анімації, якщо ви застосуєте її viewDidAppear:замість viewDidLoad.
wbyoung

8

Існує багато відповідей на це рішення, проте жодна з них не спрацювала дуже добре для мене.

Це працює чудово. Немає анімації, і робиться на-viewDidLoad

 - (void)viewDidLoad {
     [super viewDidLoad];
     self.tableView.contentOffset = CGPointMake(0,  self.searchBar.frame.size.height - self.tableView.contentOffset.y);
 }

Примітка:

Код припускає, що у вас є те, searchBar @propertyщо пов’язане з рядком пошуку. Якщо ні, ви можете використовувати self.searchDisplayController.searchBarзамість цього


це єдиний, хто працював у мене! , але це лише робить un viewdidload ... так що вперше його приховано, але потім більше не приховується під час запуску програми de. І я спробував помістити це в viewwillappear і viewdidappear, але працює неправильно. Дякую!
fguespe

@fguespe це дивно .. ViewDidLoad повинен викликатися щоразу, коли подання створюється за допомогою екземпляра. Мені ніколи не траплялося, що UISearchBar було видно після повернення з іншого виду.
Матей

я використовую додаток із вкладками. Може це?
fguespe

@fguespe Я бачу, що це може бути проблемою хм. На жаль, я не впевнений, як би ви вирішили, якщо -viewWillAppearце не спрацює .. Чи викликається воно, коли контролер вкладок перемикається на вигляд?
Матей

викликаються viewwillappear і viewdidappear, але я спробував і те, і інше, і вигляд схожий на неправильний зміщений. Це не повертає очікуваний результат. Я маю на увазі, щось робить, але робить це погано.
fguespe

7

Для Swift 3+

Я зробив це:

Заявіть про це var:

    var searchController = UISearchController()

І в viewDidLoad()методі

    searchController = UISearchController(searchResultsController: nil)
    searchController.searchResultsUpdater = self
    searchController.hidesNavigationBarDuringPresentation = false
    searchController.dimsBackgroundDuringPresentation = true
    searchController.searchBar.placeholder = NSLocalizedString("Search", comment: "")
    definesPresentationContext = true
    tableView.tableHeaderView = searchController.searchBar

    tableView.contentOffset = CGPoint(x: 0, y: searchController.searchBar.frame.size.height)

5

Моєю метою було приховати рядок пошуку, доданий до tableHeaderView простого стилю TableView, в раскадровці, коли контролер подання завантажений, і показати панель, коли користувач прокручує вниз у верхній частині UITableView. Отже, я використовую Swift і додав розширення:

extension UITableView {
    func hideSearchBar() {
        if let bar = self.tableHeaderView as? UISearchBar {
            let height = CGRectGetHeight(bar.frame)
            let offset = self.contentOffset.y
            if offset < height {
                self.contentOffset = CGPointMake(0, height)
            }
        }
    }
}

in viewDidLoad () просто зателефонуйте:

self.tableView.hideSearchBar()

5

Всі існуючі рішення не працюють для мене на iOS 8, коли для заповнення tableView недостатньо рядків, оскільки iOS автоматично регулює вставку в цій ситуації. (Існуючі відповіді хороші, коли достатньо рядків)

Потративши близько 6 годин на це питання, я нарешті отримав це рішення.

Коротше кажучи, вам потрібно вставити порожні комірки в tableView, якщо недостатньо комірок, тому розмір вмісту tableView досить великий, щоб iOS не коригувала вставку для вас.

Ось як я це зробив у Свіфті:

1.) оголосити змінну minimumCellNumяк властивість класу

var minimumCellNum: Int?

2.) обчислити minimumCellNum і набір tableView.contentOffsetвviewWillAppear

let screenHeight = Int(UIScreen.mainScreen().bounds.height)

// 101 = Height of Status Bar(20) + Height of Navigation Bar(44) + Height of Tab Bar(49)
// you may need to subtract the height of other custom views from the screenHeight. For example, the height of your section headers.
self.minimumCellNum = (screenHeight - 103 - heightOfOtherCustomView) / heightOfYourCell
self.tableView.contentOffset = CGPointMake(0, 44)

3.) в tableView(tableView: UITableView, numberOfRowsInSection section: Int))

let numOfYourRows = YOUR LOGIC
if numOfYourRows > minimumCellNum {
    return numOfYourRows
} else {
    return minimumCellNum!
}

4.) Зареєструйте порожню комірку, selectionатрибут якоїNone , на розкадруванні та вtableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)

if indexPath.row < numOfYourRows {
    return YOUR CUSTOM CELL
} else {
    let cell = tableView.dequeueReusableCellWithIdentifier("EmptyCell", forIndexPath: indexPath) as! UITableViewCell
    return cell
}

5.) в tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)

if tableView == self.tableView {
    if numOfYourRows < (indexPath.row + 1) {
        return
    }
    YOUR LOGIC OF SELECTING A CELL
}

Це не ідеальне рішення, але це єдине обхідне рішення, яке справді працює для мене на iOS 8. Я хотів би знати, чи є більш акуратне рішення.


Мені довелося це зробити для iOS 11. Налаштування contentOffset працювало лише для iOS 9/10 для 0 або більше комірок, а також працювало в iOS 11 для 8+ комірок. Все, що менше вимагало цього рішення. Чи є причина чому?
Тоні

Думаю, перший абзац цього допису пояснює, чому.
Брайан

Я зрозумів перший абзац, але все одно було дивно, що він лише «зламався» на iOS 11 для мене. Я би очікував подібної поведінки для iOS 9, 10 і 11, коли кількість комірок однакова (менше, ніж достатньо). Ще раз спасибі і вибачте за те, що ви підняли стару публікацію!
Тоні,

4

Жодне з перерахованого не працювало для мене, тож про всяк випадок, якщо хтось матиме те саме питання.

Я searchBarвстановив як tableHeaderViewу згрупованій таблиці на iOS7. В viewDidLoadя повинен tableView.contentOffset = CGPointMake(0, 44);триматиsearchBar спочатку "прихованим". Коли користувач прокручує вниз searchBar, dsiplayed

Мета: приховати панель пошуку при натисканні кнопки скасування

В searchBarCancelButtonClicked(searchBar: UISearchBar!)

Це не спрацювало: [yourTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; tableView занадто сильно прокручував угору, в результаті рядок 0 заходив за панель інструментів.

Це не спрацювало: tableView.ContentOffset = CGPointMake(0, 44) - нічого не відбувається

Це не спрацювало: tableView.ContentOffset = CGPointMake(0, searchBar.frame.size.height) - нічого не відбувається

Це не спрацювало: tableView.setContentOffset(CGPointMake(0, 44), animated: true); знову tableView прокручував занадто сильно, в результаті рядок 0 заходив за панель інструментів.

Це спрацювало частково: tableView.setContentOffset(CGPointMake(0, 0) алеtitleForHeaderInSection йшло за панеллю інструментів

ЦЕ РОБОТАЛ: tableView.setContentOffset(CGPointMake(0, -20)

Здається не логічним, але лише -20 перемістило його в правильне положення


Ви дійсно не повинні жорстко кодувати свої точки, чому б вам не використовувати CGRectGetHeight(searchBar.bounds)?
Зорайр

Я думаю, ви повинні це робити у viewDidLoad () / init (), де для вас не встановлено жодного кадру, щоб отримати висоту рядка пошуку. Можливо, вам слід спробувати viewDidLayoutSubviews ()
Kaushil Ruparelia

Якщо tableView не завершив завантаження, жодне з ContentOffsetрішень не буде працювати
Maor

4

Зміщення вмісту - це шлях, але він не працює у 100% випадків.

Це не працює, якщо встановлено estimatedRowHeightфіксовану кількість. Я ще не знаю, як це працювати. Я створив проект, що відображає проблему (проблеми), і створив радар з Apple.

Ознайомтеся з проектом, якщо вам потрібен швидкий приклад того, як це все налаштувати.


1
Дякую! Це те, що я шукав. Вам потрібно піднятися вгору.
user2387149

3

Зауважте, що прийнята відповідь зазнає аварійного завершення, якщо в поданні таблиці немає даних. І додавання його до viewDidLoad може не працювати за певних обставин.

[yourTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; // crashes if no rows/data

Тому замість цього додайте цей рядок коду:

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

    [yourTableView setContentOffset:CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height)];
}

2

Чесно кажучи, жодна з відповідей насправді не вирішила проблему (для мене). Якщо у поданні таблиці немає рядків АБО висота рядків відрізняється, цих відповідей недостатньо.

Єдине, що працює:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.tableView.contentOffset = (CGPoint){.y =  self.tableView.contentOffset.y + self.searchController.searchBar.frame.size.height};
}

Хороше рішення для офсетної проблеми. Мені працює під час використання UINavigationController.
Андреас Крафт,

1
Рішення не працює, коли кількість рядків не охоплює весь екран - чи знаєте ви, як це зробити?
Zorayr

Не впевнений, що я це повністю розумію. Це працює для мене, коли в таблиці є лише кілька рядків. Однак я використовую наступне: self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];.
p0lAris

2

scrollToRowAtIndexPathМетод викликає збій , якщо є нульові рядки в таблиці.

UITableView - це лише вид прокрутки, тому ви можете просто змінити зміщення вмісту.

Це перевіряє, чи є у вас щось як tableHeaderView, і припускаючи, що ви робите прокрутки, щоб приховати це

    if let searchHeight = tableView.tableHeaderView?.frame.height {
        tableView.setContentOffset(CGPoint.init(x: 0, y:searchHeight ), animated: false)
    }

1

Я виявив, що програма "Примітки" не може шукати елементи, коли tableView порожній. Тому я думаю, що це просто використання-(void)addSubview:(UIView *)view спочатку додавання порожньої таблиці. Коли ви додаєте елемент до його масиву джерела даних, він запускає інший код коду для завантаження tableView.

Отже, моє рішення:

if([self.arrayList count] > 0)
{
     [self.tableView reloadData];     //use jdandrea's code to scroll view when cell==nil in cellForRowAtIndexPath
     self.tableView.scrollEnabled = YES;
}
else
{
     tableBlank = [[UITableView alloc]initWithFrame:CGRectMake(0,0,320,416) style:UITableViewStylePlain] autorelease];
     tableBlank.delegate = self;
     tableBlank.dataSource = self;
     [self.view addSubview:tableBlank];
}

Сподіваюся, це допоможе :-)


1

Змініть межі tableView, це можна зробити всередині, viewDidLoadплюс вам не доведеться турбуватися про те, порожня таблиця чи ні (щоб уникнути винятку).

CGRect newBounds = self.tableView.bounds;
newBounds.origin.y = newBounds.origin.y + self.searchBar.bounds.size.height;
self.tableView.bounds = newBounds;

Після того, як користувач прокрутив таблицю, рядок пошуку з’явиться та поведеться нормально


1

Інші відповіді на це питання або взагалі не працювали для мене, мали неабияку поведінку, коли tableView мав 0 рядків, або псували позицію прокрутки при переході вперед-назад між батьківськими та вкладеними рядками. Це спрацювало для мене (метою було приховати UISearchBar під час завантаження):

public override func viewWillAppear(animated: Bool) {        
    if self.tableView.contentOffset.y == 0 {
        self.tableView.contentOffset = CGPoint(x: 0.0, y: self.searchBar.frame.size.height) 
    }
}

Пояснення
Кожного разу, коли tableViewз’явиться цей код, цей код перевіряє, чи видно панель UISearchBar (мається на увазі yпозиція contentOffset, яка також називається позицією прокрутки 0). Якщо це так, він просто прокручує вниз по висоті панелі пошуку. Якщо панель пошуку не видно (подумайте про більший список елементів у tableView), тоді вона не буде намагатися прокручувати tableView, оскільки це призведе до псування позиціонування, коли ви повернетесь із вкладеної позиції.

Додаткова примітка.
Я рекомендую помістити цей код в окремий метод, щоб забезпечити єдину відповідальність та забезпечити можливість повторного використання його в будь-який час у коді.


1

Якщо у вашій таблиці завжди буде хоча б один рядок, просто прокрутіть до першого рядка таблиці, і рядок пошуку буде автоматично прихований.

let firstIndexPath = NSIndexPath(forRow: 0, inSection: 0)

self.tableView.selectRowAtIndexPath (firstIndexPath, animated: false, scrollPosition: .Top)

Якщо ви розмістите наведений вище код на viewDidLoad , він видасть помилку, оскільки tableView ще не завантажився, тому вам доведеться помістити його в viewDidAppear, оскільки на цей момент tableView вже завантажився.

Якщо ви помістите це на viewDidAppear , кожен раз, коли ви відкриваєте таблицю View, вона прокручуватиметься до початку.

Можливо, ви не хочете такої поведінки, якщо tableView залишається відкритим, наприклад, коли це контролер перегляду UITabBar, або коли ви робите програму, а потім повертаєтесь. Якщо ви просто хочете, щоб воно прокручувалося до початку при початковому завантаженні, ви можете створити змінну, щоб перевірити, чи це початкове завантаження, щоб вона прокручувалась до верху лише один раз.

Спершу визначте змінну з назвою isInitialLoad у класі контролера подання та встановіть її рівною "true":

var isInitialLoad = true

Потім перевірте, чи isInitialLoad має значення true у viewDidAppear, і якщо воно відповідає істині, прокрутіть до початку та встановіть для змінної isInitialLoad значення false:

if isInitialLoad {
            let firstIndexPath = NSIndexPath(forRow: 0, inSection: 0)
            self.tableView.selectRowAtIndexPath(firstIndexPath, animated: false, scrollPosition: .Top)

            isInitialLoad = false
        }

1

Нижче код працює для мене

self.tableView.contentOffset = CGPointMake(0.0, 44.0);

1

Я спробував встановити зміщення вмісту та scrollToIndexpath, але нічого не працювало задовільно. Я видалив налаштування tableHeaderView як searchBar з viewDidLoad і встановив його в делегаторі scrollview, як показано нижче

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    if scrollView.contentOffset.y < 0 && tableView.tableHeaderView == nil {
        tableView.tableHeaderView = searchController.searchBar
        tableView.setContentOffset(CGPointMake(0, scrollView.contentOffset.y + searchController.searchBar.frame.size.height), animated: false)
    }
}

Це спрацювало як шарм. Налаштування зміщення вмісту призначене для плавної прокрутки


0

Не забудьте взяти до уваги рядок стану та навігаційну панель . Мій контролер подання таблиці вбудований в навігаційний контролер, в viewDidAppear:

tableView.contentOffset = CGPointMake(0, -20) // -20 = 44 (search bar height) - 20 (status bar height) - 44 (navigation bar height)

працював у мене.


0

Для Свіфта:

Просто зробіть зміст табличного перегляду y зміщенням, яке повинно бути як висота панелі пошуку.

self.tableView.contentOffset = CGPointMake(0, self.searchController.searchBar.frame.size.height)

0

Стрімкий 3

працює і з порожньою таблицею!

У моєму середовищі я завантажую власні комірки файлом xib, і рядокHeight встановлюється автоматично.

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        self.tableView.contentOffset = CGPoint(x: 0, y: self.tableView.contentOffset.y + self.searchController.searchBar.bounds.height)
}

0

У мене була подібна проблема. І єдиним способом, який я знайшов для вирішення цієї проблеми, було використання спостерігача ключових значень у властивості UITableView contentOffset.

Після деякої налагодження я зрозумів, що проблема виникає, лише якщо розмір вмісту подання таблиці менший, ніж табличний. Я запускаю KVO, коли відображається viewWillAppear. І зупинка відправки KVO через 0,3 секунди після появи перегляду. Щоразу, коли contentOffset ставав 0.0, KVO реагував і встановлював contentOffset на висоті рядка пошуку. Я зупиняю KVO асинхронно через 0,3 секунди, оскільки макет подання скидає contentOffset навіть після появи перегляду.

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  [self.tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}

-(void)viewDidAppear:(BOOL)animated{
   __weak typeof (self) weakSelf = self;
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [weakSelf.tableView removeObserver:self forKeyPath:@"contentOffset"];});
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    if ([keyPath isEqualToString:@"contentOffset"] && self.tableView.contentOffset.y == 0.0) {
       self.tableView.contentOffset = CGPointMake(0, CGRectGetHeight(self.tableView.tableHeaderView.frame));
    }
}

-(void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentOffset"];
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.