UICollectionView Các ô tự định cỡ với bố cục tự động


207

Tôi đang cố gắng tự kích thước UICollectionViewCellslàm việc với Bố cục tự động, nhưng dường như tôi không thể khiến các ô tự kích thước theo nội dung. Tôi gặp khó khăn khi hiểu kích thước của ô được cập nhật từ nội dung của những gì bên trong nội dung của ô.

Đây là thiết lập tôi đã thử:

  • Tùy chỉnh UICollectionViewCellvới một UITextViewtrong nội dung của nó.
  • Cuộn cho UITextViewbị vô hiệu hóa.
  • Ràng buộc ngang của contentView là: "H: | [_textView (320)]", tức UITextViewlà được ghim ở bên trái của ô có chiều rộng rõ ràng là 320.
  • Ràng buộc dọc của contentView là: "V: | -0 - [_ textView]", tức là UITextViewđược ghim vào đầu ô.
  • UITextViewmột ràng buộc về chiều cao được đặt thành một hằng số mà các UITextViewbáo cáo sẽ phù hợp với văn bản.

Đây là giao diện của nền ô được đặt thành màu đỏ và UITextViewnền được đặt thành Màu xanh lam: nền màu đỏ, màu xanh nền UITextView

Tôi đặt dự án mà tôi đã chơi trên GitHub tại đây .


Bạn đang thực hiện phương thức sizeForItemAtIndexPath?
Daniel Galasko

@DanielGalasko Tôi không. Tôi hiểu rằng tính năng các ô tự kích thước mới trong iOS 8 không yêu cầu bạn phải làm điều đó nữa.
rawbee

không chắc chắn bạn đã lấy thông tin đó từ đâu nhưng bạn vẫn cần đến, hãy xem đoạn trích WWDC asciiwwdc.com/2014/simes/226
Daniel Galasko

nhưng một lần nữa, nó phụ thuộc vào trường hợp sử dụng của bạn, hãy đảm bảo bạn triển khai ước
tínhSemSize

1
2019 - Điều cần thiết là phải xem xét điều này: stackoverflow.com/a/58108897/294884
Fattie

Câu trả lời:


309

Đã cập nhật cho Swift 5

preferredLayoutAttributesFittingAttributesđổi tên thành preferredLayoutAttributesFittingvà sử dụng kích thước tự động


Đã cập nhật cho Swift 4

systemLayoutSizeFittingSize đổi tên thành systemLayoutSizeFitting


Đã cập nhật cho iOS 9

Sau khi thấy giải pháp GitHub của tôi bị hỏng trong iOS 9, cuối cùng tôi cũng có thời gian để điều tra vấn đề đầy đủ. Bây giờ tôi đã cập nhật repo để bao gồm một số ví dụ về các cấu hình khác nhau cho các ô tự kích thước. Kết luận của tôi là các tế bào tự kích thước là lý thuyết tuyệt vời nhưng lộn xộn trong thực tế. Một lời cảnh báo khi tiến hành với các tế bào tự kích thước.

TL; DR

Kiểm tra dự án GitHub của tôi


Các ô tự kích thước chỉ được hỗ trợ với bố cục luồng, vì vậy hãy chắc chắn rằng đó là những gì bạn đang sử dụng.

Có hai điều bạn cần thiết lập để các ô tự kích thước hoạt động.

1. Đặt estimatedItemSizetrênUICollectionViewFlowLayout

Bố cục dòng chảy sẽ trở nên năng động trong tự nhiên khi bạn đặt thuộc estimatedItemSizetính.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Thêm hỗ trợ cho kích thước trên lớp con di động của bạn

Điều này có 2 hương vị; Tự động bố trí hoặc ghi đè tùy chỉnh của preferredLayoutAttributesFittingAttributes.

Tạo và cấu hình các ô với Bố cục tự động

Tôi sẽ không đi vào chi tiết về điều này vì có một bài viết SO tuyệt vời về cấu hình các ràng buộc cho một ô. Hãy cảnh giác rằng Xcode 6 đã phá vỡ một loạt các thứ với iOS 7, vì vậy, nếu bạn hỗ trợ iOS 7, bạn sẽ cần làm những thứ như đảm bảo autoresizingMask được đặt trên contentView của cell và giới hạn của contentView được đặt làm giới hạn của ô khi tế bào được tải (tức là awakeFromNib).

Điều bạn cần lưu ý là ô của bạn cần bị ràng buộc nghiêm trọng hơn so với Ô xem bảng. Ví dụ, nếu bạn muốn chiều rộng của bạn là động thì ô của bạn cần một ràng buộc về chiều cao. Tương tự như vậy, nếu bạn muốn chiều cao là động thì bạn sẽ cần một ràng buộc về chiều rộng cho ô của bạn.

Thực hiện preferredLayoutAttributesFittingAttributestrong ô tùy chỉnh của bạn

Khi chức năng này được gọi, chế độ xem của bạn đã được định cấu hình với nội dung (tức là cellForItemđã được gọi). Giả sử các ràng buộc của bạn đã được đặt một cách thích hợp, bạn có thể có một triển khai như thế này:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

LƯU Ý Trên iOS 9, hành vi đã thay đổi một chút có thể gây ra sự cố khi triển khai nếu bạn không cẩn thận (Xem thêm tại đây ). Khi bạn triển khai, preferredLayoutAttributesFittingAttributesbạn cần đảm bảo rằng bạn chỉ thay đổi khung thuộc tính bố cục của bạn một lần. Nếu bạn không làm điều này, bố cục sẽ gọi việc thực hiện của bạn vô thời hạn và cuối cùng sẽ sụp đổ. Một giải pháp là lưu trữ kích thước được tính toán trong ô của bạn và vô hiệu hóa điều này bất cứ khi nào bạn sử dụng lại ô hoặc thay đổi nội dung của nó như tôi đã làm với thuộc isHeightCalculatedtính.

Trải nghiệm bố cục của bạn

Tại thời điểm này, bạn nên có các ô động 'hoạt động' trong bộ sưu tập của bạn. Tôi chưa tìm thấy giải pháp vượt trội trong các thử nghiệm của mình, vì vậy hãy bình luận nếu bạn có. Nó vẫn cảm thấy như UITableViewchiến thắng trong cuộc chiến về kích thước IMHO năng động.

Hãy cẩn thận

Hãy lưu ý rằng nếu bạn đang sử dụng các ô nguyên mẫu để tính toán Ước tính kích thước - điều này sẽ bị hỏng nếu XIB của bạn sử dụng các lớp kích thước . Lý do cho điều này là khi bạn tải ô của mình từ XIB, lớp kích thước của nó sẽ được cấu hình Undefined. Điều này sẽ chỉ bị hỏng trên iOS 8 trở lên vì trên iOS 7, lớp kích thước sẽ được tải dựa trên thiết bị (iPad = Chính quy-Bất kỳ, iPhone = Nhỏ gọn-Bất kỳ). Bạn có thể đặt ước tính kích thước mà không cần tải XIB hoặc bạn có thể tải ô từ XIB, thêm nó vào bộ sưu tập (điều này sẽ đặt traitCollection), thực hiện bố cục và sau đó xóa nó khỏi giám sát. Ngoài ra, bạn cũng có thể làm cho tế bào của bạn ghi đè lên traitCollectiongetter và trả về các đặc điểm thích hợp. Tuỳ bạn.

Hãy cho tôi biết nếu tôi bỏ lỡ bất cứ điều gì, hy vọng tôi đã giúp và chúc may mắn mã hóa



2
Tôi đang cố gắng thực hiện điều tương tự với bố cục luồng, tuy nhiên khi tôi trả lại newFrame với kích thước được cập nhật, bố cục dường như không tính toán lại các vị trí y, vẫn dựa trên chiều cao trong Ước tính kích thước. Cái gì còn thiếu?
anna

@anna bạn đang gọi không hợp lệLayout trên bố trí?
Daniel Galasko

1
Ah, tìm thấy sự khác biệt, bạn đặt chiều cao ước tính khá cao (400) trong khi tôi đang làm tối thiểu (44). Điều đó đã giúp. Nhưng contentSize dường như vẫn chưa được đặt chính xác cho đến khi bạn cuộn hết cỡ đó, nên vẫn không sử dụng được. Btw, tôi đã thay đổi UITextView thành UILabel, hành vi tương tự.
anna

17
Điều này dường như bị phá vỡ cơ bản trên iOS 9 GM. Cài đặt estimatedItemSizedẫn đến sự cố - dường như có một số lỗi lớn trong cách bố trí tự động đang cố xử lý UICollectionViewCell.
Wes Campaigne

3
Chạy vào một vấn đề tương tự, có ai tìm thấy một công việc xung quanh?
Tony

47

Trong iOS10 có hằng số mới được gọi UICollectionViewFlowLayout.automaticSize(trước đây UICollectionViewFlowLayoutAutomaticSize), vì vậy thay vào đó:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

bạn có thể sử dụng cái này:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Nó có hiệu suất tốt hơn đặc biệt là khi các ô trong chế độ xem bộ sưu tập của bạn mở rộng liên tục

Giao diện truy cập luồng:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Cập nhật Swift 5:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}

8
Nơi nào bạn đặt điều này? trong viewdidload? Làm thế nào để bạn có được một xử lý trên Flowlayout?
UKDataGeek

1
Tôi đã quản lý để thực hiện điều này - nhưng nó có một hành vi kỳ lạ khiến nó tập trung vào các ô nếu có một ô duy nhất. Dưới đây là câu hỏi chồng tràn về nó - đã cho tôi bối rối: stackoverflow.com/questions/43266924/...
UKDataGeek

1
Bạn có thể truy cập bố cục luồng thông qua bộ sưu tập Xem collectionViewLayoutvà chỉ cần kiểm tra xem nó có thuộc loạiUICollectionViewFlowLayout
d4Rk

4
nó làm cho các tế bào của tôi rất nhỏ, vô dụng
user924

44

Một vài thay đổi quan trọng đối với câu trả lời của Daniel Galask đã khắc phục tất cả các vấn đề của tôi. Thật không may, tôi không có đủ danh tiếng để bình luận trực tiếp (chưa).

Trong bước 1, khi sử dụng Bố cục tự động, chỉ cần thêm một UIView cha đơn lẻ vào ô. MỌI THỨ bên trong tế bào phải là một cuộc phỏng vấn của cha mẹ. Điều đó đã trả lời tất cả các vấn đề của tôi. Mặc dù Xcode tự động thêm tính năng này cho UITableViewCells, nhưng nó không (nhưng nó nên) cho UICollectionViewCells. Theo các tài liệu :

Để định cấu hình giao diện của ô của bạn, hãy thêm các chế độ xem cần thiết để trình bày nội dung của mục dữ liệu dưới dạng xem trước cho chế độ xem trong thuộc tính contentView. Không trực tiếp thêm các cuộc phỏng vấn vào chính tế bào.

Sau đó bỏ qua bước 3 hoàn toàn. Nó không cần thiết.


1
Hấp dẫn; điều này đặc biệt dành cho Xibs, tuy nhiên cảm ơn, sẽ thử và kết hợp với dự án Github và xem liệu tôi có thể sao chép không. Có thể chia câu trả lời thành Xib vs lập trình
Daniel Galasko

Rất hữu ích. Cảm ơn!
Vấn đề

2
iOS 9, Xcode 7. Các tế bào được protoyped trong bảng phân cảnh, thiết lập lớp con tùy chỉnh. Đã thử tạo một thuộc tính được gọi contentView, Xcode phàn nàn rằng nó xung đột với thuộc tính hiện có. Đã thử thêm các cuộc phỏng vấn vào self.contentViewvà đặt các ràng buộc cho điều đó, sau đó ứng dụng gặp sự cố.
Nicolas Miari

@NicolasMiari Tôi không biết làm thế nào để thực hiện việc này theo chương trình, giải pháp của tôi thực sự chỉ là thêm một uiview duy nhất trong XIB nơi mọi thứ nằm trong đó. Bạn không cần phải tạo một tài sản hoặc bất cứ điều gì.
Matt Koala

Tôi đang đối mặt với cùng một vấn đề như @NicolasMiari. Khi tôi đặt ước tínhContentSize cho các ô được tạo nguyên mẫu trong bảng phân cảnh với các ràng buộc tự động thanh toán trên iOS 9 / Xcode 7, ứng dụng gặp sự cố với ngoại lệ truy cập xấu và không có stacktrace hữu ích
wasabi

34

Trong iOS 10+ đây là quy trình 2 bước rất đơn giản.

  1. Đảm bảo rằng tất cả nội dung ô của bạn được đặt trong một UIView duy nhất (hoặc bên trong hậu duệ của UIView như UIStackView giúp đơn giản hóa việc tự động thanh toán rất nhiều). Giống như với thay đổi kích thước động UITableViewCells, toàn bộ hệ thống phân cấp chế độ xem cần phải được cấu hình các ràng buộc, từ thùng chứa ngoài cùng đến chế độ xem trong cùng. Điều đó bao gồm các ràng buộc giữa UICollectionViewCell và trẻ em ngay lập tức

  2. Hướng dẫn tự động chuyển đổi luồng UICollectionView của bạn sang kích thước

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Tôi tò mò, làm thế nào để bọc nội dung các ô của bạn trong UIView làm cho Bố cục tự động hoạt động tốt hơn? Id có nghĩ rằng nó sẽ hoạt động tốt hơn với hệ thống phân cấp nông hơn?
James Heald

1
Tôi không đề cập đến hiệu suất, chỉ đơn giản. Các ô tự kích thước sẽ chỉ hoạt động nếu bạn có các ràng buộc kéo dài suốt từ trái sang phải và giữa trên và dưới. Đạt được điều đó là dễ nhất khi tất cả các khung nhìn được bọc trong một thùng chứa duy nhất. Nếu container đó là một UIStackView thì sẽ dễ dàng hơn nếu đó là bất kỳ hậu duệ nào khác của UIView.
Marmoy

Bạn có thể vui lòng cho tôi biết cách đặt hằng số chiều cao (như đặt chiều cao 25 ​​và chiều rộng sẽ được tự động thay đổi) cho UICollectionViewFlowLayoutAutomaticSize.
ZhengGe Che

Sử dụng Swift 4.1, Xcode cho tôi biết UICollectionViewFlowLayout.automaticSizeđã được đổi tên thành UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth

14
  1. Thêm FlowLayout trên viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Ngoài ra, đặt UIView làm mainContainer cho ô của bạn và thêm tất cả các chế độ xem được yêu cầu bên trong nó.

  3. Tham khảo hướng dẫn tuyệt vời, thú vị này để tham khảo thêm: UICollectionView với ô tự động hóa bằng cách sử dụng tự động thanh toán trong iOS 9 & 10


11

EDIT 11/19/19: Đối với iOS 13, chỉ cần sử dụng UICollectionViewCompositableLayout với chiều cao ước tính. Đừng lãng phí thời gian của bạn để đối phó với API bị hỏng này.

Sau khi vật lộn với điều này một thời gian, tôi nhận thấy rằng thay đổi kích thước không hoạt động đối với UITextViews nếu bạn không tắt cuộn:

let textView = UITextView()
textView.scrollEnabled = false

Khi tôi cố cuộn, ô xem bộ sưu tập không được trơn tru. Bạn có giải pháp nào cho việc này không?
Sathish Kumar Gurunathan

6

Nội dung neo bí ẩn:

Trong một trường hợp kỳ lạ này

    contentView.translatesAutoresizingMaskIntoConstraints = false

Sẽ không làm việc. Đã thêm bốn neo rõ ràng vào contentView và nó đã hoạt động.

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

và như thường lệ

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

trong YourLayout: UICollectionViewFlowLayout

Ai biết? Có thể giúp ai đó.

tín dụng

https://www.vadimbulavin.com/collection-view-cells-elf-sizing/

vấp phải mẹo ở đó - không bao giờ thấy nó ở bất kỳ nơi nào khác trong tất cả 1000 bài viết về điều này.


3

Tôi đã làm một chiều cao tế bào năng động của bộ sưu tập xem. Đây là git trung tâm repo .

Và, tìm hiểu lý do tại sao ưa thíchLayoutAttributFmitAttribut được gọi nhiều hơn một lần. Trên thực tế, nó sẽ được gọi ít nhất 3 lần.

Hình ảnh nhật ký giao diện điều khiển: nhập mô tả hình ảnh ở đây

Ưu tiên số 1LayoutAttributFmitAttribut :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Bố cụcAttribut.frame.size.height là trạng thái hiện tại 57.5 .

Ưu tiên thứ 2LayoutAttributFmitAttribut :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Chiều cao khung ô thay đổi thành 534,5 như mong đợi của chúng tôi. Nhưng, xem bộ sưu tập vẫn không có chiều cao.

Ưu tiên thứ 3LayoutAttributFmitAttribut :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

Bạn có thể thấy chiều cao của bộ sưu tập đã được thay đổi từ 0 thành 477 .

Hành vi tương tự như xử lý cuộn:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

Lúc đầu, tôi nghĩ phương pháp này chỉ gọi một lần. Vì vậy, tôi đã mã hóa như sau:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

Đường thẳng này:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

sẽ gây ra cuộc gọi hệ thống vô hạn vòng lặp và sự cố ứng dụng.

Bất kỳ kích thước nào cũng được thay đổi, nó sẽ xác nhận tất cả các ô ưa thíchLayoutAttributFmitAttribution nhiều lần cho đến khi mọi vị trí của các ô (ví dụ: khung) không còn thay đổi nữa.


2

Ngoài các câu trả lời trên,

Chỉ cần đảm bảo rằng bạn đặt thuộc tính Ước tính Kích thước của UICollectionViewFlowLayout ở một số kích thước và không triển khai phương thức ủy nhiệm sizeForItem: atIndexPath .

Đó là nó.


1

Nếu bạn triển khai phương thức UICollectionViewDelegateFlowLayout:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

Khi bạn gọi collectionview performBatchUpdates:completion:, chiều cao kích thước sẽ sử dụng sizeForItemAtIndexPaththay vì preferredLayoutAttributesFittingAttributes.

Quá trình kết xuất performBatchUpdates:completionsẽ đi qua phương thức preferredLayoutAttributesFittingAttributesnhưng nó bỏ qua các thay đổi của bạn.


Tôi đã nhìn thấy hành vi sai trái này, nhưng chỉ khi kích thước ước tính bằng không. Bạn có chắc chắn bạn đang đặt kích thước ước tính?
dgatwood

1

Để bất cứ ai nó có thể giúp đỡ,

Tôi đã có sự sụp đổ khó chịu nếu estimatedItemSizeđược thiết lập. Ngay cả khi tôi trả về 0 trong numberOfItemsInSection. Do đó, chính các ô và bố trí tự động của chúng không phải là nguyên nhân gây ra sự cố ... Bộ sưu tập đã bị hỏng, ngay cả khi trống, chỉ vìestimatedItemSize được đặt để tự kích thước.

Trong trường hợp của tôi, tôi đã sắp xếp lại dự án của mình, từ một bộ điều khiển có chứa bộ sưu tậpView đến bộ sưu tậpViewContaptor và nó đã hoạt động.

Đi hình.


1
Hãy thử gọi collectionView.collectionViewLayout.invalidateLayout()sau collectionView.reloadData().
chengsam

cho ios10 và dưới thêm mã này `viewWillLayoutSubviews override func () {super.viewWillLayoutSubviews () nếu #available (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma

1

Đối với bất cứ ai đã thử mọi thứ mà không gặp may mắn, đây là điều duy nhất khiến nó hoạt động với tôi. Đối với các nhãn đa dòng bên trong tế bào, hãy thử thêm dòng ma thuật này:

label.preferredMaxLayoutWidth = 200

Thêm thông tin: tại đây

Chúc mừng!


0

Phương pháp ví dụ trên không biên dịch. Đây là một phiên bản chính xác (nhưng chưa được kiểm tra xem nó có hoạt động hay không.)

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}

0

Cập nhật thêm thông tin:

  • Nếu bạn sử dụng flowLayout.estimatedItemSize, đề nghị sử dụng phiên bản iOS8.3 sau. Trước iOS8.3, nó sẽ bị sập [super layoutAttributesForElementsInRect:rect];. Thông báo lỗi là

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • Thứ hai, trong phiên bản iOS8.x, flowLayout.estimatedItemSizesẽ khiến cài đặt phần chèn khác nhau không hoạt động. tức là hàm : (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.


0

Giải pháp bao gồm 4 bước quan trọng:

  1. Kích hoạt kích thước tế bào động

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Hoàn toàn cho phép bố trí tự động và ghim contentViewvào các cạnh của ô vì contentViewngăn tự kích thước, do nó không bật bố trí tự động.
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. Đặt contentView. ThongAnchor.constraint từ collectionView(:cellForItemAt:)để giới hạn độ rộng của contentView thành chiều rộng của bộ sưu tậpView.

Nó xảy ra như sau; chúng ta sẽ thiết lập tế bàomaxWidth biến rộng CollectionView tại collectionView(:cellForItemAt:)và trong maxWidth's didSetphương pháp chúng tôi sẽ thiết widthAnchor.constant để maxWidth.

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

Vì bạn muốn kích hoạt tự kích thước UITextView, nên nó có thêm một bước để;

4. Tính toán và thiết lập heightAnchor.constant của UITextView.

Vì vậy, bất cứ khi nào độ rộng của contentView được đặt, chúng tôi sẽ điều chỉnh chiều cao của UITextView cùng didSetvớimaxWidth .

Bên trong UICollectionViewCell:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        contentViewWidthAnchor.constant = maxWidth
        contentViewWidthAnchor.isActive = true

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

Các bước này sẽ giúp bạn có được kết quả mong muốn. Đây là một runnable hoàn chỉnh ý chính

Cảm ơn Vadim Bulavin vì bài đăng trên blog rõ ràng và sáng sủa của mình - Bộ sưu tập Xem các ô tự kích thước: Hướng dẫn từng bước


-2

Tôi đã thử sử dụng estimatedItemSizenhưng có một loạt lỗi khi chèn và xóa các ô nếu estimatedItemSizenó không chính xác bằng chiều cao của ô. tôi đã dừng cài đặtestimatedItemSize và triển khai các ô động bằng cách sử dụng một ô nguyên mẫu. Đây là cách thực hiện:

tạo giao thức này:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

thực hiện giao thức này trong tùy chỉnh của bạn UICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

bây giờ làm cho bộ điều khiển của bạn tuân thủ UICollectionViewDelegateFlowLayoutvà trong đó, có trường này:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

nó sẽ được sử dụng như một ô nguyên mẫu để liên kết dữ liệu và sau đó xác định cách dữ liệu đó ảnh hưởng đến thứ nguyên mà bạn muốn là động

cuối cùng, UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)phải được thực hiện:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

và đó là nó. Trả lại kích thước của ô collectionView(:layout:sizeForItemAt:)theo cách này giúp tôi không phải sử dụng estimatedItemSize, và chèn và xóa các ô hoạt động hoàn hảo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.