UITableView öğesini içeriğe sığacak şekilde yeniden boyutlandırma


170

Bir soru UILabelve görüntülenen çoktan seçmeli cevaplar UITableView, her satır çoktan seçmeli gösteren bir uygulama oluşturacağım. Sorular ve cevaplar değişecektir, bu yüzden UITableViewbunun dinamik olması gerekir .

sizeToFitMasa için etrafta bir çalışma bulmak istiyorum . Tablonun çerçevesinin tüm içeriğinin yüksekliğine ayarlandığı yer.

Herkes bunu nasıl başarabileceğim konusunda tavsiyede bulunabilir mi?


OSX'te benzer bir şey yapmak isteyen herkes için şu soruya bakın: stackoverflow.com/questions/11322139/…
Michael Teper

1
@MiMo merhaba, lütfen "sizeToFit" yaklaşımı ile neyin yanlış olduğunu açıklayabilir misiniz? :)
iamdavidlam

“Etrafında bir" sizeToFit "bulmak istiyorum" . UIView'in - sizeToFityöntemini denediniz mi denediniz mi?
Slipp D.Thompson

Yanıtlar:


155

Aslında cevabı kendim buldum.

Sadece yeni oluşturmak CGRectiçin tableView.framebirlikte heightbirtable.contentSize.height

Yani yüksekliğini ayarlar UITableViewiçin heightiçeriğinin. Kod kullanıcı arayüzünü değiştirdiğinden, ana iş parçacığında çalıştırmayı unutmayın:

dispatch_async(dispatch_get_main_queue(), ^{
        //This code will run in the main thread:
        CGRect frame = self.tableView.frame;
        frame.size.height = self.tableView.contentSize.height;
        self.tableView.frame = frame;
    });

3
Bu yöntemle sorun yaşamak mümkündür. Büyük bir listeniz varsa, çerçevenin yüksekliğinin bazı hücrelerinizi kesmesi mümkündür. Bu, zıplatma özelliğini etkinleştirdiyseniz ve bazı hücrelerin görünümden geri döndüğünü görmek için aşağıya kaydırdığınızda görebilirsiniz.
whyoz

Yüksekliği tam olarak nerede belirttiniz? Veriyi yeniden yüklemek ve sonradan yeniden boyutlandırmak için bir çağrı yaptınız mı?
James

27
Bu kodu koymak için en iyi yer neresi? Ben viewDidLoad denedim ve tableview delege yöntemleri henüz ateş yoktu çünkü muhtemelen işe yaramadı. Hangi delege yöntemi en iyisidir?
Kyle Clegg

4
Bunu viewDidAppear yaptım ve işe yaramış gibi görünüyor.
Tablo görünümünün

2
Ben viewDidAppear tüm ilk tableView cellForRowAtIndexPath aramaları bitirdikten sonra ne istediğiniz şeyler koymak için harika bir yer olduğunu saptadık
rigdonmr

121

KVO, DispatchQueue veya ayar kısıtlamaları olmadan Swift 5 ve 4.2 çözümü.

Bu çözüm Gulz'un cevabına dayanıyor .

1) Aşağıdakilerin bir alt sınıfını oluşturun UITableView:

import UIKit

final class ContentSizedTableView: UITableView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

2) UITableViewDüzeninize bir ekleyin ve her tarafta kısıtlamalar ayarlayın. Sınıfını olarak ayarlayın ContentSizedTableView.

3) Bazı hatalar görmelisiniz, çünkü Storyboard alt sınıfımızı dikkate almıyor ' intrinsicContentSize. Boyut denetçisini açıp intrinsicContentSize değerini bir yer tutucu değerine geçersiz kılarak bunu düzeltin. Bu, tasarım zamanı için bir geçersiz kılmadır. Çalışma zamanında ContentSizedTableViewsınıfımızdaki geçersiz kılmayı kullanır


Güncelleme: Swift 4.2 için kod değiştirildi. Önceki bir sürümü kullanıyorsanız, UIViewNoIntrinsicMetricyerineUIView.noIntrinsicMetric


1
Çok iyi bir cevap, benim için çalıştı, teşekkürler. Yine de bir şey - benim durumumda film şeridinde tableViewolmak için sınıfını değiştirmem gerekiyordu IntrinsicTableView. Tablo görünümümü bir başkasının içine yerleştirmem gerekmedi UIView.
dvp.petrov

Evet, haklısın. Cevabımı güncelledim. Adım 2, dediğin gibi okunabilir. Tabii ki ayarlamak anlamına tableViewiçin IntrinsicTableView. Ayrıca ekstra bir sargı UIViewgerekli değildir. Tabii ki varolan herhangi bir ana görünümü kullanabilirsiniz :)
fl034

1
Swift 4 Bu benim için iyi çalıştıtableView.sizeToFit()
Souf ROCHDI

İçeriğiniz durağansa, bu iyi bir çözüm olabilir. Benim yaklaşımımla, hangi görünümlere sizeToFit()ihtiyaç duyulduğunu düşünmeden dinamik görünüm içeren bir tablo görünümünü diğer görünümlerde gömmek ve her zaman tam yükseklikte tutmak için Otomatik Düzen'i kullanabilirsiniz
fl034 21:18

2
Harika, teşekkürler!. Sonunda tableview için ebeveyn ve şal içeriği maç ...
Amber K

79

Hızlı Çözüm

Bu adımları takip et:

1- Tablo için yükseklik sınırlamasını film şeridinden ayarlayın.

2- Yükseklik sınırlamasını film şeridinden sürükleyin ve görünüm denetleyicisi dosyasında bunun için @IBOutlet oluşturun.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Sonra bu kodu kullanarak tablonun yüksekliğini dinamik olarak değiştirebilirsiniz:

override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.tableHeight?.constant = self.table.contentSize.height
}

Güncelleme

Son satır sizin için kesilirse, willDisplay hücre işlevinde viewWillLayoutSubviews () öğesini çağırmayı deneyin:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}

1
Benim için çalıştı !! thnx
Pushpa Y

5
Bu çözüm benim için Xcode 8.3'deki son satırı her zaman kesti.
Jen C

@Cec C: Benim için de değil
KMC

Benim için de çalıştı !!
Antony Raphel

1
Bunu cellForRowAtIndexPath: yöntemine koymak zorunda kaldım. ViewWillLayoutSubviews yöntemine koyarsam, hesaplanan contentSize yüksekliği, gerçek içerik yüksekliğini değil, tahmini yüksekliği temel alır.
Manu Mateos

21

Bunu iOS 7'de denedim ve benim için çalıştı

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}

3
UITableView'ınız dinamikse (yani bilgi anında ve ondan anında yüklenir), bunu viewDidLoad içine değil başka bir yere koymanız gerekir. Benim için cellForRowAtIndexPath içine koydum. Ayrıca eski "özellik tableView bulunamadı" hatası önlemek için "tableView" benim IBOutlet özelliği adına değiştirmek zorunda kaldı.
jeremytripp

thx, popover içinde tableview yeniden boyutlandırma ile ilgili sorun vardı, o çalıştı
Zaporozhchenko Oleksandr

19

Tablo görünümünde contentSize özelliği için bir gözlemci ekleyin ve çerçeve boyutunu buna göre ayarlayın

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

sonra geri aramada:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
         CGRect frame = your_tableview.frame;
         frame.size = your_tableview.contentSize;
         your_tableview.frame = frame;
    }

Umarım bu size yardımcı olacaktır.


2
Bir şey daha eklemelisin. Tablo görünümü için gözlemciyi kaldırmalısınız. Çünkü hücre yeniden yerleştirilirse çökecektir.
Mahesh Agrawal

12

Kaydırma görünümü içinde bir tablo görünümü vardı ve tableView yüksekliğini hesaplamak ve buna göre yeniden boyutlandırmak zorunda kaldı. Bunlar attığım adımlar:

0) scrollView için bir UIView ekleyin (muhtemelen bu adım olmadan çalışacaktır, ancak olası çakışmalardan kaçınmak için yaptım) - bu tablo görünümünüz için bir includer görünümü olacaktır. Bu adımı uygularsanız, görünüm sınırlarını doğrudan tablo görünümünün kenarlarına ayarlayın.

1) UITableView'in bir alt sınıfını oluşturun:

class IntrinsicTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        self.layoutIfNeeded()
        return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
    }

}

2) Storyboard'daki bir tablo görünümü sınıfını IntrinsicTableView olarak ayarlayın: ekran görüntüsü: http://joxi.ru/a2XEENpsyBWq0A

3) heightConstraint'i tablo görünümünüze ayarlayın

4) Tablonuzun IBoutlet'ini ViewController'ınıza sürükleyin

5) tablonuzun yükseklik kısıtlamasının IBoutlet'ini ViewController'ınıza sürükleyin

6) bu yöntemi ViewController'ınıza ekleyin:

override func viewWillLayoutSubviews() {
        super.updateViewConstraints()
        self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height
    }

Bu yardımcı olur umarım


Alt sınıfınıza dayalı olan, ancak yükseklik sınırlaması ayarına gerek duymayan cevabımı görün.
fl034

@ fl034 bağlantı sağlar?
Gulz

@ fl034 Öğeleriniz Kaydırma Görünümü'ne katıldığında kısıtlamaları kullanmaktan kaçınabileceğinizi sanmıyorum) Benim durumum ScrollView
Gulz

Ayrıca bir kaydırma görünümü içinde benim tablo görünümü var ve mükemmel çalışıyor :)
fl034

iOS 13 / Xcode 11'de benim için çalışıyor 3 gün boyunca acı çekiyorsunuz efendim, teşekkür ederim
Mehdi S.

8

Tablo görünümünün içerik boyutu değişikliklerini kendiniz izlemek istemiyorsanız, bu alt sınıfı yararlı bulabilirsiniz.

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
    func tableViewDidUpdateContentSize(_ tableView: UITableView)
}

class ContentFittingTableView: UITableView {

    override var contentSize: CGSize {
        didSet {
            if !constraints.isEmpty {
                invalidateIntrinsicContentSize()
            } else {
                sizeToFit()
            }

            if contentSize != oldValue {
                if let delegate = delegate as? ContentFittingTableViewDelegate {
                    delegate.tableViewDidUpdateContentSize(self)
                }
            }
        }
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return contentSize
    }
}

1
Hızlı 3 için, intrinsicContentSize bir var, gibioverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod

benim için çalışmıyor. hala masanın altına saklanıyor
Gulz


4

Swift 3, iOS 10.3

Çözüm 1: Hemen koymakself.tableview.sizeToFit()içindecellForRowAt indexPathişlevi. Tablo görünümü yüksekliğini ihtiyacınızdan daha yükseğe ayarladığınızdan emin olun. Tablo görünümünün altında görünümleriniz yoksa bu iyi bir çözümdür. Ancak, varsa, alt tableview kısıtlaması güncellenmeyecek (2. çözümü bulduğum için düzeltmeye çalışmadım)

Misal:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
        cell.configureCell(data: testArray[indexPath.row])
        self.postsTableView.sizeToFit()
        return cell
    }

    return UITableViewCell()
}

Çözüm 2: Film şeridinde tablo görünümü yükseklik sınırlamasını ayarlayın ve ViewController'a sürükleyin. Hücrenizin ortalama yüksekliğini biliyorsanız ve dizinizin kaç öğe içerdiğini biliyorsanız, şöyle bir şey yapabilirsiniz:

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

*DÜZENLE:

Tüm çözümlerin sonra denedim ve bunların her şey sabitleme, ama tamamen değil, bu tamamen bu sorunu açıklıyor ve düzeltmeler cevaptır.


Bu çok basit bir çözüm, benim için çalıştı, teşekkür ederim @Dorde Nilovic
R. Mohan

1

Mimo'nun yanıtı ve Anooj VM'nin yanıtı da harika, ancak büyük bir listeniz varsa küçük bir sorun var, çerçevenin yüksekliğinin bazı hücrelerinizi kesmesi mümkündür.

Yani. Cevabı biraz değiştirdim:

dispatch_async(dispatch_get_main_queue()) {
    //This code will run in the main thread:
    CGFloat newHeight=self.tableView.contentSize.height;
    CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
    if (newHeight>screenHeightPermissible)
    {
        //so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
        newHeight=screenHeightPermissible;
    }

    CGRect frame = self.tableView.frame;
    frame.size.height = newHeight;
    self.tableView.frame = frame;
}

1

AutoLayout kullanıyorsanız bunu yapmanın çok daha iyi bir yolu vardır: yüksekliği belirleyen kısıtlamayı değiştirin. Sadece tablo içeriğinizin yüksekliğini hesaplayın, ardından kısıtlamayı bulun ve değiştirin. İşte bir örnek (tablonuzun yüksekliğini belirleyen kısıtlamanın aslında "Eşit" ilişkisiyle bir yükseklik kısıtlaması olduğu varsayılarak):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    for constraint in tableView.constraints {
        if constraint.firstItem as? UITableView == tableView {
            if constraint.firstAttribute == .height {
                constraint.constant = tableView.contentSize.height
            }
        }
    }
}

Bu konuda geliştirebilirsiniz. Tablo görünümünüze yükseklik <= 10000 gibi ek bir kısıtlama ekleyin. Bu kısıtlamayı görünüm denetleyicinizin bir özelliği haline getirmek için Interface Builder'dan .h dosyanıza kontrol sürüklemesini kullanın. Ardından, kısıtlamalar ve arama yoluyla yinelemeyi önleyebilirsiniz. Doğrudan ayarlayınmyTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

Bu, benim için yalnızca bir bölümlü bir tablo görünümü olan Otomatik Düzen'i kullanarak çalışır.

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
        tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
        let rows = tableView.numberOfRows(inSection: 0)
        var height = CGFloat(0)
        for n in 0...rows - 1 {
            height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
        }
        return height
    }

Bu işlevi Otomatik Mizanpaj kurarken çağırıyorum (Buradaki örnek SnapKit kullanıyor, ancak fikri anlıyorsunuz):

    let height = getTableViewContentHeight(tableView: myTableView)
    myTableView.snp.makeConstraints {
        ...
        ...
        $0.height.equalTo(height)
    }

UITableView sadece hücrelerin birleşik yüksekliği kadar uzun olmasını istiyorum; Hücreler arasında dolaşıyorum ve hücrelerin toplam yüksekliğini biriktiriyorum. Tablo görünümünün boyutu bu noktada CGRect.zero olduğundan, hücre tarafından tanımlanan Otomatik Düzen kurallarına uymak için sınırları ayarlamam gerekiyor. Boyutu yeterince büyük olması gereken keyfi bir değere ayarladım. Gerçek boyut daha sonra Otomatik Düzen sistemi tarafından hesaplanacaktır.


1

Ben biraz farklı bir şekilde yaptım, Aslında benim TableView scrollview içinde oldu, bu yüzden 0 olarak yükseklik kısıtlaması vermek zorunda kaldı.

Sonra çalışma zamanında aşağıdaki değişiklikleri yaptım,

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            self.viewWillLayoutSubviews()
       }
    
       override func viewWillLayoutSubviews() {
            super.updateViewConstraints()
             DispatchQueue.main.async {
               self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
               self.view.layoutIfNeeded()
          }
       }

0

Anooj VM'nin cevabının bir uzantısı olarak, içerik boyutunu yalnızca değiştiğinde yenilemek için aşağıdakileri öneriyorum .

Bu yaklaşım ayrıca kaydırmayı düzgün bir şekilde devre dışı bırakır ve daha büyük listeler ile döndürmeyi destekler . ContentSize değişiklikleri ana iş parçacığında gönderildiği için dispatch_async öğesine gerek yoktur.

- (void)viewDidLoad {
        [super viewDidLoad];
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; 
}


- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
        CGRect superviewTableFrame  = self.tableView.superview.bounds;
        CGRect tableFrame = self.tableView.frame;
        BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
        tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
        [UIView animateWithDuration:0.3
                                    delay:0
                                    options:UIViewAnimationOptionCurveLinear
                                    animations:^{
                            self.tableView.frame = tableFrame;
        } completion: nil];
        self.tableView.scrollEnabled = shouldScroll;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
        [keyPath isEqualToString:@"contentSize"] &&
        !CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
        [self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
    } 
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    [self resizeTableAccordingToContentSize:self.tableView.contentSize]; }

- (void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentSize"];
}

0

Musa almatri'nin objc versiyonu

(void)viewWillLayoutSubviews
{
    [super updateViewConstraints];
    CGFloat desiredHeight = self.tableView.contentSize.height;
    // clamp desired height, if needed, and, in that case, leave scroll Enabled
    self.tableHeight.constant = desiredHeight;
    self.tableView.scrollEnabled = NO;
}

0

Bu Özel'i deneyebilirsiniz AGTableView

TableView Yükseklik Kısıtlaması Ayarlamak için Film şeridini veya programlı olarak kullanma. (Bu sınıf otomatik olarak bir yükseklik sınırlaması getirir ve içerik görüntüleme yüksekliğini, görüntülenebilir görüntüleme yüksekliğinize ayarlar).

class AGTableView: UITableView {

    fileprivate var heightConstraint: NSLayoutConstraint!

    override init(frame: CGRect, style: UITableViewStyle) {
        super.init(frame: frame, style: style)
        self.associateConstraints()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }

    override open func layoutSubviews() {
        super.layoutSubviews()

        if self.heightConstraint != nil {
            self.heightConstraint.constant = self.contentSize.height
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint to Resizing UITableView to fit content")
        }
    }

    func associateConstraints() {
        // iterate through height constraints and identify

        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

Not Yükseklik ayarlamak için herhangi bir sorun varsa yourTableView.layoutSubviews().


0

Cevabını dayanarak fl034 . Ancak Xamarin.iOS kullanıcıları için:

    [Register("ContentSizedTableView")]
    public class ContentSizedTableView : UITableView
    {
        public ContentSizedTableView(IntPtr handle) : base(handle)
        {
        }

        public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
        public override CGSize IntrinsicContentSize
        {
            get
            {
                this.LayoutIfNeeded();
                return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
            }
        }
    }

0

Swift 5 uygulaması, tableView öğesinin yükseklik sınırlamasını içeriğinin boyutuna ayarlamaktır ( contentSize.height). Bu yöntem, otomatik düzen kullandığınızı varsayar. Bu kod cellForRowAttableView yönteminin içine yerleştirilmelidir .

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true

-1

Tablonuzun dinamik olmasını istiyorsanız, yukarıda açıklandığı gibi tablo içeriğine dayalı bir çözüm kullanmanız gerekecektir. Yalnızca daha küçük bir tablo görüntülemek istiyorsanız, bir kapsayıcı görünümü kullanabilir ve içine bir UITableViewController gömebilirsiniz - UITableView kap boyutuna göre yeniden boyutlandırılır.

Bu, birçok hesaplama ve düzen çağrısını önler.


1
Kelime kullanmayın aboveçünkü cevaplar karıştırılabilir.
Nik Kov

-3

Hızlı 3'te bunun için bir çözüm :viewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
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.