Можно ли использовать AutoLayout с tableHeaderView UITableView?
Так как я обнаружил AutoLayout
Я использую это везде, теперь я пытаюсь использовать это с tableHeaderView
,
я сделал subclass
из UIView
добавил все (ярлыки и т. д.), я хотел с их ограничениями, затем я добавил это CustomView
к UITableView
'tableHeaderView
,
Все работает отлично, кроме UITableView
всегда отображается над CustomView
выше я имею в виду CustomView
находится под UITableView
так что это не видно!
Кажется, что независимо от того, что я делаю, height
из UITableView
'tableHeaderView
всегда 0 (как и ширина, х и у).
Мой вопрос: возможно ли вообще это сделать, не устанавливая фрейм вручную?
РЕДАКТИРОВАТЬ: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
"название" в нем.
РЕДАКТИРОВАТЬ 2: После другого исследования кажется, что высота и ширина CustomView правильно рассчитаны, но вершина CustomView все еще находится на том же уровне, что и верхняя часть UITableView, и они перемещаются вместе, когда я прокручиваю.
30 ответов
Я задал и ответил на аналогичный вопрос здесь. Таким образом, я добавляю заголовок один раз и использую его, чтобы найти необходимую высоту. Затем эту высоту можно применить к заголовку, и заголовок устанавливается во второй раз, чтобы отразить изменение.
- (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;
}
Если у вас есть многострочные метки, это также зависит от пользовательского представления, устанавливающего предпочитаемый параметр MaxLayoutWidth каждой метки:
- (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;
Я видел много методов, делающих так много ненужных вещей, но вам не нужно так много, чтобы использовать автоматическую разметку в представлении заголовка. Вам просто нужно создать свой 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;
});
}
Примечание: это исправляет ошибку, когда загруженный вид имеет фиксированную высоту. Я не пробовал, когда высота заголовка зависит только от его содержимого.
РЕДАКТИРОВАТЬ:
Вы можете найти более чистое решение этой проблемы, реализовав эту функцию и вызвав ее в viewDidLayoutSubviews
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self sizeHeaderToFit];
}
Код:
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
}
}
Обновлено для Swift 4.2
extension UITableView {
var autolayoutTableViewHeader: UIView? {
set {
self.tableHeaderView = newValue
guard let header = newValue else { return }
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size =
header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
self.tableHeaderView = header
}
get {
return self.tableHeaderView
}
}
}
Вы можете получить autolayout, чтобы предоставить вам размер, используя метод systemLayoutSizeFittingSize.
Затем вы можете использовать это для создания фрейма для вашего приложения. Этот метод работает всякий раз, когда вам нужно знать размер представления, которое использует внутреннее размещение.
Код в 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
Следует также отметить, что в этом конкретном случае переопределение requireConstraintBasedLayout в вашем подклассе приводит к выполнению этапа макета, однако результаты этого этапа макета игнорируются, а системный кадр устанавливается на ширину tableView и 0 высот.
Расширенное решение 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.frame) + padding
UPD: самое простое решение из всех: Итак, в представлении заголовка разместите подпредставление и закрепите его слева, справа, сверху. В этом подпредставлении разместите ваши подпредставления с автоматическими ограничениями высоты. После этого отдайте всю работу на автопрокладку (расчет не требуется)
- (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;
}
В результате подпредставление расширяется / уменьшается, как и должно быть, в конце оно вызывает viewDidLayoutSubviews. В то время как мы знаем фактический размер представления, поэтому установите высоту headerView и обновите его, переназначив. Работает как шарм!
Также работает для нижнего колонтитула.
Вы можете добавить верхнее + горизонтальное ограничение местоположения между заголовком и табличным представлением, чтобы правильно разместить его (если сам заголовок содержит все необходимые внутренние ограничения макета, чтобы иметь правильный кадр)
в методе 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
Следующее сработало для меня.
- Используйте старый добрый
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))
}
В большинстве случаев лучшим решением будет просто не бороться с фреймворком и использовать маски автоизменения размеров:
// embrace autoresizing masks and let the framework add the constraints for you
headerView.translatesAutoresizingMaskIntoConstraints = true
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// figure out what's the best size based on the table view width
let width = self.tableView.frame.width
let targetSize = headerView.systemLayoutSizeFitting(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
headerView.frame.size = targetSize
self.tableView.tableHeaderView = headerView
Используя маски с автоизменением размеров, вы сообщаете фреймворку, как ваше представление должно изменять свой размер, когда супервизор изменяет свой размер. Но это изменение основано на начальном кадре, который вы установили.
Мое решение - создать такой новый класс.
class BaseTableHeaderView: UIView {
func sizeToFitBasedOnConstraints(width: CGFloat = Screen.width) {
let size = systemLayoutSizeFitting(CGSize(width: width, height: 10000),
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel)
frame = CGRect(origin: .zero, size: size)
}
override func willMove(toSuperview newSuperview: UIView?) {
sizeToFitBasedOnConstraints()
super.willMove(toSuperview: newSuperview)
}
}
Чтобы использовать его, просто добавьте все свои subviews в экземпляр BaseTableHeaderView
и прикрепите его к представлению таблицы.
let tableHeaderView = BaseTableHeaderView()
tableHeaderView.addSubview(...)
tableView.tableHeaderView = tableHeaderView
Он автоматически изменит размер в зависимости от ограничений.
Мой вид заголовка таблицы - это подкласс UIView - я создал один контент-вид 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
}
Мой 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;
После проверки различных решений, которые раньше работали, но больше не работают, я нашел следующее решение для представления заголовка с многострочной меткой, которое отлично работает для меня на iOS 12, iOS 13, iOS 14 во всех случаях, а также с поворотом устройства:
- Создайте свой заголовок с автоматическим макетом, как обычно (таким образом, чтобы автоматический макет мог вычислить высоту этого представления на основе используемых ограничений). Если вы создаете его в коде, убедитесь, что вы установили translatesAutoresizingMaskIntoConstraints = false.
- Назначьте представление заголовка свойству tableHeaderView вашего tableView.
- Установите ограничение, чтобы ширина представления заголовка соответствовала ширине представления таблицы.
- В viewDidLayoutSubviews снова установите tableHeaderView с тем же представлением, но только если оно еще не было установлено ранее с той же высотой (чтобы предотвратить рекурсивный цикл вызовов viewDidLayoutSubviews). Для корректной работы во всех случаях и на разных версиях iOS нужно сделать это в DispatchQueue.main.async (без него работает только в некоторых случаях).
- Если вы измените что-то в своем представлении, которое может изменить свою высоту (например, текст в многострочной метке), вам также нужно сделать то же самое, что и в viewDidLayoutSubviews, чтобы оно работало во всех случаях (опять же, без него оно работает только в некоторых случаях). случаи).
Пример кода:
class TestViewController: UITableViewController {
let headerView = TestHeaderView() // has translatesAutoresizingMaskIntoConstraints = false
var lastSetHeaderViewHeight: CGFloat?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
reloadHeaderViewIfNeeded()
}
func reloadHeaderViewIfNeeded() {
DispatchQueue.main.async {
if self.lastSetHeaderViewHeight != self.headerView.bounds.size.height {
self.lastSetHeaderViewHeight = self.headerView.bounds.size.height
self.tableView.tableHeaderView = self.headerView
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableHeaderView = headerView
headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor, constant: 0).isActive = true
// ...
}
func somethingThatCanChangeHeightOfHeaderView() {
// ...
reloadHeaderViewIfNeeded()
}
// ...
}
Советы: Если вы используете метод setAndLayoutTableHeaderView, вы должны обновить фрейм подпредставлений, поэтому в этой ситуации предпочитаемый ULabelMaxLayoutWidth должен вызываться до вызова systemLayoutSizeFittingSize, не вызывать в layoutSubview.
Старый пост. Но хороший пост. Вот мои 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)
}
Для пользователей 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;
}
Предполагая, что вы назвали представление заголовка вашей таблицы как TableviewHeader
Я создал подкласс UITableView
и использовал UIStackView
как для верхнего, так и для нижнего колонтитула он также позволяет настроить более одного представления.
Вот как вы можете сделать в своем 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)
}
}
Вот что работает для UITableViewController в ios 12,
Нарисуйте UIView в TableView над всеми ячейками прототипа для заголовка и под всеми ячейками прототипа для нижнего колонтитула. Настройте верхний и нижний колонтитулы по мере необходимости. Установите все необходимые ограничения.
Теперь используйте следующие методы расширения
public static class UITableVIewExtensions
{
public static void MakeHeaderAutoDimension(this UITableView tableView)
{
if (tableView.TableHeaderView is UIView headerView) {
var size = headerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
if (headerView.Frame.Size.Height != size.Height) {
var frame = headerView.Frame;
frame.Height = size.Height;
headerView.Frame = frame;
tableView.TableHeaderView = headerView;
tableView.LayoutIfNeeded();
}
}
}
public static void MakeFooterAutoDimension(this UITableView tableView)
{
if (tableView.TableFooterView is UIView footerView) {
var size = footerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
if (footerView.Frame.Size.Height != size.Height) {
var frame = footerView.Frame;
frame.Height = size.Height;
footerView.Frame = frame;
tableView.TableFooterView = footerView;
tableView.LayoutIfNeeded();
}
}
}
}
и вызвать его в ViewDidLayoutSubviews подкласса UITableViewController
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
TableView.MakeHeaderAutoDimension();
TableView.MakeFooterAutoDimension();
}
Я нашел обходной путь. оберните ваше представление заголовка xib autolayout wrriten в пустую оболочку uiview и назначьте представление заголовка свойству tableView tableViewHeader.
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;
В моем случае метод с 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;
}
Я столкнулся с проблемой получения ширины 375pt, единственный способ, который сработал для меня, - это ретранслировать tableView, чтобы получить правильную ширину. Я также предпочел AutoLayout настройке размера кадра.
Вот версия, которая мне подходит:
Xamarin.iOS
public static void AutoLayoutTableHeaderView(this UITableView tableView, UIView header)
{
tableView.TableHeaderView = header;
tableView.SetNeedsLayout();
tableView.LayoutIfNeeded();
header.WidthAnchor.ConstraintEqualTo(tableView.Bounds.Width).Active = true;
tableView.TableHeaderView = header;
}
Swift Version (изменено из ответа @Ben Packard)
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
self.setNeedsLayout()
self.layoutIfNeeded()
header.widthAnchor.widthAnchor.constraint(equalTo: self.bounds.width).isActive = true
self.tableHeaderView = header
}
}
Я знаю, что это старый пост, но, пройдя все посты SO по этому поводу и проведя целый день, играя с этим, я наконец-то нашел чистое и в то же время очень простое решение.
Прежде всего, моя иерархия представлений выглядит так:
- Табличное представление
- Посмотреть
tableHeaderView
- Посмотреть с помощью розетки headerView
- Посмотреть
Теперь внутри View (№ 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;
}
Поделитесь моим подходом.
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));
}];
}];
Любое ограничение 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;
}
Все подробности и фрагменты кода можно найти здесь
Я смог добиться этого с помощью следующего подхода (это работает для нижнего колонтитула так же).
Во-первых, вам понадобится маленький UITableView
расширение:
Свифт 3
extension UITableView {
fileprivate func adjustHeaderHeight() {
if let header = self.tableHeaderView {
adjustFrame(header)
}
}
private func adjustFrame(_ view: UIView) {
view.frame.size.height = calculatedViewHeight(view)
}
fileprivate func calculatedHeightForHeader() -> CGFloat {
if let header = self.tableHeaderView {
return calculatedViewHeight(header)
}
return 0.0
}
private func calculatedViewHeight(_ view: UIView) -> CGFloat {
view.setNeedsLayout()
let height = view.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
return height
}
}
По вашему мнению реализация класса контроллера:
// this is a UIView subclass with autolayout
private var headerView = MyHeaderView()
override func loadView() {
super.loadView()
// ...
self.tableView.tableHeaderView = headerView
self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension
// ...
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// this is to prevent recursive layout calls
let requiredHeaderHeight = self.tableView.calculatedHeightForHeader()
if self.headerView.frame.height != requiredHeaderHeight {
self.tableView.adjustHeaderHeight()
}
}
Примечания о заголовке UIView
Подвид реализации:
Вы должны быть на 100% уверены, что ваш заголовок имеет правильную настройку автоматического размещения. Я бы порекомендовал начать с простого вида заголовка с одним ограничением высоты и попробовать настройку выше.
Override
requiresConstraintBasedLayout
и вернутьсяtrue
:
,
class MyHeaderView: UIView {
// ...
override static var requiresConstraintBasedLayout : Bool {
return true
}
// ...
}
Принятый ответ полезен только для таблиц с одним разделом. Для многосекционных UITableView
просто убедитесь, что ваш заголовок наследуется от UITableViewHeaderFooterView
и у тебя все будет хорошо.
В качестве альтернативы просто вставьте текущий заголовок в contentView
из UITableViewHeaderFooterView
, Точно так же, как UITableViewCell
работает.