Можно ли отключить плавающие заголовки в UITableView с UITableViewStylePlain?
Я использую UITableView
для разметки контента "страницы". Я использую заголовки табличного представления для размещения определенных изображений и т. Д., И я бы предпочел, чтобы они не плавали, а оставались статичными, как они делают, когда стиль установлен на UITableViewStyleGrouped
,
Другое, то с помощью UITableViewStyleGrouped
, Есть ли способ сделать это? Я хотел бы избегать использования группировки, так как это добавляет запас по всем моим ячейкам и требует отключения фонового представления для каждой из ячеек. Я хотел бы получить полный контроль над моим макетом. В идеале они должны быть "UITableViewStyleBareBones", но я не видел эту опцию в документах...
Большое спасибо,
32 ответа
Вы должны иметь возможность подделать это, используя пользовательскую ячейку для создания строк заголовка. Затем они будут прокручиваться, как и любая другая ячейка в табличном представлении.
Вам просто нужно добавить немного логики в ваш cellForRowAtIndexPath
вернуть правильный тип ячейки, когда это строка заголовка.
Вам, вероятно, придется самостоятельно управлять своими разделами, то есть иметь все в одном разделе и подделывать заголовки. (Вы также можете попытаться вернуть скрытое представление для представления заголовка, но я не знаю, будет ли это работать)
Вероятно, более простой способ добиться этого:
Objective-C:
CGFloat dummyViewHeight = 40;
UIView *dummyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, dummyViewHeight)];
self.tableView.tableHeaderView = dummyView;
self.tableView.contentInset = UIEdgeInsetsMake(-dummyViewHeight, 0, 0, 0);
Swift:
let dummyViewHeight = CGFloat(40)
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: dummyViewHeight))
self.tableView.contentInset = UIEdgeInsetsMake(-dummyViewHeight, 0, 0, 0)
Заголовки разделов теперь будут прокручиваться, как и любая обычная ячейка.
Изменить стиль таблицы с простого на сгруппированный (для тех, кто когда-либо попал сюда из-за неправильного стиля таблицы)
let tableView = UITableView(frame: .zero, style: .grouped)
ВНИМАНИЕ: это решение реализует зарезервированный метод API. Это может помешать одобрению приложения Apple для распространения в AppStore.
Я описал частные методы, которые превращают заголовки разделов в мой блог.
В основном вам просто нужно создать подкласс UITableView
и вернуться NO
в двух своих методах:
- (BOOL)allowsHeaderViewsToFloat;
- (BOOL)allowsFooterViewsToFloat;
В вашем Интерфейсном Разработчике нажмите на вашу проблему.
Затем перейдите к Инспектору Атрибутов и измените Стиль Обычный на Сгруппированный;) Легко
Хорошо, я знаю, что уже поздно, но я должен был это сделать. Я потратил 10 часов на поиск рабочего решения, но не нашел полного ответа. Нашел некоторые подсказки, но их трудно понять начинающим. Поэтому я должен был положить свои 2 цента и завершить ответ.
Как было предложено в нескольких ответах, единственное рабочее решение, которое мне удалось реализовать, - это вставить нормальные ячейки в табличное представление и обработать их как заголовки разделов, но лучший способ добиться этого - вставить эти ячейки в строка 0 каждого раздела. Таким образом, мы можем легко обрабатывать эти нестандартные заголовки без плавающих элементов.
Итак, шаги есть.
Реализуйте UITableView со стилем UITableViewStylePlain.
-(void) loadView { [super loadView]; UITableView *tblView =[[UITableView alloc] initWithFrame:CGRectMake(0, frame.origin.y, frame.size.width, frame.size.height-44-61-frame.origin.y) style:UITableViewStylePlain]; tblView.delegate=self; tblView.dataSource=self; tblView.tag=2; tblView.backgroundColor=[UIColor clearColor]; tblView.separatorStyle = UITableViewCellSeparatorStyleNone; }
Реализуйте titleForHeaderInSection как обычно (вы можете получить это значение, используя собственную логику, но я предпочитаю использовать стандартные делегаты).
- (NSString *)tableView: (UITableView *)tableView titleForHeaderInSection:(NSInteger)section { NSString *headerTitle = [sectionArray objectAtIndex:section]; return headerTitle; }
Воплощение numberOfSectionsInTableView как обычно
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { int sectionCount = [sectionArray count]; return sectionCount; }
Реализуйте numberOfRowsInSection как обычно.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { int rowCount = [[cellArray objectAtIndex:section] count]; return rowCount +1; //+1 for the extra row which we will fake for the Section Header }
Вернуть 0.0f в heightForHeaderInSection.
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 0.0f; }
НЕ реализуйте viewForHeaderInSection. Удалите метод полностью вместо возврата ноль.
В высоту ForRowAtIndexPath. Проверьте, если (indexpath.row == 0) и верните желаемую высоту ячейки для заголовка раздела, иначе верните высоту ячейки.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if(indexPath.row == 0) { return 80; //Height for the section header } else { return 70; //Height for the normal cell } }
Теперь в cellForRowAtIndexPath, проверьте, если (indexpath.row == 0), и реализуйте ячейку так, как вы хотите, чтобы был заголовок раздела, и установите стиль выбора равным none. Иначе реализуйте ячейку так, как вы хотите, чтобы была нормальная ячейка.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row == 0) { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SectionCell"]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SectionCell"] autorelease]; cell.selectionStyle = UITableViewCellSelectionStyleNone; //So that the section header does not appear selected cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"SectionHeaderBackground"]]; } cell.textLabel.text = [tableView.dataSource tableView:tableView titleForHeaderInSection:indexPath.section]; return cell; } else { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"] autorelease]; cell.selectionStyle = UITableViewCellSelectionStyleGray; //So that the normal cell looks selected cell.backgroundView =[[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"CellBackground"]]autorelease]; cell.selectedBackgroundView=[[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"SelectedCellBackground"]] autorelease]; } cell.textLabel.text = [[cellArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row -1]; //row -1 to compensate for the extra header row return cell; } }
Теперь реализуем willSelectRowAtIndexPath и возвращаем nil, если indexpath.row == 0. Это будет заботиться о том, чтобы didSelectRowAtIndexPath никогда не запускалось для строки заголовка раздела.
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row == 0) { return nil; } return indexPath; }
И, наконец, в didSelectRowAtIndexPath, проверьте, если (indexpath.row!= 0) и продолжить.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row != 0) { int row = indexPath.row -1; //Now use 'row' in place of indexPath.row //Do what ever you want the selection to perform } }
С этим вы сделали. Теперь у вас есть идеально прокручиваемый, не плавающий заголовок раздела.
Измените ваш стиль TableView:
self.tableview = [[UITableView alloc] initwithFrame:frame style:UITableViewStyleGrouped];
Согласно документации Apple для UITableView:
UITableViewStylePlain - простое табличное представление. Любые верхние или нижние колонтитулы отображаются как встроенные разделители и плавают при прокрутке табличного представления.
UITableViewStyleGrouped - табличное представление, разделы которого представляют различные группы строк. Верхние и нижние колонтитулы раздела не плавают.
Надеюсь, что это небольшое изменение поможет вам..
Интересной особенностью UITableViewStyleGrouped является то, что tableView добавляет стиль в ячейки, а не в TableView.
Стиль добавляется в ячейки как backgroundView как класс с именем UIGroupTableViewCellBackground, который обрабатывает рисование различного фона в соответствии с положением ячейки в разделе.
Поэтому очень простым решением будет использовать UITableViewStyleGrouped, установить backgroundColor таблицы в clearColor и просто заменить backgroundView ячейки в cellForRow:
cell.backgroundView = [[[UIView alloc] initWithFrame:cell.bounds] autorelease];
Есть еще один хитрый способ. Основная идея состоит в том, чтобы удвоить номер раздела, и первый показывает только headerView, а второй показывает реальные ячейки.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return sectionCount * 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section%2 == 0) {
return 0;
}
return _rowCount;
}
Затем нужно реализовать делегаты headerInSection:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section%2 == 0) {
//return headerview;
}
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (section%2 == 0) {
//return headerheight;
}
return 0;
}
Этот подход также мало влияет на ваши источники данных:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
int real_section = (int)indexPath.section / 2;
//your code
}
По сравнению с другими подходами этот способ безопасен, не изменяя фрейм или наборы содержимого табличного представления. Надеюсь, что это может помочь.
Самый простой способ получить то, что вы хотите, это установить стиль таблицы как UITableViewStyleGrouped
, стиль разделителя как UITableViewCellSeparatorStyleNone
:
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return CGFLOAT_MIN; // return 0.01f; would work same
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
return [[UIView alloc] initWithFrame:CGRectZero];
}
Не пытайтесь вернуть вид нижнего колонтитула как nil
Не забудьте установить высоту заголовка и вид заголовка, после того, как вы должны получить то, что вы хотели.
Для Swift 3+
Просто использовать UITableViewStyleGrouped
и установите высоту нижнего колонтитула в ноль с помощью следующего:
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return .leastNormalMagnitude
}
Хотя это, возможно, не решит вашу проблему, оно помогло мне, когда я хотел сделать подобное. Вместо установки заголовка я использовал нижний колонтитул раздела выше. Что спасло меня, так это то, что этот раздел был небольшим и статичным по своей природе, поэтому он никогда не прокручивался ниже нижней части окна.
Думая, как подойти к этой проблеме, я вспомнил очень важную деталь об UITableViewStyleGrouped. Способ, которым UITableView реализует сгруппированный стиль (округленные границы вокруг ячеек), заключается в добавлении пользовательского backgroundView в UITableViewCells, а не в UITableView. В каждую ячейку добавляется backgroundView в соответствии с его положением в разделе (верхние строки получают верхнюю часть границы раздела, средние - боковую, а нижний - ну, в общем, нижнюю часть). Итак, если мы просто хотим простой стиль, и у нас нет настраиваемого backgroundView для наших ячеек (что имеет место в 90% случаев), тогда все, что нам нужно сделать, это использовать UITableViewStyleGrouped и удалить пользовательский фон, Это можно сделать, выполнив эти два шага:
Измените наш стиль tableView на UITableViewStyleGrouped. Добавьте следующую строку в cellForRow непосредственно перед тем, как мы вернем ячейку:
cell.backgroundView = [[[UIView alloc] initWithFrame: cell.bounds] autorelease];
И это все. Стиль tableView станет точно таким же, как UITableViewStylePlain, за исключением плавающих заголовков.
Надеюсь это поможет!
Может быть, вы могли бы использовать scrollViewDidScroll
на tableView и изменить contentInset
на основе текущего видимого заголовка.
Кажется, работает для моего случая использования!
extension ViewController : UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let tableView = scrollView as? UITableView,
let visible = tableView.indexPathsForVisibleRows,
let first = visible.first else {
return
}
let headerHeight = tableView.rectForHeader(inSection: first.section).size.height
let offset = max(min(0, -tableView.contentOffset.y), -headerHeight)
self.tableView.contentInset = UIEdgeInsetsMake(offset, 0, -offset, 0)
}
}
Вы можете добавить один раздел (с нулевыми строками) выше, а затем установить вышеупомянутый sectionFooterView в качестве headerView текущего раздела, footerView не плавает. Надеюсь, это поможет.
Другой способ сделать это - создать пустой раздел прямо перед тем, на котором вы хотите заголовок, и поместить свой заголовок в этот раздел. Поскольку раздел пуст, заголовок будет прокручиваться немедленно.
Чтобы полностью удалить заголовки плавающей секции, вы можете сделать это:
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return [[UIView alloc] init];
}
return nil
не работает
Чтобы отключить плавающие, но по-прежнему показывать заголовки разделов, вы можете предоставить собственное представление с его собственным поведением.
Сложный способ - добавить пустой раздел для заголовка. Поскольку в разделе нет ячейки, он вообще не будет плавать.
Фрагмент отображает липкий заголовок только для первого раздела. Заголовки других разделов будут перемещаться вместе с ячейками.
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
if section == 1 {
tableView.contentInset = .zero
}
}
func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
if section == 0 {
tableView.contentInset = .init(top: -tableView.sectionHeaderHeight, left: 0, bottom: 0, right: 0)
}
}
Игнорировать XAK. Не используйте частные методы, если хотите, чтобы ваше приложение было принято Apple.
Это проще всего, если вы используете Interface Builder. Вы бы добавили UIView в верхней части представления (куда будут добавляться изображения), а затем добавили свое табличное представление под ним. IB должен измерить это соответственно; то есть верхняя часть табличного представления касается нижней части только что добавленного UIView, а его высота покрывает остальную часть экрана.
Мысль здесь в том, что если этот UIView на самом деле не является частью табличного представления, он не будет прокручиваться вместе с табличным представлением. т.е. игнорировать заголовок таблицы.
Если вы не используете конструктор интерфейса, это немного сложнее, потому что вы должны получить правильное расположение и высоту для таблицы.
Проверьте ответ, как реализовать заголовки с помощью StoryBoard: представления заголовков таблиц в StoryBoards
Также обратите внимание, что если вы не реализуете
viewForHeaderInSection:(NSInteger)section
это не будет плавать, что именно то, что вы хотите.
Лучшее решение для ниже 2 сценариев:
Сценарий 1. Если стиль tableView не сгруппирован. Нет проблем с изменением его на сгруппированный. нижний колонтитул не будет плавать.
Сценарий 2. Если вам нужен только один нижний колонтитул в конце tableView. и это стиль. простой
Шаги:
- добавить еще один раздел в конце.
- убедитесь, что в добавленном разделе (метод nuberOfRowsInSection) нулевое количество строк.
- добавить пользовательский нижний колонтитул в этот раздел.
- установите heightForFooterInSection для своего пользовательского нижнего колонтитула и установите .zero для нижних колонтитулов других разделов.
пример :
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
// примечание: отфильтруйте раздел, в который вы намеревались добавить настраиваемый раздел
let footerView = UIView()
submitOrganiser?.showFooterAtTheEnd(view: footerView) //my method to customise the footer, use your implementation
return footerView
}
**Swift 5.3 | Программно **
private func buildTableView() -> UITableView {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.rowHeight = UITableView.automaticDimension
tableView.showsVerticalScrollIndicator = false
tableView.separatorStyle = .none
let dummyViewHeight: CGFloat = 80
tableView.tableFooterView = UIView(
frame: CGRect(x: .zero,
y: .zero,
width: tableView.bounds.size.width,
height: dummyViewHeight))
tableView.contentInset = UIEdgeInsets(top: .zero, left: .zero, bottom: -dummyViewHeight, right: .zero)
return tableView
}
Если вы не возражаете против использования
UICollectionView
, этого также можно добиться с помощью конфигураций списка UICollectionView в iOS 13.0+ и с помощью
headerMode
установлен в
.firstItemInSection
.
override func viewDidLoad() {
super.viewDidLoad()
var config = UICollectionLayoutListConfiguration.init(appearance: .plain)
config.headerMode = .firstItemInSection
let listLayout = UICollectionViewCompositionalLayout.list(using: config)
self.collectionView.collectionViewLayout = listLayout
}
Вариант решения @samvermette:
/// Allows for disabling scrolling headers in plain-styled tableviews
extension UITableView {
static let shouldScrollSectionHeadersDummyViewHeight = CGFloat(40)
var shouldScrollSectionHeaders: Bool {
set {
if newValue {
tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: bounds.size.width, height: UITableView.shouldScrollSectionHeadersDummyViewHeight))
contentInset = UIEdgeInsets(top: -UITableView.shouldScrollSectionHeadersDummyViewHeight, left: 0, bottom: 0, right: 0)
} else {
tableHeaderView = nil
contentInset = .zero
}
}
get {
return tableHeaderView != nil && contentInset.top == UITableView.shouldScrollSectionHeadersDummyViewHeight
}
}
}
Может быть, вы можете просто сделать фоновый вид заголовка прозрачным:
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section {
view.tintColor = [UIColor clearColor];
}
Или примените это глобально:
[UITableViewHeaderFooterView appearance].tintColor = [UIColor clearColor];
Согласно ответу @samvermette , Я реализовал приведенный выше код в Swift, чтобы программистам было проще использовать swift.
let dummyViewHeight = CGFloat(40)
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: dummyViewHeight))
self.tableView.contentInset = UIEdgeInsetsMake(-dummyViewHeight, 0, 0, 0)
let tableView = UITableView(frame: .zero, style: .grouped)
Pluse
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
return 40
}else{
tableView.sectionHeaderHeight = 0
}
return 0
}
Это помогло использовать пространство заголовка
Это может быть достигнуто путем назначения представления заголовка вручную в методе viewDidLoad UITableViewController вместо использования делегата viewForHeaderInSection и heightForHeaderInSection. Например, в вашем подклассе UITableViewController вы можете сделать что-то вроде этого:
- (void)viewDidLoad {
[super viewDidLoad];
UILabel *headerView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 40)];
[headerView setBackgroundColor:[UIColor magentaColor]];
[headerView setTextAlignment:NSTextAlignmentCenter];
[headerView setText:@"Hello World"];
[[self tableView] setTableHeaderView:headerView];
}
Представление заголовка тогда исчезнет, когда пользователь прокрутит. Я не знаю, почему это работает так, но похоже, что вы достигаете того, что вы хотите сделать.
У меня есть другое, еще более простое решение, которое можно использовать без автоматической разметки и со всем, что делается через XIB:
1 / Поместите заголовок в табличное представление, перетащив его прямо в табличное представление.
2 / В инспекторе размера вновь созданного заголовка просто измените его авторазмер: вы должны оставить только верхний, левый и правый якоря, а также заполнение по горизонтали.
Это должно делать свое дело!