Czy można używać AutoLayout z tableHeaderView UITableView?

100

Odkąd odkryłem, AutoLayoutże używam go wszędzie, teraz próbuję go używać z rozszerzeniem tableHeaderView.

Zrobiłem subclassz UIViewdodatkiem wszystkiego (etykiety itp ...) Chciałem z ich ograniczeniami, a następnie dodałem to CustomViewdo UITableView" tableHeaderView.

Wszystko działa dobrze z wyjątkiem UITableViewzawsze wyświetla powyższychCustomView przez wyżej mam na myśli CustomViewto podUITableView więc nie widać!

Wydaje się, że bez względu na to, co robię, heightz UITableView' tableHeaderViewjest zawsze 0 (tak samo jak szerokość, x i y).

Moje pytanie: czy w ogóle da się to zrobić bez ręcznego ustawiania ramy ?

EDIT:CustomView ' subviewże używam ma następujące ograniczenia:

_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]]]];

Używam przydatnej biblioteki „KeepLayout”, ponieważ ręczne pisanie ograniczeń zajmuje wieczność i zbyt wiele wierszy dla jednego ograniczenia, ale metody nie wymagają wyjaśnień.

I UITableViewma te ograniczenia:

_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;

Nie wiem, czy muszę ustawiać pewne ograniczenia bezpośrednio na CustomView, myślę, że wysokość CustomView jest określana przez ograniczenia zawarte w UILabel„tytule”.

EDYCJA 2: Po kolejnym dochodzeniu wydaje się, że wysokość i szerokość CustomView są poprawnie obliczone, ale górna część CustomView jest nadal na tym samym poziomie co górna część UITableView i poruszają się razem, gdy przewijam.

To sekret
źródło
Tak to mozliwe. Czy możesz pokazać kod, którego używasz? Trudno jest doradzić, nie wiedząc, jakie ograniczenia ustawiłeś w widoku nagłówka.
jrturton
Łatwym sposobem na osiągnięcie tego jest dodanie tego widoku w IB do tableView… po prostu utwórz widok w tej samej scenie zawierającej widok tabeli i przeciągnij go do tabeli.
Mariam K.
Staram się unikać IB najbardziej, jak mogę, do tej pory nie musiałem go używać, jeśli nie mogę go uruchomić, spróbuję z IB
ItsASecret
1
Apple radzi programistom, aby korzystali z IB zawsze, gdy jest to możliwe, jeśli chodzi o automatyczne układanie. To naprawdę pomaga w uniknięciu wielu problemów związanych z niespójnością.
Mariam K.
Prawdziwe kompletne rozwiązanie autoukładu jest tutaj
malex

Odpowiedzi:

136

Tutaj zadałem i odpowiedziałem na podobne pytanie . Podsumowując, raz dodaję nagłówek i używam go do znalezienia wymaganej wysokości. Wysokość tę można następnie zastosować do nagłówka, a nagłówek jest ustawiany po raz drugi, aby odzwierciedlić zmianę.

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

Jeśli masz etykiety wielowierszowe, zależy to również od ustawienia widoku niestandardowego preferowanejMaxLayoutWidth każdej etykiety:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
    self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
}

a może bardziej ogólnie:

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

Aktualizacja styczeń 2015

Niestety nadal wydaje się to konieczne. Oto szybka wersja procesu tworzenia układu:

tableView.tableHeaderView = header
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
tableView.tableHeaderView = header

Uważam, że przydatne jest przeniesienie tego do rozszerzenia w 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
    }
}

Stosowanie:

let header = SCAMessageView()
header.titleLabel.text = "Warning"
header.subtitleLabel.text = "Warning message here."
tableView.setAndLayoutTableHeaderView(header)
Ben Packard
źródło
8
Alternatywą dla używania preferredMaxLayoutWidthjest dodanie ograniczenia szerokości (równego szerokości widoku tabeli) w widoku nagłówka przed użyciem systemLayoutSizeFittingSize:.
Benjohn
2
UWAGA: jeśli zauważysz, że nagłówek znajduje się powyżej pierwszych komórek, zapomniałeś zresetować właściwość nagłówka naself.tableView.tableHeaderView
Laszlo.
9
Często zdumiewa mnie, jak trudne może być zrobienie czegoś tak trywialnego.
TylerJames
5
UWAGA: Jeśli chcesz uzyskać dokładną szerokość jako tableView, powinieneś uzyskać wysokość z wymaganym priorytetem poziomymlet height = header.systemLayoutSizeFittingSize(CGSizeMake(CGRectGetWidth(self.bounds), 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
JakubKnejzlik
3
Po prostu użyj header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFitting(UILayoutFittingCompressedSize) self.tableHeaderView = headerdziałałoby na iOS 10.2
Kesong Xie,
24

Nie udało mi się dodać widoku nagłówka za pomocą ograniczeń (w kodzie). Jeśli podam dla mojego widoku ograniczenie szerokości i / lub wysokości, wyskakuje mi komunikat o treści:

 "terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."

Kiedy dodaję widok w scenorysie do widoku tabeli, nie pokazuje on żadnych ograniczeń i działa dobrze jako widok nagłówka, więc myślę, że umieszczenie widoku nagłówka nie jest wykonywane przy użyciu ograniczeń. W tym względzie nie wygląda jak normalny widok.

Szerokość jest automatycznie szerokością widoku tabeli, jedyne, co musisz ustawić, to wysokość - wartości początkowe są ignorowane, więc nie ma znaczenia, co wstawisz dla nich. Na przykład to zadziałało dobrze (podobnie jak 0,0,0,80 dla prostownika):

UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)];
headerview.backgroundColor = [UIColor yellowColor];
self.tableView.tableHeaderView = headerview;
rdelmar
źródło
Miałem też ten wyjątek, ale dodanie kategorii do UITableView naprawiło to, znalazłem to w tej odpowiedzi: stackoverflow.com/questions/12610783/ ...
ItsASecret
Nadal spróbuję tego, co sugerujesz, ale jutro rano jest 1:34 idę spać, bardzo dziękuję za poświęcenie czasu na odpowiedź! (Ale naprawdę nie chcę określać wysokości, chciałbym, aby była obliczana na podstawie ograniczeń, które ustawiłem na etykiecie w CustomView)
ItsASecret
Próbowałem i tak, ustawienie ramy działa, ale szukałem sposobu na uniknięcie ustawiania ramy, będę szukał dalej i jeśli nie znajdę nic innego, przyjmuję twoją odpowiedź
ItsASecret
1
Otrzymuję ten wyjątek (obecnie testuję 7.1), jeśli dodany widok nagłówka ma translatesAutoresizingMaskIntoConstraints = NO. Włączenie tłumaczenia zapobiega błędowi - podejrzewam, UITableViewże od wersji 7.1 nie próbuje automatycznie układać widoku nagłówka i chce czegoś z ustawioną ramką.
Benjohn
17

Widziałem tutaj wiele metod, które robią tyle niepotrzebnych rzeczy, ale nie potrzebujesz aż tak dużo, aby użyć automatycznego układu w widoku nagłówka. Musisz tylko stworzyć swój plik xib, umieścić swoje ograniczenia i zainicjować go w ten sposób:

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
    }
Ramon Vasconcelos
źródło
Działało to również dla nas na iOS 11 z dynamicznym nagłówkiem wysokości z etykietami wieloliniowymi.
Ben Scheirman
1
Oczywiście możesz również usunąć opcję flexibleHeight-Autoresize-Option w IB.
d4Rk
Próbowałem ustawić wysokość mojego tableFooterView (przez xib / nib) i nie udało mi się ustawić ramki, wysokości, układuIfNeeded () itp. Ale to rozwiązanie ostatecznie pozwoliło mi to ustawić.
vikzilla
Nie zapomnij ustawić ograniczenia wysokości na cały widok w pliku xib.
Denis Kutlubaev
6

Innym rozwiązaniem jest wysłanie tworzenia widoku nagłówka do następnego wywołania głównego wątku:

- (void)viewDidLoad {
    [super viewDidLoad];

    // ....

    dispatch_async(dispatch_get_main_queue(), ^{
        _profileView = [[MyView alloc] initWithNib:@"MyView.xib"];
        self.tableView.tableHeaderView = self.profileView;
    });
}

Uwaga: naprawia błąd, gdy załadowany widok ma stałą wysokość. Nie próbowałem, gdy wysokość nagłówka zależy tylko od jego zawartości.

EDYTOWAĆ :

Możesz znaleźć czystsze rozwiązanie tego problemu, implementując tę funkcję i wywołując jąviewDidLayoutSubviews

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    [self sizeHeaderToFit];
}
Jaskółka oknówka
źródło
1
@TussLaszlo tableHeaderViewsą trochę buggy z automatycznym układem. Istnieje kilka obejść, takich jak ten. Ale ponieważ to pisałem, znalazłem lepsze i czystsze rozwiązanie tutaj stackoverflow.com/a/21099430/127493 przez wywołanie - (void)sizeHeaderToFitinviewDidLayoutSubviews
Martin
4

Kod:

  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
          }
  }
Phil
źródło
To działa. Nadaj odpowiednie ograniczenia autoukładu wszystkim widokom podrzędnym widoku nagłówka tabeli. Jeśli przegapisz jedno ograniczenie, to nie zadziała.
abhimuralidharan
4

Rozszerzono to rozwiązanie http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/ dla widoku stopki tabeli:

@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
źródło
Spędziłem wczoraj dzień, próbując zmusić tableHeader do poprawnej automatycznej zmiany rozmiaru / układu. To rozwiązanie działa u mnie. Wielkie dzięki.
docchang
Cześć! Czy mógłbyś wyjaśnić self.tableFooterView.transformczęść? Dlaczego jest to konieczne?
mrvn
@mrvn transformacja służy do przenoszenia stopki na dół tableView.
k06a
3

Możesz uzyskać autolayout, aby podać rozmiar, używając metody systemLayoutSizeFittingSize .

Możesz następnie użyć tego do stworzenia ramki dla swojej aplikacji. Ta technika działa zawsze, gdy potrzebujesz znać rozmiar widoku, który wewnętrznie używa autoukładu.

Wygląda jak kod w języku 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

Lub w 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

Należy również zauważyć, że w tym konkretnym przypadku przesłanianie wymagaConstraintBasedLayout w podklasie, powoduje wykonanie przebiegu układu, jednak wyniki tego przebiegu układu są ignorowane, a ramka systemowa jest ustawiona na szerokość tableView i 0 wysokość.

Jonathan
źródło
3

Poniższe zadziałały dla mnie.

  1. Użyj zwykłego starego UIViewjako widoku nagłówka.
  2. Dodaj do tego podziały UIView
  3. Użyj automatycznego układu na widokach podrzędnych

Główną korzyścią, jaką widzę, jest ograniczenie obliczeń ramek. Apple powinno naprawdę zaktualizować UITableViewAPI, aby było to łatwiejsze.

Przykład użycia 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))
}
David Nix
źródło
3

Dziwne rzeczy się zdarzają. systemLayoutSizeFittingSize działa świetnie na iOS9, ale w moim przypadku nie na iOS 8. Więc ten problem rozwiązuje się dość łatwo. Po prostu uzyskaj link do dolnego widoku w nagłówku i w viewDidLayoutSubviews po super wywołanie granic widoku nagłówka aktualizacji, wstawiając wysokość jako CGRectGetMaxY (yourview.frame) + dopełnienie

UPD: Najłatwiejsze rozwiązanie wszechczasów : w widoku nagłówka umieść widok podrzędny i przypnij go do lewej , prawej , góry . W tym widoku podrzędnym umieść podglądy z automatycznymi ograniczeniami wysokości. Następnie przekaż całą pracę do autoukładu (bez konieczności obliczania)

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

W rezultacie podwidok rozszerza się / kurczy tak, jak powinien, na końcu wywołuje viewDidLayoutSubviews. W tym momencie znamy rzeczywisty rozmiar widoku, więc ustaw wysokość headerView i zaktualizuj ją przez ponowne przypisanie. Działa jak marzenie!

Działa również w widoku stopki.

HotJard
źródło
1
To pętle dla mnie w iOS 10.
Simon,
3

Zaktualizowano dla 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
        }
    }
}
Caleb Friden
źródło
2

możesz dodać górne + poziome ograniczenie lokalizacji między nagłówkiem a widokiem tabeli, aby umieścić je poprawnie (jeśli sam nagłówek zawiera wszystkie niezbędne wewnętrzne ograniczenia układu, aby mieć poprawną ramkę)

w metodzie 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
GreatWiz
źródło
1

Mój widok nagłówka tabeli jest podklasą UIView - utworzyłem pojedynczy contentView UIView w inicjatorze, z jego granicami takimi samymi jak ramka widoku nagłówka tabeli i dodałem wszystkie moje obiekty jako podwidok tego.

Następnie dodaj ograniczenia dla obiektów w layoutSubviewsmetodzie widoku nagłówka tabeli, a nie w inicjatorze. To rozwiązało awarię.

- (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
}
Ryan
źródło
1

Mój układ AutoLayout działa bardzo dobrze:

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
źródło
Nie zrobiłem tego dokładnie, ale dałeś mi dobry pomysł - usunięcie headerView, ponowne ustawienie jego ramki i dodanie go z powrotem.
dinesharjani
1

W większości przypadków najlepszym rozwiązaniem jest po prostu nie walka z frameworkiem i korzystanie z autorezowania masek:

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

Używając masek autorezacji, mówisz frameworkowi, jak twój widok powinien zmienić swój rozmiar, gdy nadzór zmieni swój rozmiar. Ale ta zmiana jest oparta na ustawionej początkowej klatce.

pfandrade
źródło
0

Wiem, że to stary post, ale po przejrzeniu wszystkich postów SO dotyczących tego i spędzeniu całego popołudnia bawiąc się tym, w końcu wpadłem na czyste, a jednocześnie bardzo proste rozwiązanie

Przede wszystkim moja hierarchia widoków wygląda następująco:

  1. Widok tabeli
    1. Widok tableHeaderView
      1. Widok z gniazdem o nazwie headerView

Teraz w widoku (nr 3) ustawiłem wszystkie ograniczenia, tak jak zwykle, włączając dolną przestrzeń do kontenera. Spowoduje to, że kontener (tj. 3.View, tj. HeaderView) zmieni rozmiar na siebie w oparciu o jego podziały i ich ograniczenia.

Następnie ustawiłem ograniczenia między 3. Viewi 2. Viewdo tych:

  1. Górna przestrzeń do kontenera: 0
  2. Wiodąca przestrzeń do kontenera: 0
  3. Końcowa przestrzeń do kontenera: 0

Zauważ, że celowo pomijam dolną przestrzeń.

Gdy wszystko to zostanie zrobione w scenorysie, wszystko, co pozostaje do zrobienia, to wkleić te trzy linie kodów:

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;
}
Marc-Alexandre Bérubé
źródło
0

Porady: Jeśli używasz metody setAndLayoutTableHeaderView, powinieneś zaktualizować ramkę subviews, więc w tej sytuacji preferowanyMaxLayoutWidth UILabel powinien wywołać przed wywołaniem systemLayoutSizeFittingSize, nie wywołuj w layoutSubview.

pokaż kod

user1511613
źródło
0

Podziel się moim podejściem.

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

Stosowanie.

[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));
    }];
  }];
Vincent Sit
źródło
0

W moim przypadku metoda z systemLayoutSizeFittingSize z jakiegoś powodu nie działała. U mnie zadziałała modyfikacja rozwiązania opublikowana przez HotJarda (jego oryginalne rozwiązanie również nie działało w moim przypadku na iOS 8). Musiałem w widoku nagłówka umieścić widok podrzędny i przypiąć go do lewej, prawej, góry (nie przypinaj do dołu). Umieść wszystko za pomocą autolayout w tym podglądzie i w kodzie zrób to:

- (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;
}
Leszek Szary
źródło
0

Stary post. Ale dobry post. Oto moje 2 centy.

Po pierwsze, upewnij się, że widok nagłówka ma swoje ograniczenia tak ułożone, aby mógł obsługiwać własny wewnętrzny rozmiar zawartości. Następnie wykonaj następujące czynności.

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

źródło
0

Udało mi się to osiągnąć w następujący sposób (działa to w przypadku stopki w ten sam sposób).

Najpierw będziesz potrzebować małego UITableViewrozszerzenia:

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

W Twoim widoku implementacja klasy kontrolera:

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

Uwagi dotyczące nagłówka UIView implementacji widoku podrzędnego :

  1. Musisz mieć 100% pewności, że Twój widok nagłówka ma poprawną konfigurację automatycznego układu. Poleciłbym zacząć od prostego widoku nagłówka z jednym ograniczeniem wysokości i wypróbować powyższą konfigurację.

  2. Zastąp requiresConstraintBasedLayouti zwróć true:

.

class MyHeaderView: UIView {
   // ...
   override static var requiresConstraintBasedLayout : Bool {
       return true
   }
   // ...
}
Yevhen Dubinin
źródło
0

Dla użytkowników platformy 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;
}

Zakładając, że nazwałeś widok nagłówka widoku tabeli jako TableviewHeader

Gustavo Baiocchi Costa
źródło
0

Oto, jak możesz to zrobić w swoim 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
źródło
0

Każde oparte na ograniczeniach UIViewmoże być dobre tableHeaderView.

Należy ustawić tableFooterViewprzed, a następnie nałożyć dodatkowe ograniczenie końcowe na tableFooterViewi 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;

}

Wszystkie szczegóły i fragmenty kodu można znaleźć tutaj

malex
źródło
0

Znalazłem obejście. Zawiń widok nagłówka autolayout wrriten xib w pustym opakowaniu uiview i przypisz widok nagłówka do właściwości 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
źródło
0

Oto, co działa dla UITableViewController w iOS 12,

Przeciągnij UIView do TableView powyżej wszystkich prototypowych komórek dla nagłówka i poniżej wszystkich prototypowych komórek dla stopki. W razie potrzeby skonfiguruj nagłówek i stopkę. Ustaw wszystkie wymagane ograniczenia.

Teraz użyj następujących metod rozszerzających

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

i wywołaj go w ViewDidLayoutSubviews podklasy UITableViewController

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    TableView.MakeHeaderAutoDimension();
    TableView.MakeFooterAutoDimension();
}
Pijany tatuś
źródło
0

Napotkałem problem z uzyskaniem szerokości 375pt, jedynym sposobem, który zadziałał dla mnie, jest przekazanie tableView w celu uzyskania prawidłowej szerokości. Wolałem też AutoLayout zamiast ustawiania rozmiaru ramki.

Oto wersja, która działa dla mnie:

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

Wersja Swift (zmodyfikowana na podstawie odpowiedzi @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
    }
}
Baron Ch'ng
źródło
0

Moim rozwiązaniem jest stworzenie takiej nowej klasy.

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

}

Aby go użyć, po prostu dodaj wszystkie podglądy do instancji programu BaseTableHeaderViewi dołącz je do widoku tabeli.

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

Automatycznie zmieni rozmiar na podstawie swoich ograniczeń.

Hesse Huang
źródło
-1

Zaakceptowana odpowiedź jest przydatna tylko w przypadku tabel z jedną sekcją. W przypadku wielu sekcji UITableViewpo prostu upewnij się, że nagłówek dziedziczy poUITableViewHeaderFooterView a wszystko będzie dobrze.

Alternatywnie, po prostu umieść bieżący nagłówek w pliku contentViewa UITableViewHeaderFooterView. Dokładnie tak, jak UITableViewCelldziała.

redent84
źródło
9
pytanie tableHeaderViewnie dotyczy nagłówka sekcji.
Rpranata