Как установить высоту tableHeaderView (UITableView) с автозапуском?
Я размахиваю своей головой о стену в течение последних 3 или 4 часов, и я не могу понять это. У меня есть UIViewController с полноэкранным UITableView внутри него (там некоторые другие вещи на экране, поэтому я не могу использовать UITableViewController), и я хочу, чтобы мой tableHeaderView изменил размер с автозапуском. Излишне говорить, что он не сотрудничает.
Смотрите снимок экрана ниже.
Поскольку обзорная заметка (например, текст "Обзор обзорной информации здесь." ) имеет динамический контент, я использую автозапуск, чтобы изменить его размер и контролировать. У меня есть все изменения размера, за исключением таблицыHeaderView, которая находится прямо под Paralax Table View в hiearchy.
Единственный способ, которым я нашел изменение размера заголовка, является программным, со следующим кодом:
CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];
В этом случае headerFrameHeight представляет собой ручной расчет высоты таблицы TableHeader следующим образом (innerHeaderView - это белая область или второй "вид", headerView - tableHeaderView):
CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y;
CGRect overviewSize = [self.overviewLabel.text
boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: self.overviewLabel.font}
context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);
Ручной расчет работает, но он неуклюже и подвержен ошибкам, если в будущем ситуация изменится. То, что я хочу, чтобы иметь возможность сделать, - это сделать автоматическое изменение размера таблицыHeaderView на основе предоставленных ограничений, например, в любом другом месте. Но для жизни меня я не могу понять.
Там есть несколько сообщений об этом, но ни один из них не ясен и в конечном итоге запутывал меня больше. Вот несколько:
На самом деле не имеет смысла изменять свойство translatesAutoresizingMaskIntoConstraints на NO, поскольку это просто вызывает ошибки для меня и в любом случае не имеет смысла концептуально.
Любая помощь будет действительно оценена!
РЕДАКТИРОВАТЬ 1:
Благодаря предложению TomSwift, я смог понять это. Вместо того, чтобы вручную вычислять высоту обзора, я могу рассчитать его для меня следующим образом, а затем снова установить tableHeaderView как обычно.
[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView.
CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];
Изменить 2: Как отмечали другие, решение, опубликованное в редакторе 1, похоже, не работает в viewDidLoad. Однако, похоже, он работает в viewWillLayoutSubviews. Пример кода ниже:
// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[_headerWrapper setNeedsLayout];
[_headerWrapper layoutIfNeeded];
CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = _headerWrapper.frame;
headerFrame.size.height = height;
_headerWrapper.frame = headerFrame;
_tableView.tableHeaderView = _headerWrapper;
}
Ответы
Ответ 1
Вам нужно использовать метод UIView systemLayoutSizeFittingSize:
для получения минимального ограничивающего размера вашего заголовка.
Далее я расскажу об использовании этого API в этом Q/A:
Как изменить размер супервизора для соответствия всем подзонам с автозапуском?
Ответ 2
Я действительно сражался с этим, и наложение настроек в viewDidLoad не сработало для меня, поскольку фрейм не установлен в viewDidLoad, я также столкнулся с множеством беспорядочных предупреждений, где инкапсулированная высота автоматического макета заголовка уменьшено до 0. Я заметил только проблему с iPad при представлении tableView в представлении формы.
Для меня проблема заключалась в установке tableViewHeader в viewWillLayoutSubviews, а не в viewDidLoad.
func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if tableView.tableViewHeaderView == nil {
let header: MyHeaderView = MyHeaderView.createHeaderView()
header.setNeedsUpdateConstraints()
header.updateConstraintsIfNeeded()
header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
var newFrame = header.frame
header.setNeedsLayout()
header.layoutIfNeeded()
let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
newFrame.size.height = newSize.height
header.frame = newFrame
self.tableView.tableHeaderView = header
}
}
Ответ 3
Я нашел элегантный способ использования автоматического макета для изменения размеров заголовков таблиц с анимацией и без нее.
Просто добавьте это в свой контроллер просмотра.
func sizeHeaderToFit(tableView: UITableView) {
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
}
}
Чтобы изменить размер в соответствии с динамически меняющейся меткой:
@IBAction func addMoreText(sender: AnyObject) {
self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}
override func viewDidLayoutSubviews() {
// viewDidLayoutSubviews is called when labels change.
super.viewDidLayoutSubviews()
sizeHeaderToFit(tableView)
}
Анимация размера в соответствии с изменениями ограничения:
@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!
@IBAction func makeThisTaller(sender: AnyObject) {
UIView.animateWithDuration(0.3) {
self.tableView.beginUpdates()
self.makeThisTallerHeight.constant += 20
self.sizeHeaderToFit(self.tableView)
self.tableView.endUpdates()
}
}
См. проект AutoResizingHeader, чтобы увидеть это в действии.
https://github.com/p-sun/Swift2-iOS9-UI
Ответ 4
Ваше решение, использующее systemLayoutSizeFittingSize: работает, если вид заголовка просто обновляется один раз на каждом представлении вида. В моем случае представление заголовка обновлялось несколько раз, чтобы отразить изменения состояния. Но systemLayoutSizeFittingSize: всегда сообщается о том же размере. То есть размер, соответствующий первому обновлению.
Чтобы получить systemLayoutSizeFittingSize: чтобы вернуть правильный размер после каждого обновления, мне пришлось сначала удалить заголовок таблицы перед обновлением и повторным добавлением:
self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];
Ответ 5
Это работало для меня на ios10 и Xcode 8
func layoutTableHeaderView() {
guard let headerView = tableView.tableHeaderView else { return }
headerView.translatesAutoresizingMaskIntoConstraints = false
let headerWidth = headerView.bounds.size.width;
let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])
headerView.addConstraints(temporaryWidthConstraints)
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
let height = headerSize.height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
self.tableView.tableHeaderView = headerView
headerView.removeConstraints(temporaryWidthConstraints)
headerView.translatesAutoresizingMaskIntoConstraints = true
}
Ответ 6
Это решение изменяет размер tableHeaderView и избегает бесконечного цикла в методе viewDidLayoutSubviews()
, который я имел с некоторыми другими ответами здесь:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
// comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
}
}
}
Смотрите также эту запись: fooobar.com/questions/23277/...
Ответ 7
В моем случае viewDidLayoutSubviews
работал лучше. viewWillLayoutSubviews
приводит к появлению белых строк tableView
. Также я добавил, проверяет, существует ли мой объект headerView
.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if ( ! self.userHeaderView ) {
// Setup HeaderView
self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
[self.userHeaderView setNeedsLayout];
[self.userHeaderView layoutIfNeeded];
CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = self.userHeaderView.frame;
headerFrame.size.height = height;
self.userHeaderView.frame = headerFrame;
self.tableView.tableHeaderView = self.userHeaderView;
// Update HeaderView with data
[self.userHeaderView updateWithProfileData];
}
}
Ответ 8
Вполне возможно использовать универсальный UIView на основе UIView
с любой внутренней структурой tableHeaderView
AL в качестве tableHeaderView
.
Единственное, что нужно - это tableFooterView
установить простой tableFooterView
!
Пусть self.headerView
- это некий UIView
основанный на UIView
.
- (void)viewDidLoad {
........................
self.tableView.tableFooterView = [UIView new];
[self.headerView layoutIfNeeded]; // to set initial size
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;
// and the key constraint
[self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}
Если self.headerView
меняет высоту при повороте пользовательского интерфейса, нужно реализовать
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
// needed to resize header height
self.tableView.tableHeaderView = self.headerView;
} completion: NULL];
}
Для этого можно использовать категорию ObjC
@interface UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView;
@end
@implementation UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView {
NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");
[headerView layoutIfNeeded];
self.tableHeaderView = headerView;
[self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES;
[self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
[self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES;
[self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
}
@end
А также расширение Swift
extension UITableView {
func am_insertHeaderView2(_ headerView: UIView) {
assert(tableFooterView != nil, "Need to define tableFooterView first!")
headerView.layoutIfNeeded()
tableHeaderView = headerView
leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true
tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
}
}
Ответ 9
Он работает как для представления заголовка, так и для нижнего колонтитула, просто замените заголовок нижним колонтитулом
func sizeHeaderToFit() {
if let headerView = tableView.tableHeaderView {
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
}
}