Дизайн заголовка раздела UITableView в интерфейсе Builder
У меня есть файл xib
с UITableView
, для которого я хочу добавить собственное представление заголовка раздела, используя метод делегата tableView:viewForHeaderInSection:
. Есть ли возможность спроектировать его в Interface Builder
, а затем программным образом изменить некоторые его свойства представления?
My UITableView
имеет больше заголовков разделов, поэтому создавая один UIView
в Interface Builder
, и возврат его не работает, потому что мне придется его дублировать, но нет хорошего способа его выполнения. Архивирование и разблокировка не работает для UIImage
, поэтому UIImageView
будет отображаться пустым.
Кроме того, я не хочу создавать их программно, потому что они слишком сложны, и полученный код будет трудно читать и поддерживать.
Изменить 1. Вот мой метод tableView:viewForHeaderInSection:
:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
}
CGSize headerSize = CGSizeMake(self.view.frame.size.width, 100);
/* wrapper */
UIView *wrapperView = [UIView viewWithSize:headerSize];
wrapperView.backgroundColor = [UIColor colorWithHexString:@"2670ce"];
/* title */
CGPoint titleMargin = CGPointMake(15, 8);
UILabel *titleLabel = [UILabel labelWithText:self.categoriesNames[section] andFrame:CGEasyRectMake(titleMargin, CGSizeMake(headerSize.width - titleMargin.x * 2, 20))];
titleLabel.textColor = [UIColor whiteColor];
titleLabel.font = [UIFont fontWithStyle:FontStyleRegular andSize:14];
[wrapperView addSubview:titleLabel];
/* body wrapper */
CGPoint bodyWrapperMargin = CGPointMake(10, 8);
CGPoint bodyWrapperViewOrigin = CGPointMake(bodyWrapperMargin.x, CGRectGetMaxY(titleLabel.frame) + bodyWrapperMargin.y);
CGSize bodyWrapperViewSize = CGSizeMake(headerSize.width - bodyWrapperMargin.x * 2, headerSize.height - bodyWrapperViewOrigin.y - bodyWrapperMargin.y);
UIView *bodyWrapperView = [UIView viewWithFrame:CGEasyRectMake(bodyWrapperViewOrigin, bodyWrapperViewSize)];
[wrapperView addSubview:bodyWrapperView];
/* image */
NSInteger imageSize = 56;
NSString *imageName = [self getCategoryResourceItem:section + 1][@"image"];
UIImageView *imageView = [UIImageView imageViewWithImage:[UIImage imageNamed:imageName] andFrame:CGEasyRectMake(CGPointZero, CGEqualSizeMake(imageSize))];
imageView.layer.masksToBounds = YES;
imageView.layer.cornerRadius = imageSize / 2;
[bodyWrapperView addSubview:imageView];
/* labels */
NSInteger labelsWidth = 60;
UILabel *firstLabel = [UILabel labelWithText:@"first" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 0, labelsWidth, 16)];
[bodyWrapperView addSubview:firstLabel];
UILabel *secondLabel = [UILabel labelWithText:@"second" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 20, labelsWidth, 16)];
[bodyWrapperView addSubview:secondLabel];
UILabel *thirdLabel = [UILabel labelWithText:@"third" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 40, labelsWidth, 16)];
[bodyWrapperView addSubview:thirdLabel];
[@[ firstLabel, secondLabel, thirdLabel ] forEachView:^(UIView *view) {
UILabel *label = (UILabel *)view;
label.textColor = [UIColor whiteColor];
label.font = [UIFont fontWithStyle:FontStyleLight andSize:11];
}];
/* line */
UIView *lineView = [UIView viewWithFrame:CGRectMake(imageSize + labelsWidth + bodyWrapperMargin.x * 2, bodyWrapperMargin.y, 1, bodyWrapperView.frame.size.height - bodyWrapperMargin.y * 2)];
lineView.backgroundColor = [UIColor whiteColorWithAlpha:0.2];
[bodyWrapperView addSubview:lineView];
/* progress */
CGPoint progressSliderOrigin = CGPointMake(imageSize + labelsWidth + bodyWrapperMargin.x * 3 + 1, bodyWrapperView.frame.size.height / 2 - 15);
CGSize progressSliderSize = CGSizeMake(bodyWrapperViewSize.width - bodyWrapperMargin.x - progressSliderOrigin.x, 30);
UISlider *progressSlider = [UISlider viewWithFrame:CGEasyRectMake(progressSliderOrigin, progressSliderSize)];
progressSlider.value = [self getCategoryProgress];
[bodyWrapperView addSubview:progressSlider];
return wrapperView;
}
и я хотел бы, чтобы он выглядел примерно так:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
}
SectionView *sectionView = ... // get the view that is already designed in the Interface Builder
sectionView.headerText = self.categoriesNames[section];
sectionView.headerImage = [self getCategoryResourceItem:section + 1][@"image"];
sectionView.firstLabelText = @"first";
sectionView.secondLabelText = @"second";
sectionView.thirdLabelText = @"third";
sectionView.progress = [self getCategoryProgress];
return wrapperView;
}
Изменить 2. Я не использую файлы Storyboard
, просто .xib
. Кроме того, у меня нет UITableViewController
, просто UIViewController
, в который я добавил UITableView
.
Ответы
Ответ 1
Я, наконец, решил его использовать этот учебник, который в основном состоит из следующих (адаптированный к моему примеру):
- Создайте класс
SectionHeaderView
, который подклассы UIView
.
- Создайте файл
SectionHeaderView.xib
и установите его File Owner
CustomClass
в класс SectionHeaderView
.
- Создайте свойство
UIView
в файле .m
, например: @property (strong, nonatomic) IBOutlet UIView *viewContent;
- Подключите
.xib
View
к этой розетке viewContent
.
-
Добавьте метод инициализации, который выглядит следующим образом:
+ (instancetype)header {
SectionHeaderView *sectionHeaderView = [[SectionHeaderView alloc] init];
if (sectionHeaderView) { // important part
sectionHeaderView.viewContent = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:sectionHeaderView options:nil] firstObject];
[sectionHeaderView addSubview:sectionHeaderView.viewContent];
return sectionHeaderView;
}
return nil;
}
Затем я добавил UILabel
внутри файла .xib
и подключил его к выходу labelCategoryName
и реализовал метод setCategoryName:
внутри класса SectionHeaderView
следующим образом:
- (void)setCategoryName:(NSString *)categoryName {
self.labelCategoryName.text = categoryName;
}
Затем я реализовал метод tableView:viewForHeaderInSection:
следующим образом:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
SectionHeaderView *sectionHeaderView = [SectionHeaderView header];
[sectionHeaderView setCategoryName:self.categoriesNames[section]];
return sectionHeaderView;
}
И это наконец сработало. Каждая секция имеет собственное имя, а также UIImageView
отображается правильно.
Надеюсь, что это помогает другим, которые снова и снова спотыкаются о одних и тех же неправильных решениях по всему миру, как и я.
Ответ 2
Раскадровка или XIB
-
Тот же Storyboard
:
return tableView.dequeueReusableCell(withIdentifier: "header")
![Два заголовка раздела]()
-
Отделить XIB
(Дополнительный шаг: сначала необходимо зарегистрировать этот Nib
):
tableView.register(UINib(nibName: "XIBSectionHeader", bundle:nil),
forCellReuseIdentifier: "xibheader")
Для загрузки из Storyboard
вместо XIB
см. этот ответ.
Использование UITableViewCell для создания заголовка раздела в IB
Воспользуйтесь тем, что заголовок секции является регулярным UIView
, а UITableViewCell
тоже является UIView
. В Конструктор интерфейсов перетащите ячейку просмотра таблицы из библиотеки объектов в свой содержимое прототипа таблицы.
Добавьте Идентификатор к недавно добавленной ячейке просмотра таблицы и настройте ее внешний вид в соответствии с вашими потребностями. В этом примере я использовал header
.
![Изменить ячейку]()
Используйте dequeueReusableCell:withIdentifier
, чтобы найти заголовок раздела, так же, как и любую ячейку таблицы. Вам нужно будет поставить heightForHeaderInSection
, который для ясности жестко запрограммирован как 44:
//MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
// This is where you would change section header content
return tableView.dequeueReusableCell(withIdentifier: "header")
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 44
}
Swift 2 и ранее:
return tableView.dequeueReusableCellWithIdentifier("header") as? UIView
self.tableView.registerNib(UINib(nibName: "XIBSectionHeader", bundle:nil),
forCellReuseIdentifier: "xibheader")
► Найдите это решение на GitHub и дополнительные сведения о Swift Рецепты.
Ответ 3
Решение Простой
Создайте один xib, создайте пользовательский интерфейс в соответствии с вашей документацией
то в viewForHeaderInSection получить xib
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil];
HeaderView *headerView = [nibArray objectAtIndex:0];
return headerView;
}
Ответ 4
Насколько я понимаю вашу проблему, вы хотите, чтобы один и тот же UIView дублировал несколько раз для заголовков нескольких разделов, которые вы хотите отображать.
Если это была моя проблема, вот как я ее разрешу.
ОРИГИНАЛЬНОЕ РЕШЕНИЕ
1)
В моем UIViewController, которому принадлежит представление таблицы, я также создаю представление, что шаблон для заголовка. Назначьте это IBOutlet. Это будет представление, которое вы можете редактировать с помощью Interface Builder.
2)
В вашем методе ViewDidLoad
или (возможно, лучше) ViewWillAppear
вы захотите сделать столько копий этого шаблона заголовка UIView, сколько вам нужно будет отображать для заголовков разделов.
Создание копий UIViews в памяти не является тривиальным, но это не сложно. Вот ответ из связанного вопроса, который показывает вам, как это сделать.
Добавьте копии в NSMutableArray
(где индекс каждого объекта будет соответствовать разделам... вид в индексе 0 массива будет тем, что вы возвращаете для раздела 0, вид 1 в массиве для раздела 1, ec.).
3)
Вы не сможете использовать IBOutlets для элементов этого заголовка раздела (поскольку ваш код связывает только точки с одним конкретным видом из файла XIB).
Итак, для этого вы, вероятно, захотите использовать свойства тега представления для каждого из элементов пользовательского интерфейса в своем представлении заголовка, которые вы хотите изменить/изменить для каждого другого раздела. Вы можете установить эти теги через Interface Builder, а затем обращаться к ним программно в своем коде.
В вашем методе viewForHeaderInSection
вы сделаете что-то вроде:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
}
SectionView *sectionView = [self.arrayOfDuplicatedHeaderViews objectAtIndex: section];
// my title view has a tag of 10
UILabel *titleToModify = [sectionView viewWithTag: 10];
if(titleToModify)
{
titleToModify.text = [NSString stringWithFormat:@"section %d", section];
}
return sectionView;
}
Имеет смысл?
РАЗНОЕ РЕШЕНИЕ
1)
Вам все равно нужен массив UIViews (или Section View
"подклассов UIViews), но вы можете создать каждый из них с последующими вызовами для загрузки представления из собственного файла XIB.
Что-то вроде этого:
@implementation SectionView
+ (SectionView*) getSectionView
{
NSArray* array = [[NSBundle mainBundle] loadNibNamed:@"SectionView" owner:nil options:nil];
return [array objectAtIndex:0]; // assume that SectionView is the only object in the xib
}
@end
(более подробно найдено в ответе на этот связанный вопрос)
2)
Возможно, вы сможете использовать IBOutlets (но я не уверен на 100%), но свойства тегов снова могут работать очень хорошо.