Можно ли использовать AutoLayout с UITableView tableHeaderView?
С AutoLayout
Я использую его везде, теперь я пытаюсь использовать его с tableHeaderView
.
я subclass
на UIView
добавлены все (этикетки и т. д...) Я хотел с их ограничениями, то я добавил Это CustomView
до UITableView
'tableHeaderView
.
все работает просто отлично, за исключением UITableView
всегда отображается выше the CustomView
, by выше я имею в виду CustomView
и под the UITableView
так что это не видно !
кажется, что независимо от того, что я делаю,height
на UITableView
'tableHeaderView
- это всегда 0 (так это ширина, x и y).
мой вопрос : возможно ли вообще это сделать без установки рамки вручную ?
EDIT:
Элемент CustomView
'subview
что я использую имеет следующие ограничения :
_title = [[UILabel alloc]init];
_title.text = @"Title";
[self addSubview:_title];
[_title keep:[KeepTopInset rules:@[[KeepEqual must:5]]]]; // title has to stay at least 5 away from the supperview Top
[_title keep:[KeepRightInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepLeftInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepBottomInset rules:@[[KeepMin must:5]]]];
я использую удобную библиотеку "KeepLayout", потому что пишу ограничения вручную занимает вечность и слишком много строк для одного ограничения, но методы самоописания.
и UITableView
имеет следующие ограничения :
_tableView = [[UITableView alloc]init];
_tableView.translatesAutoresizingMaskIntoConstraints = NO;
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.backgroundColor = [UIColor clearColor];
[self.view addSubview:_tableView];
[_tableView keep:[KeepTopInset rules:@[[KeepEqual must:0]]]];// These 4 constraints make the UITableView stays 0 away from the superview top left right and bottom.
[_tableView keep:[KeepLeftInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepRightInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepBottomInset rules:@[[KeepEqual must:0]]]];
_detailsView = [[CustomView alloc]init];
_tableView.tableHeaderView = _detailsView;
я не знаю, если я должен установить некоторые ограничения непосредственно на CustomView
, Я думаю, что высота CustomView определяется ограничениями на UILabel
"название" в нем.
EDIT 2: после другого исследования кажется высота и ширина CustomView правильно рассчитаны, но верхняя часть CustomView по-прежнему находится на том же уровне, что и верхняя часть UITableView, и они перемещаются вместе при прокрутке.
23 ответа:
Я задал и ответил на аналогичный вопрос здесь. Таким образом, я добавляю заголовок один раз и использую его, чтобы найти необходимую высоту. Затем эта высота может быть применена к заголовку, и заголовок устанавливается во второй раз, чтобы отразить изменение.
- (void)viewDidLoad { [super viewDidLoad]; self.header = [[SCAMessageView alloc] init]; self.header.titleLabel.text = @"Warning"; self.header.subtitleLabel.text = @"This is a message with enough text to span multiple lines. This text is set at runtime and might be short or long."; //set the tableHeaderView so that the required height can be determined self.tableView.tableHeaderView = self.header; [self.header setNeedsLayout]; [self.header layoutIfNeeded]; CGFloat height = [self.header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; //update the header's frame and set it again CGRect headerFrame = self.header.frame; headerFrame.size.height = height; self.header.frame = headerFrame; self.tableView.tableHeaderView = self.header; }
если у вас есть многострочные метки, это также зависит от настройки пользовательского представления preferredMaxLayoutWidth каждой метки:
- (void)layoutSubviews { [super layoutSubviews]; self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame); self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame); }
или, возможно, в более общем плане:
override func layoutSubviews() { super.layoutSubviews() for view in subviews { guard let label = view as? UILabel where label.numberOfLines == 0 else { continue } label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame) } }
Обновление Января 2015
к сожалению, это все еще кажется необходимым. Вот быстрая версия процесса компоновки:
tableView.tableHeaderView = header header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) tableView.tableHeaderView = header
Я нашел полезным переместить это в расширение на UITableView:
extension UITableView { //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again func setAndLayoutTableHeaderView(header: UIView) { self.tableHeaderView = header header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) self.tableHeaderView = header } }
использование:
let header = SCAMessageView() header.titleLabel.text = "Warning" header.subtitleLabel.text = "Warning message here." tableView.setAndLayoutTableHeaderView(header)
мне не удалось добавить представление заголовка с помощью ограничений (в коде). Если я даю моему виду ограничение ширины и / или высоты, я получаю сбой с сообщением:
"terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."
когда я добавляю представление в раскадровке в мое табличное представление, оно не показывает никаких ограничений, и оно отлично работает как представление заголовка, поэтому я думаю, что размещение представления заголовка не выполняется с использованием ограничений. Это, кажется, не ведет себя как нормальный взгляд в этом отношении.
ширина автоматически ширина табличного представления, единственное, что вам нужно установить, это высота-исходные значения игнорируются, поэтому не имеет значения, что вы вкладываете в них. Например, это работало нормально (как и 0,0,0,80 для прямой кишки):
UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)]; headerview.backgroundColor = [UIColor yellowColor]; self.tableView.tableHeaderView = headerview;
Я видел много методов здесь делать так много ненужных вещей, но вам не нужно так много, чтобы использовать auto layout в представлении заголовка. Вам просто нужно создать xib-файл, поместить свои ограничения и создать его следующим образом:
func loadHeaderView () { guard let headerView = Bundle.main.loadNibNamed("CourseSearchHeader", owner: self, options: nil)?[0] as? UIView else { return } headerView.autoresizingMask = .flexibleWidth headerView.translatesAutoresizingMaskIntoConstraints = true tableView.tableHeaderView = headerView }
другое решение-отправить создание представления заголовка в следующий вызов основного потока:
- (void)viewDidLoad { [super viewDidLoad]; // .... dispatch_async(dispatch_get_main_queue(), ^{ _profileView = [[MyView alloc] initWithNib:@"MyView.xib"]; self.tableView.tableHeaderView = self.profileView; }); }
Примечание: это исправить ошибку, когда загруженный вид имеет фиксированную высоту. Я не пробовал, когда высота заголовка зависит только от его содержимого.
EDIT:
вы можете найти более чистое решение этой проблемы, реализовав это функции, и
viewDidLayoutSubviews
- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self sizeHeaderToFit]; }
расширил это решение http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/ для просмотра нижнего колонтитула таблицы:
@interface AutolayoutTableView : UITableView @end @implementation AutolayoutTableView - (void)layoutSubviews { [super layoutSubviews]; // Dynamic sizing for the header view if (self.tableHeaderView) { CGFloat height = [self.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; CGRect headerFrame = self.tableHeaderView.frame; // If we don't have this check, viewDidLayoutSubviews() will get // repeatedly, causing the app to hang. if (height != headerFrame.size.height) { headerFrame.size.height = height; self.tableHeaderView.frame = headerFrame; self.tableHeaderView = self.tableHeaderView; } [self.tableHeaderView layoutIfNeeded]; } // Dynamic sizing for the header view if (self.tableFooterView) { CGFloat height = [self.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; CGRect footerFrame = self.tableFooterView.frame; // If we don't have this check, viewDidLayoutSubviews() will get // repeatedly, causing the app to hang. if (height != footerFrame.size.height) { footerFrame.size.height = height; self.tableFooterView.frame = footerFrame; self.tableFooterView = self.tableFooterView; } self.tableFooterView.transform = CGAffineTransformMakeTranslation(0, self.contentSize.height - footerFrame.size.height); [self.tableFooterView layoutIfNeeded]; } } @end
странные вещи происходит. systemLayoutSizeFittingSize отлично подходит для iOS9, но не для iOS 8 в моем случае. Так что эту проблему решить довольно легко. Просто получить ссылку на вид снизу в заголовке и в viewDidLayoutSubviews после супер вызова обновить границы представления заголовка, вставив высоту как CGRectGetMaxY (yourview.рамка) + обивка
UPD: самое простое решение когда-либо: Итак, в представлении заголовка поместите подвиды и прикрепите его к левый,право, top. В этом подвиде разместите свои подвиды с ограничениями автоматической высоты. После этого отдайте все задание на автоотчет (расчет не требуется)
- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; CGFloat height = CGRectGetMaxY(self.tableView.tableHeaderView.subviews.firstObject.frame); self.tableView.tableHeaderView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height); self.tableView.tableHeaderView = self.tableView.tableHeaderView; }
в результате subview расширяется / сжимается, как и должно быть, в конце он вызывает viewDidLayoutSubviews. В то время мы знаем фактический размер представления, поэтому установите headerview height и обновите его путем повторного назначения. Работает как шарм!
также работает для нижнего колонтитула.
вы можете получить autolayout, чтобы предоставить вам размер с помощью systemLayoutSizeFittingSize метод.
затем вы можете использовать это для создания фрейма для вашего приложения. Этот метод работает всякий раз, когда вам нужно знать размер представления, которое использует autolayout внутренне.
код в swift выглядит как
//Create the view let tableHeaderView = CustomTableHeaderView() //Set the content tableHeaderView.textLabel.text = @"Hello world" //Ask auto layout for the smallest size that fits my constraints let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) //Create a frame tableHeaderView.frame = CGRect(origin: CGPoint.zeroPoint, size: size) //Set the view as the header self.tableView.tableHeaderView = self.tableHeaderView
или в Objective-C
//Create the view CustomTableHeaderView *header = [[CustomTableHeaderView alloc] initWithFrame:CGRectZero]; //Set the content header.textLabel.text = @"Hello world"; //Ask auto layout for the smallest size that fits my constraints CGSize size = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; //Create a frame header.frame = CGRectMake(0,0,size.width,size.height); //Set the view as the header self.tableView.tableHeaderView = header
следует также отметить, что в данном конкретном случае, переопределение requiresConstraintBasedLayout в вашем подклассе приводит к выполнению прохода макета, однако результаты этого прохода макета игнорируются, а системный фрейм устанавливается на ширину tableView и высоту 0.
код:
extension UITableView { func sizeHeaderToFit(preferredWidth: CGFloat) { guard let headerView = self.tableHeaderView else { return } headerView.translatesAutoresizingMaskIntoConstraints = false let layout = NSLayoutConstraint( item: headerView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: preferredWidth) headerView.addConstraint(layout) let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height headerView.frame = CGRectMake(0, 0, preferredWidth, height) headerView.removeConstraint(layout) headerView.translatesAutoresizingMaskIntoConstraints = true self.tableHeaderView = headerView } }
следующее работало для меня.
- использовать простой старый
UIView
Как посмотреть заголовок.- добавить подвиды к этому
UIView
- используйте автозапуск на подвидах
основное преимущество, которое я вижу, это ограничение вычислений кадров. Apple действительно должна обновить
UITableView
API, чтобы сделать это проще.пример использования SnapKit:
let layoutView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 60)) layoutView.backgroundColor = tableView.backgroundColor tableView.tableHeaderView = layoutView let label = UILabel() layoutView.addSubview(label) label.text = "I'm the view you really care about" label.snp_makeConstraints { make in make.edges.equalTo(EdgeInsets(top: 10, left: 15, bottom: -5, right: -15)) }
мое представление заголовка таблицы является подклассом UIView - я создал один contentView UIView в инициализаторе, с его границами, такими же, как фрейм представления заголовка таблицы, и добавил Все мои объекты в качестве подвида этого.
затем добавьте ограничения для ваших объектов в представлении заголовка таблицы
layoutSubviews
метод, а не внутри инициализатора. Это решило проблему аварии.- (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:CGRectMake(0, 0, 0, 44.0)]; if (self) { UIView *contentView = [[UIView alloc] initWithFrame:self.bounds]; contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth; // add other objects as subviews of content view } return self; } - (void)layoutSubviews { [super layoutSubviews]; // remake constraints here }
вы можете добавить ограничение top + horizontal location между заголовком и tableview, чтобы разместить его правильно (если сам заголовок содержит все необходимые внутренние ограничения макета, чтобы иметь правильный кадр)
в tableViewController методе viewDidLoad
headerView.translatesAutoresizingMaskIntoConstraints = false tableView.tableHeaderView = headerView headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true headerView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
Я знаю, что это старый пост, но после прохождения всех сообщений SO относительно этого и прохождения целого дня, играя с этим, я, наконец, придумал чистое и все же очень простое решение
прежде всего, моя иерархия представления выглядит так:
- Посмотреть В Таблице
- View
tableHeaderView
- View С розеткой под названием headerView
теперь внутри представления (№3) я настроил все ограничения, как обычно, включая нижнее пространство для контейнера. Это сделает контейнер (т. е. 3.View т. е. headerView) для определения размера на основе его подвидов и их ограничений.
после этого я устанавливаю ограничения между
3. View
и2. View
на эти:
- верхний космос к контейнеру: 0
- ведущий космос к контейнеру: 0
- конечное пространство для контейнера: 0
обратите внимание, что я намеренно опускаю нижнее пространство намеренно.
после того, как все это делается в раскадровке, все, что осталось сделать, это вставить этот код:
if (self.headerView.frame.size.height != self.tableView.tableHeaderView.frame.size.height) { UIView *header = self.tableView.tableHeaderView; CGRect frame = self.tableView.tableHeaderView.frame; frame.size.height = self.headerView.frame.size.height + frame.origin.y; header.frame = frame; self.tableView.tableHeaderView = header; }
советы: Если вы используете метод setAndLayoutTableHeaderView, вы должны обновить фрейм вложенных представлений, поэтому в этой ситуации предпочтительный параметр Uilabelmaxlayoutwidth должен вызывать до вызова systemLayoutSizeFittingSize, не вызывайте layoutSubview.
поделиться своим подходом.
UITableView+XXXAdditions.m
- (void)xxx_setTableHeaderView:(UIView *)tableHeaderView layoutBlock:(void(^)(__kindof UIView *tableHeaderView, CGFloat *containerViewHeight))layoutBlock { CGFloat containerViewHeight = 0; UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; [backgroundView addSubview:tableHeaderView]; layoutBlock(tableHeaderView, &containerViewHeight); backgroundView.frame = CGRectMake(0, 0, 0, containerViewHeight); self.tableHeaderView = backgroundView; }
использование.
[self.tableView xxx_setTableHeaderView:myView layoutBlock:^(__kindof UIView * _Nonnull tableHeaderView, CGFloat *containerViewHeight) { *containerViewHeight = 170; [tableHeaderView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(@20); make.centerX.equalTo(@0); make.size.mas_equalTo(CGSizeMake(130, 130)); }]; }];
в моем случае метод с systemLayoutSizeFittingSize почему-то не работал. То, что сработало для меня, - это модификация решения, опубликованного HotJard (его оригинальное решение также не работало в моем случае на iOS 8). То, что мне нужно было сделать, это в представлении заголовка поместить подвида и закрепить его слева, справа, сверху (не закреплять снизу). Поместите все с помощью autolayout в этом подвиде и в коде сделать это:
- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self resizeHeaderToFitSubview]; } - (void)resizeHeaderToFitSubview { UIView *header = self.tableView.tableHeaderView; [header setNeedsLayout]; [header layoutIfNeeded]; CGFloat height = CGRectGetHeight(header.subviews.firstObject.bounds); header.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height); self.tableView.tableHeaderView = nil; self.tableView.tableHeaderView = header; }
старый пост. Но хороший пост. Вот мои 2 цента.
во-первых, убедитесь, что ваше представление заголовка имеет свои ограничения, расположенные так, чтобы оно могло поддерживать свой собственный внутренний размер контента. Затем сделайте следующее.
//ViewDidLoad headerView.translatesAutoresizingMaskIntoConstraints = false headerView.configure(title: "Some Text A") //Somewhere else headerView.update(title: "Some Text B) private var widthConstrained = false override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if widthConstrained == false { widthConstrained = true tableView.addConstraint(NSLayoutConstraint(item: headerView, attribute: .width, relatedBy: .equal, toItem: tableView, attribute: .width, multiplier: 1, constant: 0)) headerView.layoutIfNeeded() tableView.layoutIfNeeded() } } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { (context) in self.headerView.layoutIfNeeded() self.tableView.layoutIfNeeded() }, completion: nil) }
я смог добиться этого с помощью следующего подхода (это работает для нижнего колонтитула таким же образом).
во-первых, вам понадобится небольшая
мой AutoLayout работает очень хорошо:
CGSize headerSize = [headerView systemLayoutSizeFittingSize:CGSizeMake(CGRectGetWidth([UIScreen mainScreen].bounds), 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; headerView.frame = CGRectMake(0, 0, headerSize.width, headerSize.height); self.tableView.tableHeaderView = headerView;
вот как вы можете сделать в вашем
UIViewController
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if headerView.frame.size.height == 0 { headerView.label.preferredMaxLayoutWidth = view.bounds.size.width - 20 let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height headerView.frame.size = CGSize(width: tableView.bounds.size.width, height: height) } }
любое ограничение на основе
UIView
может быть хорошимtableHeaderView
.нужно установить
tableFooterView
перед тем, как наложить дополнительное ограничение трейлинга наtableFooterView
иtableHeaderView
.- (void)viewDidLoad { ........................ // let self.headerView is some constraint-based UIView self.tableView.tableFooterView = [UIView new]; [self.headerView layoutIfNeeded]; self.tableView.tableHeaderView = self.headerView; [self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES; [self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES; [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES; [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}
можно найти все детали и фрагменты кода здесь
Я придумал обходной путь. оберните ваши автомакет wrriten вид заголовка xib в пустой фантик наследник UIView, и назначить вид заголовка в собственность tableViewHeader TableView для это.
UIView *headerWrapper = [[UIView alloc] init]; AXLHomeDriverHeaderView *headerView = [AXLHomeDriverHeaderView loadViewFromNib]; [headerWrapper addSubview:headerView]; [headerView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(headerWrapper); }]; self.tableView.tableHeaderView = headerView;
принятый ответ полезен только для таблиц с одним разделом. Для многосекционных
UITableView
просто убедитесь, что ваш заголовок наследует отUITableViewHeaderFooterView
и вы будете в порядке.в качестве альтернативы, просто вставьте свой текущий заголовок в
contentView
наUITableViewHeaderFooterView
. Точно какUITableViewCell
строительство.
для пользователей Xamarin:
public override void ViewDidLayoutSubviews() { base.ViewDidLayoutSubviews(); TableviewHeader.SetNeedsLayout(); TableviewHeader.LayoutIfNeeded(); var height = TableviewHeader.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize).Height; var frame = TableviewHeader.Frame; frame.Height = height; TableviewHeader.Frame = frame; }
предполагая, что вы назвали представление заголовка вашего tableview как TableviewHeader