Можно ли использовать AutoLayout с tableHeaderView UITableView?

97

Поскольку я обнаружил, AutoLayoutчто использую его везде, теперь я пытаюсь использовать его с tableHeaderView.

Я сделал subclassиз UIViewдобавленных все (этикеток и т.д. ...) Я хотел с их ограничениями, то я добавил это CustomViewк UITableView' tableHeaderView.

Все работает отлично , за исключением UITableViewвсегда отображается вышеCustomView , по выше я имею в виду CustomViewэто подUITableView поэтому он не может быть виден!

Кажется , что независимо от того , что я делаю, то heightиз UITableView' tableHeaderViewявляется всегда 0 (так ширина х и у).

У меня вопрос: а можно ли вообще это сделать без ручной настройки рамки ?

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«заголовок» в нем.

РЕДАКТИРОВАТЬ 2: после другого исследования кажется, что высота и ширина CustomView рассчитаны правильно, но верх CustomView по-прежнему находится на том же уровне, что и верх UITableView, и они перемещаются вместе, когда я прокручиваю.

Это секрет
источник
Да, это возможно. Вы можете показать код, который используете? Трудно дать совет, не зная, какие ограничения вы установили для представления заголовка.
jrturton
Простой способ сделать это - добавить это представление в IB к tableView ... просто создайте представление в той же сцене, что и табличное представление, и перетащите его в таблицу.
Мариам К.
Я стараюсь избегать IB как можно больше, пока мне не приходилось его использовать, если я не могу заставить его работать, я попробую с IB
ItsASecret
1
Apple советует разработчикам использовать IB, когда это возможно, когда дело доходит до автоматического размещения. Это действительно помогает избежать множества проблем с несогласованностью.
Мариам К.
Настоящее полное решение для автоматической
раскладки

Ответы:

134

Я задал и ответил на аналогичный вопрос здесь . Таким образом, я добавляю заголовок один раз и использую его, чтобы найти нужную высоту. Затем эту высоту можно применить к заголовку, и заголовок устанавливается второй раз, чтобы отразить изменение.

- (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;
}

Если у вас есть многострочные метки, это также зависит от пользовательского представления, устанавливающего предпочитаемую максимальную ширину каждой метки:

- (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)
Бен Паккард
источник
8
Альтернативой использованию preferredMaxLayoutWidthявляется добавление ограничения ширины (равной ширине представления таблицы) в представление заголовка перед использованием systemLayoutSizeFittingSize:.
Benjohn
2
ПРИМЕЧАНИЕ: если вы заметили, что заголовок находится над первыми ячейками, то вы забыли сбросить свойство заголовка наself.tableView.tableHeaderView
Laszlo
8
Часто меня поражает, насколько сложно сделать что-то совершенно тривиальное, подобное этому.
TylerJames
5
ПРИМЕЧАНИЕ. Если вам нужно получить точную ширину в виде tableView, вы должны получить высоту с требуемым приоритетом по горизонталиlet height = header.systemLayoutSizeFittingSize(CGSizeMake(CGRectGetWidth(self.bounds), 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
Якуб Кнейзлик,
3
Just Use header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFitting(UILayoutFittingCompressedSize) self.tableHeaderView = headerбудет работать с iOS 10.2
Кесон Се
24

Мне не удалось добавить представление заголовка с использованием ограничений (в коде). Если я даю своему представлению ограничение ширины и / или высоты, я получаю сбой с сообщением:

 "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;
Rdelmar
источник
У меня тоже было это исключение, но добавление категории в UITableView исправило его. Я нашел его в том ответе: stackoverflow.com/questions/12610783/…
ItsASecret
Я все еще собираюсь попробовать то, что вы предлагаете, но завтра утром, сейчас 1:34, я иду спать, большое спасибо, что нашли время ответить! (Но я действительно хочу не указывать высоту, я бы хотел, чтобы она рассчитывалась по ограничениям, которые я установил на этикетке в CustomView)
ItsASecret,
Я пробовал, и да, настройка рамки работает, но я искал способ избежать установки рамки, я буду продолжать искать, и если ничего не найду, я приму ваш ответ
ItsASecret
1
Я получаю это исключение (в настоящее время тестирую 7.1), если добавленное представление заголовка имеет translatesAutoresizingMaskIntoConstraints = NO. Включение перевода предотвращает ошибку - я подозреваю, UITableViewчто начиная с версии 7.1 не пытается автоматически раскладывать представление заголовка и хочет что-то с предустановленным фреймом.
Benjohn
17

Я видел здесь множество методов, делающих так много ненужного, но вам не нужно так много, чтобы использовать автоматический макет в представлении заголовка. Вам просто нужно создать свой 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
    }
Рамон Васконселос
источник
Это также сработало для нас в iOS 11 с заголовком динамической высоты с многострочными метками.
Бен Шейрман 08
1
Вы также можете удалить в IB параметр flexibleHeight-Autoresizing-Option.
d4Rk
Я пытался установить высоту моего tableFooterView (через xib / nib), и мне не удалось установить фрейм, высоту, layoutIfNeeded () и т. Д. Но это решение наконец позволило мне установить его.
vikzilla 09
Не забудьте установить ограничение по высоте для всего вида в файле xib.
Денис Кутлубаев
6

Другое решение - отправить создание представления заголовка следующему вызову основного потока:

- (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];
}
Мартин
источник
1
@TussLaszlo tableHeaderViewвроде глючит с автопложением. Есть некоторые обходные пути, подобные этому. Но так как я писал это, я нашел лучший и чистый раствор здесь stackoverflow.com/a/21099430/127493 вызывая его - (void)sizeHeaderToFitинviewDidLayoutSubviews
Мартин
4

Код:

  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
          }
  }
Фил
источник
Это работает. Задайте правильные ограничения автоматического размещения для всех подпредставлений tableheaderview. Если вы пропустите одно ограничение, оно не сработает.
abhimuralidharan
4

Расширено это решение 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 footer 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
k06a
источник
Вчера провел день, пытаясь заставить tableHeader автоматически изменять размер / макет. Это решение мне подходит. Огромное спасибо.
docchang
Здравствуй! Не могли бы вы объяснить self.tableFooterView.transformчасть? Зачем это нужно?
mrvn
Преобразование @mrvn используется для перемещения нижнего колонтитула в нижнюю часть tableView.
k06a
3

Вы можете настроить автоматическое размещение, чтобы указать размер, используя метод 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

Следует также отметить, что в этом конкретном случае переопределение requiresConstraintBasedLayout в вашем подклассе действительно приводит к выполнению прохода макета, однако результаты этого прохода макета игнорируются, а системный фрейм устанавливается на ширину tableView и высоту 0.

Джонатан
источник
3

Следующее сработало для меня.

  1. В качестве UIViewзаголовка используйте обычное старое .
  2. Добавьте к этому подвиды UIView
  3. Использовать автоматическое размещение для подвидов

Основное преимущество, которое я вижу, - это ограничение вычислений кадров. Apple действительно стоит обновить UITableViewAPI, чтобы упростить эту задачу.

Пример использования 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))
}
Дэвид Никс
источник
3

Бывают странные вещи. systemLayoutSizeFittingSize отлично работает для iOS9, но не для iOS 8 в моем случае. Так что эту проблему решить довольно просто. Просто получите ссылку на нижний вид в заголовке и в viewDidLayoutSubviews после границ представления заголовка обновления супервызова, вставив высоту как CGRectGetMaxY (yourview.frame) + padding

UPD: самое простое решение : в заголовке поместите subview и закрепите его слева , справа , сверху . В этом подвиде поместите свои подвиды с ограничениями автоматической высоты. После этого отдайте всю работу автолистке (расчет не требуется)

- (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 и обновите его, переназначив. Работает как шарм!

Также работает для нижнего колонтитула.

HotJard
источник
1
Это зацикливается на iOS 10.
Саймон
3

Обновлено для 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
        }
    }
}
Калеб Фриден
источник
2

вы можете добавить ограничение верхнего + горизонтального расположения между заголовком и табличным представлением, чтобы правильно разместить его (если сам заголовок содержит все необходимые внутренние ограничения макета для правильного фрейма)

в методе viewDidLoad tableViewController

    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
GreatWiz
источник
1

Мое представление заголовка таблицы является подклассом 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
}
Райан
источник
1

Мой 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;
RomanV
источник
Я не делал этого в точности, но вы дали мне хорошую идею - удалить headerView, переустановить его фрейм и добавить его обратно.
динешарджани
1

В большинстве случаев лучшим решением будет просто не бороться с фреймворком и использовать маски автоизменения размеров:

// 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

Используя маски с автоизменением размеров, вы сообщаете фреймворку, как ваше представление должно изменять свой размер, когда супервизор изменяет свой размер. Но это изменение основано на начальном кадре, который вы установили.

pfandrade
источник
0

Я знаю, что это старый пост, но после того, как я просмотрел все сообщения SO по этому поводу и провел целый день, играя с этим, я наконец придумал чистое и все же очень простое решение.

Во-первых, моя иерархия представлений выглядит так:

  1. Просмотр таблицы
    1. Посмотреть tableHeaderView
      1. Просмотр с выходом под названием headerView

Теперь внутри представления (№ 3) я установил все ограничения, как обычно, включая нижнее пространство для контейнера. Это заставит контейнер (т.е. 3.View, то есть headerView) иметь размер в зависимости от его подпредставлений и их ограничений.

После этого я установил ограничения между ними 3. Viewи 2. View:

  1. Верхнее пространство до контейнера: 0
  2. Ведущий пробел до контейнера: 0
  3. Конечный пробел до контейнера: 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;
}
Марк-Александр Берубе
источник
0

Подсказки: если вы используете метод setAndLayoutTableHeaderView, вы должны обновить фрейм подпредставлений, поэтому в этой ситуации предпочтительныйMaxLayoutWidth UILabel должен вызывать перед вызовом systemLayoutSizeFittingSize, не вызывайте в layoutSubview.

код шоу

user1511613
источник
0

Поделитесь моим подходом.

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));
    }];
  }];
Винсент Сит
источник
0

В моем случае метод с systemLayoutSizeFittingSize по какой-то причине не работал. У меня сработала модификация решения, опубликованного HotJard (его оригинальное решение также не сработало в моем случае на iOS 8). Что мне нужно было сделать, так это поместить в представление заголовка подпредставление и закрепить его слева, справа, сверху (не прикреплять к низу). Поместите все, используя autolayout в этом subview, и в коде сделайте следующее:

- (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;
}
Лешек Сары
источник
0

Старый пост. Но хороший пост. Вот мои 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)
}

источник
0

Мне удалось добиться этого с помощью следующего подхода (это работает и для нижнего колонтитула).

Для начала вам понадобится небольшое UITableViewрасширение:

Swift 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реализации подпредставления заголовка :

  1. Вы должны быть на 100% уверены, что ваше представление заголовка имеет правильную настройку автоматического размещения. Я бы рекомендовал начать с простого представления заголовка с одним ограничением по высоте и опробовать описанную выше настройку.

  2. Отменить requiresConstraintBasedLayoutи вернуть true:

.

class MyHeaderView: UIView {
   // ...
   override static var requiresConstraintBasedLayout : Bool {
       return true
   }
   // ...
}
Евгений Дубинин
источник
0

Для пользователей 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

Густаво Байокки Коста
источник
0

Вот как вы можете сделать в своем 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)
    }
  }
onmyway133
источник
0

Любые ограничения 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;

}

Все подробности и фрагменты кода можно найти здесь

Malex
источник
0

Я нашел обходной путь. оберните представление заголовка 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;
Tounaobun
источник
0

Вот что работает для 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();
}
Пьяный папа
источник
0

Я столкнулся с проблемой получения ширины 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
    }
}
Барон Чанг
источник
0

Я создал подкласс UITableViewи использовал его UIStackViewкак для верхнего, так и для нижнего колонтитула, он также позволяет настроить более одного представления.

https://github.com/omaralbeik/StackableTableView

Омар Альбейк
источник
0

Мое решение - создать такой новый класс.

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)
    }

}

Чтобы использовать его, просто добавьте все свои подпредставления в экземпляр BaseTableHeaderViewи прикрепите его к представлению таблицы.

let tableHeaderView = BaseTableHeaderView()
tableHeaderView.addSubview(...)
tableView.tableHeaderView = tableHeaderView

Он автоматически изменит размер в зависимости от ограничений.

Гессен Хуанг
источник
-1

Принятый ответ полезен только для таблиц с одним разделом. Если вы работаете с несколькими разделами, UITableViewпросто убедитесь, что ваш заголовок наследуется от, UITableViewHeaderFooterViewи все будет в порядке.

В качестве альтернативы, просто вставить свой текущий заголовок в contentViewо UITableViewHeaderFooterView. Точно вроде UITableViewCellработает.

redent84
источник
9
вопрос tableHeaderViewне в шапке раздела.
Rpranata