Autolayout ile tableHeaderView (UITableView) yüksekliğini nasıl ayarlarım?


86

Son 3 ya da 4 saattir bununla kafamı duvara çarpıyorum ve anlayamıyorum. İçinde tam ekran UITableView bulunan bir UIViewController'ım var (ekranda başka şeyler var, bu yüzden bir UITableViewController kullanamıyorum) ve tableHeaderView'umu otomatik düzen ile yeniden boyutlandırmak istiyorum. Söylemeye gerek yok, işbirliği yapmıyor.

Aşağıdaki ekran görüntüsüne bakın.

görüntü açıklamasını buraya girin

OverviewLabel (ör. "Genel bakış bilgilerini burada listeleyin." Metni) dinamik içeriğe sahip olduğundan, onu yeniden boyutlandırmak için otomatik düzen kullanıyorum ve gözetim altında. Hiearchy'de Paralax Tablo Görünümünün hemen altında bulunan tableHeaderView dışında her şey güzel bir şekilde yeniden boyutlandırılıyor.

Bu başlık görünümünü yeniden boyutlandırmanın bulduğum tek yolu, aşağıdaki kodla programlı olarak:

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

Bu durumda, headerFrameHeight, tableViewHeader yüksekliğinin aşağıdaki gibi manuel olarak hesaplanmasıdır (innerHeaderView, beyaz alandır veya ikinci "View", headerView ise tableHeaderView'dur) :

CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y;
CGRect overviewSize = [self.overviewLabel.text
                       boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
                       options:NSStringDrawingUsesLineFragmentOrigin
                       attributes:@{NSFontAttributeName: self.overviewLabel.font}
                       context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there's no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);

Manuel hesaplama işe yarıyor, ancak ileride işler değiştiğinde hantal ve hataya meyilli. Yapabilmek istediğim şey, başka herhangi bir yerde yapabileceğiniz gibi tableHeaderView'ın sağlanan kısıtlamalara göre otomatik olarak yeniden boyutlandırılmasıdır. Ama hayatım boyunca bunu çözemiyorum.

SO'da bununla ilgili birkaç gönderi var, ancak hiçbiri net değil ve beni daha fazla karıştırdı. İşte bunlardan birkaçı:

TranslatesAutoresizingMaskIntoConstraints özelliğini NO olarak değiştirmek gerçekten mantıklı değil, çünkü bu sadece benim için hatalara neden oluyor ve kavramsal olarak bir anlam ifade etmiyor.

Herhangi bir yardım gerçekten takdir edilecektir!

DÜZENLEME 1: TomSwift'in önerisi sayesinde bunu çözebildim. Genel bakış yüksekliğini manuel olarak hesaplamak yerine, benim için aşağıdaki gibi hesaplattırabilir ve ardından tableHeaderView'ı daha önce olduğu gibi yeniden ayarlayabilirim.

[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView.

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

Düzenleme 2: Diğerlerinin de belirttiği gibi, Düzenleme 1'de yayınlanan çözüm, viewDidLoad'da çalışmıyor gibi görünüyor. Ancak, viewWillLayoutSubviews içinde çalışıyor gibi görünüyor. Aşağıdaki örnek kod:

// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [_headerWrapper setNeedsLayout];
    [_headerWrapper layoutIfNeeded];
    CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = _headerWrapper.frame;
    headerFrame.size.height = height;
    _headerWrapper.frame = headerFrame;
    _tableView.tableHeaderView = _headerWrapper;
}

2
setTableHeaderViewXcode6 üzerinde çalışmaz. Sorun, hücrelerin tableHeaderView tarafından çakışmasıdır. Ancak, Xcode5
DawnSong

@DawnSong, cevabı güncelleyebilmem için Xcode6 için bir çözüm biliyor mu?
Andrew Cross

1
Bir çok denemeden sonra tableHeaderView yerine UITableViewCell kullanıyorum ve çalışıyor.
DawnSong

Ben de kafamı kırdım!
Akshit Zaveri

Gerçek tam otomatik
düzen

Yanıtlar:


35

UIView systemLayoutSizeFittingSize:Başlık görünümünüzün minimum sınırlayıcı boyutunu elde etmek için yöntemi kullanmanız gerekir .

Bu soru / cevap bölümünde bu API'yi kullanma hakkında daha fazla tartışma sunuyorum:

Otomatik düzen ile tüm alt görünümleri sığdırmak için denetleme nasıl yeniden boyutlandırılır?


Yükseklik = 0 ile geri geliyor, ancak bu bir UITableViewCell olmadığı için doğru yapılandırdığımdan% 100 emin değilim, bu yüzden "systemLayoutSizeFittingSize" üzerinde "systemLayoutSizeFittingSize" çağıracak contentView yok. Denediğim şey: [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat yüksekliği = [self.headerView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = yükseklik; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView: self.headerView]; Ayrıca PreferMax ... seçeneğinin geçerli olduğunu doğruladım.
Andrew Cross

Aha! Bunu anladım, gerçek dinamik içeriğe sahip olduğundan, self.headerView yerine [self.innerHeaderView systemLayoutSizeFittingSize ...] yapmam gerekiyordu. Çok teşekkürler!
Andrew Cross

Çalışıyor ancak uygulamamda küçük bir yükseklik uyuşmazlığı var. Muhtemelen etiketlerim bir atıfta bulunulan metin ve Dinamik Tür kullandığından.
biyografi

23

Bununla gerçekten savaştım ve kurulumu görünüme sokmak, çerçeve viewDidLoad'da ayarlanmadığı için DidLoad benim için işe yaramadı, ayrıca başlığın kapsüllenmiş otomatik düzen yüksekliğinin 0'a düşürüldüğü tonlarca dağınık uyarı ile sonuçlandım. Bir Form sunumunda tableView sunarken yalnızca iPad'deki sorunu fark ettim.

Benim için sorunu çözen şey, tableViewHeader'ı viewDidLoad yerine viewWillLayoutSubviews içinde ayarlamaktı.

func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        if tableView.tableViewHeaderView == nil {
            let header: MyHeaderView = MyHeaderView.createHeaderView()
            header.setNeedsUpdateConstraints()
            header.updateConstraintsIfNeeded()
            header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
            var newFrame = header.frame
            header.setNeedsLayout()
            header.layoutIfNeeded()
            let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
            newFrame.size.height = newSize.height
            header.frame = newFrame
            self.tableView.tableHeaderView = header
        }
    }

minimum kodla güncellenmiş çözüm için şunu deneyin: stackoverflow.com/a/63594053/3933302
Sourabh Sharma

23

Tablo başlıklarını animasyonlu ve animasyonsuz yeniden boyutlandırmak için otomatik düzeni kullanmanın zarif bir yolunu buldum.

Bunu View Controller'ınıza eklemeniz yeterlidir.

func sizeHeaderToFit(tableView: UITableView) {
    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
    }
}

Dinamik olarak değişen bir etikete göre yeniden boyutlandırmak için:

@IBAction func addMoreText(sender: AnyObject) {
    self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}

override func viewDidLayoutSubviews() {
    // viewDidLayoutSubviews is called when labels change.
    super.viewDidLayoutSubviews()
    sizeHeaderToFit(tableView)
}

Bir kısıtlamadaki değişikliklere göre yeniden boyutlandırmayı canlandırmak için:

@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!

@IBAction func makeThisTaller(sender: AnyObject) {

    UIView.animateWithDuration(0.3) {
        self.tableView.beginUpdates()
        self.makeThisTallerHeight.constant += 20
        self.sizeHeaderToFit(self.tableView)
        self.tableView.endUpdates()
    }
}

Bunu eylem halinde görmek için AutoResizingHeader projesine bakın. https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader


Hey p-sun. Harika örnek için teşekkürler. TableViewHeader ile sorunum var gibi görünüyor. Sizin örneğinizdeki gibi aynı kısıtlamalara sahip olmaya çalıştım ama çalışmadım. Yüksekliği artırmak istediğim etikete tam olarak nasıl kısıtlamalar eklemeliyim? Herhangi bir öneriniz için teşekkürler
Bonnke

1
Emin size uzun yapmak istediğiniz etiketi kanca olun @IBOutlet makeThisTallerve @IBAction fun makeThisTallerörnekteki gibi. Ayrıca, etiketinizin tüm kenarlarını tableViewHeader ile sınırlayın (örneğin, üst, alt, sol ve sağ).
p-sun

Çok teşekkürler! Sonunda şu satırı ekleyerek çözüldü: lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthburada etiket, boyutunu artırmak istediğim şeydir. Teşekkürler !
Bonnke

Teşekkürler! Bunu bir ViewController yerine bir View içinde yapıyorsanız, viewDidLayoutSubviews'i geçersiz kılmak yerine layoutSubviews'ları geçersiz kılabilirsiniz
Andy Weinstein

4

Bu çözüm, tableHeaderView'ı yeniden boyutlandırır ve viewDidLayoutSubviews()burada diğer yanıtlardan bazılarıyla sahip olduğum yöntemde sonsuz döngüyü önler :

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var headerFrame = headerView.frame

        // comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}

Bu gönderiye de bakın: https://stackoverflow.com/a/34689293/1245231


1
Ben de bu yöntemi kullanıyorum. Bunun en iyisi olduğunu düşünün çünkü kısıtlamalarla uğraşmak yok. Aynı zamanda çok kompakt.
biyografi

3

SystemLayoutSizeFittingSize kullanan çözümünüz: başlık görünümü her görünüm görünümünde yalnızca bir kez güncellenirse çalışır. Benim durumumda, başlık görünümü, durum değişikliklerini yansıtacak şekilde birkaç kez güncellendi. Ancak systemLayoutSizeFittingSize: her zaman aynı boyutu bildirdi. Yani, ilk güncellemeye karşılık gelen boyut.

SystemLayoutSizeFittingSize almak için: Her güncellemeden sonra doğru boyutu döndürmek için, önce tablo başlığı görünümünü güncellemeden ve yeniden eklemeden önce kaldırmalıydım:

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];

2

Bu benim için ios10 ve Xcode 8'de çalıştı

func layoutTableHeaderView() {

    guard let headerView = tableView.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false

    let headerWidth = headerView.bounds.size.width;
    let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])

    headerView.addConstraints(temporaryWidthConstraints)

    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()

    let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
    let height = headerSize.height
    var frame = headerView.frame

    frame.size.height = height
    headerView.frame = frame

    self.tableView.tableHeaderView = headerView

    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true

}

2

Hem üstbilgi görünümü hem de altbilgi için çalışır, yalnızca üstbilgiyi altbilgi ile değiştirin

func sizeHeaderToFit() {
    if let headerView = tableView.tableHeaderView {

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame

        tableView.tableHeaderView = headerView
    }
}

1

İOS 12 ve üzeri için, aşağıdaki adımlar otomatik düzenlemenin hem tablonuzda hem de başlığınızda düzgün çalışmasını sağlayacaktır.

  1. Önce tableView'ınızı, ardından başlığı oluşturun.
  2. Başlık oluşturma kodunuzun sonunda şu numarayı arayın:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. Oryantasyon değişikliğinden sonra, mizanpaj için başlığı tekrar işaretleyin ve tabloyu yeniden yükleyin; bunun, oryantasyon değişikliği bildirildikten sonra biraz olması gerekir:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{

  [tableV.tableHeaderView setNeedsLayout];
  [tableV.tableHeaderView layoutIfNeeded];
  [tableV reloadData];});

0

Benim durumumda viewDidLayoutSubviewsdaha iyi çalıştı. viewWillLayoutSubviewsa'nın beyaz çizgilerinin tableViewgörünmesine neden olur . Ayrıca headerViewnesnemin zaten var olup olmadığını kontrol etmeyi ekledim .

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if ( ! self.userHeaderView ) {
        // Setup HeaderView
        self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
        [self.userHeaderView setNeedsLayout];
        [self.userHeaderView layoutIfNeeded];
        CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.userHeaderView.frame;
        headerFrame.size.height = height;
        self.userHeaderView.frame = headerFrame;
        self.tableView.tableHeaderView = self.userHeaderView;

        // Update HeaderView with data
        [self.userHeaderView updateWithProfileData];
    }
}

0

UIViewHerhangi bir AL iç alt görünüm yapısı ile genel Otomatik Yerleşim tabanlı kullanmak oldukça mümkündür tableHeaderView.

İhtiyaç duyulan tek şey önceden basitleştirmek tableFooterView!

self.headerViewBazı kısıtlamalara dayalı olalım UIView.

- (void)viewDidLoad {

    ........................

    self.tableView.tableFooterView = [UIView new];

    [self.headerView layoutIfNeeded]; // to set initial size

    self.tableView.tableHeaderView = self.headerView;

    [self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES;
    [self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
    [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES;

    // and the key constraint
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}

Eğer self.headerViewUI altında değişiklikler yüksekliği bir rotasyona biri uygulamak zorunda

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // needed to resize header height
        self.tableView.tableHeaderView = self.headerView;
    } completion: NULL];
}

Bu amaçla ObjC kategorisi kullanılabilir

@interface UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView;
@end

@implementation UITableView (AMHeaderView)

- (void)am_insertHeaderView:(UIView *)headerView {

    NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");

    [headerView layoutIfNeeded];

    self.tableHeaderView = headerView;

    [self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES;
    [self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
    [self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES;

    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
}
@end

Ve ayrıca Swift uzantısı

extension UITableView {

    func am_insertHeaderView2(_ headerView: UIView) {

        assert(tableFooterView != nil, "Need to define tableFooterView first!")

        headerView.layoutIfNeeded()

        tableHeaderView = headerView

        leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true

        tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
    }
}


0

Bu gönderiden kopyalandı

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    if let headerView = tableView.tableHeaderView {

        let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        var headerFrame = headerView.frame
        
        //Comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.