UICollectionView, các ô có chiều rộng đầy đủ, cho phép chiều cao động tự động thanh toán?


120

Theo chiều dọc UICollectionView,

Có thể có các ô có chiều rộng đầy đủ , nhưng cho phép kiểm soát chiều cao động bằng tính năng tự động thanh toán không?

Đây có lẽ là câu hỏi quan trọng nhất trong iOS mà không có câu trả lời thực sự tốt.


Quan trọng:

Lưu ý rằng trong 99% trường hợp, để đạt được các ô có chiều rộng đầy đủ + chiều cao động tự động thanh toán, chỉ cần sử dụng chế độ xem bảng. Nó là dễ dàng.


Vậy ví dụ về nơi bạn cần xem bộ sưu tập là gì?

Chế độ xem bộ sưu tập cực kỳ mạnh mẽ hơn chế độ xem bảng.

Một ví dụ đơn giản trong đó bạn thực sự phải sử dụng chế độ xem bộ sưu tập, để đạt được các ô có chiều rộng đầy đủ + chiều cao động tự động thanh toán:

Nếu bạn tạo hoạt ảnh giữa hai bố cục trong chế độ xem bộ sưu tập. Ví dụ, giữa bố cục 1 và 2 cột, khi thiết bị xoay.

Đó là một thành ngữ phổ biến và bình thường trong iOS. Thật không may, nó chỉ có thể đạt được bằng cách giải quyết vấn đề đặt ra trong QA này. : - /


có là có thể với lớp UICollectionViewDelegateFlowLayout
Sunil Prajapati,

Điều này có nghĩa là bạn thực sự có thể sử dụng một tableView? chức năng bổ sung nào sẽ cần thiết cho bạn, để làm phức tạp điều này?
Catalina T.

1
Chào bạn @CatalinaT. , chế độ xem bộ sưu tập có nhiều, nhiều, nhiều tính năng bổ sung hơn chế độ xem bảng. Một ví dụ đơn giản là thêm vật lý. (Nếu bạn không biết những gì tôi muốn nói, đó là cách bạn làm cho "bouncy" danh sách trong iOS.)
Fattie

bạn cũng có thể Áp dụng bằng cách nào đó vật lý (như SpringDampinginitialSpringVelocity) trong bảng Cell ..... hãy xem Danh sách Bouncy này bằng cách sử dụng tableView ,,,, pastebin.com/pFRQhkrX bạn có thể tạo hoạt ảnh cho TableViewCellbảng trong willDisplayCell:(UITableViewCell *)cellphương thức delgate @Fattie mà bạn có thể chấp nhận câu trả lời đã giải quyết vấn đề của bạn để giúp những người khác đang tìm kiếm vấn đề tương tự
Dhiru

ohk, bạn đã thử danh sách Bouncy bằng chế độ xem danh sách chưa? @Fattie
Dhiru

Câu trả lời:


122

1. Giải pháp cho iOS 13+

Với Swift 5.1 và iOS 13, bạn có thể sử dụng các đối tượng Bố cục Thành phần để giải quyết vấn đề của mình.

Mã mẫu hoàn chỉnh sau đây cho biết cách hiển thị nhiều dòng UILabelbên trong toàn chiều rộng UICollectionViewCell:

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        let size = NSCollectionLayoutSize(
            widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
            heightDimension: NSCollectionLayoutDimension.estimated(44)
        )
        let item = NSCollectionLayoutItem(layoutSize: size)
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
        section.interGroupSpacing = 10

        let headerFooterSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .absolute(40)
        )
        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: headerFooterSize,
            elementKind: "SectionHeaderElementKind",
            alignment: .top
        )
        section.boundarySupplementaryItems = [sectionHeader]

        let layout = UICollectionViewCompositionalLayout(section: section)
        collectionView.collectionViewLayout = layout
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { context in
            self.collectionView.collectionViewLayout.invalidateLayout()
        }, completion: nil)
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Hiển thị mong đợi:

nhập mô tả hình ảnh ở đây


2. Giải pháp cho iOS 11+

Với Swift 5.1 và iOS 11, bạn có thể phân lớp UICollectionViewFlowLayoutvà đặt thuộc tính của nó estimatedItemSizethành UICollectionViewFlowLayout.automaticSize(điều này cho hệ thống biết rằng bạn muốn xử lý tự động lưu trữ UICollectionViewCell). Sau đó, bạn sẽ phải ghi đè layoutAttributesForElements(in:)layoutAttributesForItem(at:)để thiết lập chiều rộng ô. Cuối cùng, bạn sẽ phải ghi đè preferredLayoutAttributesFitting(_:)phương thức của ô và tính chiều cao của nó.

Đoạn mã hoàn chỉnh sau đây cho biết cách hiển thị nhiều dòng UILabelbên trong toàn chiều rộng UIcollectionViewCell(bị giới hạn bởi UICollectionViewvùng an toàn và nội bộ UICollectionViewFlowLayoutcủa '):

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]
    let customFlowLayout = CustomFlowLayout()

    override func viewDidLoad() {
        super.viewDidLoad()

        customFlowLayout.sectionInsetReference = .fromContentInset // .fromContentInset is default
        customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        customFlowLayout.minimumInteritemSpacing = 10
        customFlowLayout.minimumLineSpacing = 10
        customFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        customFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)

        collectionView.collectionViewLayout = customFlowLayout
        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

}

CustomFlowLayout.swift

import UIKit

final class CustomFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
        layoutAttributesObjects?.forEach({ layoutAttributes in
            if layoutAttributes.representedElementCategory == .cell {
                if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        })
        return layoutAttributesObjects
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let collectionView = collectionView else {
            fatalError()
        }
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }

        layoutAttributes.frame.origin.x = sectionInset.left
        layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
        return layoutAttributes
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
        layoutIfNeeded()
        layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
        return layoutAttributes
    }

}

Dưới đây là một số triển khai thay thế cho preferredLayoutAttributesFitting(_:):

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    return layoutAttributes
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    label.preferredMaxLayoutWidth = layoutAttributes.frame.width
    layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
    return layoutAttributes
}

Hiển thị mong đợi:

nhập mô tả hình ảnh ở đây


Điều này hoạt động tốt cho tôi cho đến khi tôi thêm tiêu đề phần. Tiêu đề phần được đặt sai vị trí và che các ô khác trong iOS 11. Nhưng hoạt động tốt trong iOS 12. Bạn có gặp phải vấn đề gì khi thêm tiêu đề phần không?
Nakul

Cảm ơn bạn rất nhiều. Bạn đã tiết kiệm tuần của tôi. 🙇‍♂️
Gaetan

1
Bộ sưu tậpXem cuộn lên đầu bất cứ khi nào kích thước thay đổi
Sajith

1
tin tức tuyệt vời, @ImanouPetit! Tôi ước tiền thưởng lớn hơn! Thông tin tuyệt vời về các đối tượng Bố cục Thành phần, cảm ơn.
Fattie,

1
Xin chào, tôi đang thử cái này. Mặc dù tôi đang thiết lập bố cục trong viewdidload như bạn đã làm ở trên, nhưng tôi nhận được sự cố cho biết rằng collectionview phải được khởi tạo bằng tham số bố cục không phải là nil. Có điều gì tôi đang thiếu? Đây là Xcode 11 cho iOS 13.
geistmate

29

Vấn đề

Bạn đang tìm kiếm chiều cao tự động và cũng muốn có chiều rộng đầy đủ, không thể lấy cả hai về sử dụng UICollectionViewFlowLayoutAutomaticSize.

Bạn muốn làm UICollectionViewnhư vậy thì dưới đây là giải pháp cho bạn.

Giải pháp

Bước-I : Tính chiều cao dự kiến ​​của Ô

1. Nếu bạn chỉ có UILabel trong CollectionViewCellhơn thiết lập numberOfLines=0và đã tính được chiều cao dự kiến UIlable, hãy vượt qua cả ba thông số

func heightForLable(text:String, font:UIFont, width:CGFloat) -> CGFloat {
    // pass string, font, LableWidth  
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
     label.numberOfLines = 0
     label.lineBreakMode = NSLineBreakMode.byWordWrapping
     label.font = font
     label.text = text
     label.sizeToFit()

     return label.frame.height
}

2. Nếu của bạn CollectionViewCellchỉ chứa UIImageViewvà nếu nó được cho là động trong Chiều cao hơn bạn cần lấy chiều cao của UIImage (bạn UIImageViewphải có các AspectRatioràng buộc)

// this will give you the height of your Image
let heightInPoints = image.size.height
let heightInPixels = heightInPoints * image.scale

3. Nếu nó chứa cả hai so với chiều cao tính toán của chúng và cộng chúng lại với nhau.

BƯỚC-II : Trả lại kích thước củaCollectionViewCell

1. Thêm người được UICollectionViewDelegateFlowLayoutủy quyền vào viewController của bạn

2. Thực hiện phương pháp ủy quyền

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

    // This is just for example, for the scenario Step-I -> 1 
    let yourWidthOfLable=self.view.size.width
    let font = UIFont(name: "Helvetica", size: 20.0)

    var expectedHeight = heightForLable(array[indePath.row], font: font, width:yourWidthOfLable)


    return CGSize(width: view.frame.width, height: expectedHeight)
}

Tôi mong rằng nó giúp ích được cho bạn.


1
Có thể có UICollectionView cuộn ngang với chiều cao ô tăng theo chiều dọc với bố cục tự động không?
nr5

Tôi muốn thực hiện tương tự, nhưng một nửa chiều rộng và chiều cao năng động
Mihir Mehta

return CGSize(width: view.frame.width/2, height: expectedHeight)cũng giữ Padding trong tài khoản hơn là trả về chiều rộng nếu không hai ô sẽ không vừa trong một hàng.
Dhiru

@ nr5 Bạn đã nhận được giải pháp chưa? Yêu cầu của tôi cũng giống như yêu cầu của bạn. Cảm ơn.
Maulik Bhuptani

@MaulikBhuptani Tôi không thể làm điều đó hoàn toàn với Bố cục Tự động. Phải tạo một lớp Bố cục tùy chỉnh và ghi đè một số phương thức của lớp.
nr5

18

Có một số cách bạn có thể giải quyết vấn đề này.

Một cách là bạn có thể cung cấp cho bố cục luồng chế độ xem bộ sưu tập một kích thước ước tính và tính toán kích thước ô.

Lưu ý: Như đã đề cập trong phần nhận xét bên dưới, kể từ iOS 10, bạn không còn cần cung cấp và kích thước ước tính để kích hoạt lệnh gọi đến một ô func preferredLayoutAttributesFitting(_ layoutAttributes:). Trước đây (iOS 9) sẽ yêu cầu bạn cung cấp kích thước ước tính nếu bạn muốn truy vấn ô prefferedLayoutAttributes.

(giả sử bạn đang sử dụng bảng phân cảnh và chế độ xem bộ sưu tập được kết nối qua IB)

override func viewDidLoad() {
    super.viewDidLoad()
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.estimatedItemSize = CGSize(width: 375, height: 200) // your average cell size
}

Đối với các ô đơn giản thường sẽ là đủ. Nếu kích thước vẫn không chính xác, trong ô dạng xem bộ sưu tập, bạn có thể ghi đè func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes, điều này sẽ cung cấp cho bạn khả năng kiểm soát hạt tốt hơn đối với kích thước ô. Lưu ý: Bạn vẫn cần cung cấp kích thước ước tính cho bố cục luồng .

Sau đó ghi đè func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributesđể trả lại kích thước chính xác.

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
    let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize)
    autoLayoutAttributes.frame = autoLayoutFrame
    return autoLayoutAttributes
}

Ngoài ra, thay vào đó, bạn có thể sử dụng ô định cỡ để tính toán kích thước của ô trong UICollectionViewDelegateFlowLayout.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.width
    let size = CGSize(width: width, height: 0)
    // assuming your collection view cell is a nib
    // you may also instantiate a instance of our cell from a storyboard
    let sizingCell = UINib(nibName: "yourNibName", bundle: nil).instantiate(withOwner: nil, options: nil).first as! YourCollectionViewCell
    sizingCell.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    sizingCell.frame.size = size
    sizingCell.configure(with: object[indexPath.row]) // what ever method configures your cell
    return sizingCell.contentView.systemLayoutSizeFitting(size, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
}

Mặc dù đây không phải là những ví dụ sẵn sàng cho quá trình sản xuất hoàn hảo nhưng chúng sẽ giúp bạn bắt đầu đúng hướng. Tôi không thể nói đây là phương pháp hay nhất, nhưng điều này phù hợp với tôi, ngay cả với những ô khá phức tạp chứa nhiều nhãn, có thể có hoặc không bao gồm nhiều dòng.


@Fattie Không rõ 'esitmatedItemSize' không liên quan đến ô chế độ xem bộ sưu tập có kích thước động như thế nào, vì vậy tôi muốn nghe suy nghĩ của bạn. (không phải lời mỉa mai)
Eric Murphey

Ngoài ra, nếu câu trả lời này thiếu điều gì đó cụ thể để hữu ích và / hoặc những gì bạn coi là kinh điển, hãy cho tôi biết. Tôi sẽ không tranh luận về tiền thưởng, tôi chỉ muốn hữu ích cho bất kỳ ai khác xem câu hỏi này.
Eric Murphey

1
(bao gồm hoặc không bao gồm ước tínhItemSize không tạo ra sự khác biệt nào trong iOS hiện tại và không giúp ích gì cả, trong vấn đề "chiều rộng đầy đủ + chiều cao động".) mã ở cuối, ngòi hiếm khi được sử dụng ngày nay và nó có ít liên quan đến độ cao động (ví dụ: từ một vài chế độ xem văn bản độ cao động). cảm ơn mặc dù
Fattie

Tôi cũng đã đến giải pháp này, nhưng trong trường hợp của tôi, iOS 11, Xcode 9.2, nội dung ô dường như bị ngắt kết nối khỏi chính ô - tôi không thể nhận được hình ảnh để mở rộng toàn bộ chiều cao hoặc chiều rộng của kích thước cố định. Tôi nhận thấy thêm rằng CV là các ràng buộc cài đặt trong preferredLayoutAttributesFitting. Sau đó, tôi đã thêm một ràng buộc của riêng mình trong hàm đã nói, ràng buộc với thứ nguyên cố định từ Kích thước ước tính, FWIW, hãy xem câu trả lời của tôi bên dưới.
Chris Conover

16

Tôi đã tìm thấy một giải pháp khá dễ dàng cho vấn đề đó: Bên trong CollectionViewCell của tôi, tôi có một UIView () thực ra chỉ là một cái nền. Để có được chiều rộng đầy đủ, tôi chỉ cần đặt các Anchors sau

bgView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width - 30).isActive = true // 30 is my added up left and right Inset
bgView.topAnchor.constraint(equalTo: topAnchor).isActive = true
bgView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
bgView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
bgView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

Điều "kỳ diệu" xảy ra ở dòng đầu tiên. Tôi đặt động widthAnchor thành chiều rộng của màn hình. Một điều quan trọng nữa là trừ các nội dung trong CollectionView của bạn. Nếu không, ô sẽ không hiển thị. Nếu bạn không muốn có chế độ xem nền như vậy, chỉ cần ẩn nó.

FlowLayout sử dụng các cài đặt sau

layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

Kết quả là một ô có kích thước chiều rộng đầy đủ với chiều cao động.

nhập mô tả hình ảnh ở đây


1
thánh bò - thật tuyệt vời! quá rõ ràng! xuất sắc !
Fattie

1
chỉ là TBC @ inf1783, bạn đang làm việc này trong UICollectionView? (không phải xem bảng) - đúng không?
Fattie

1
@Fattie Vâng, đó là một UICollectionView;)
inf1783

fantastic @ inf1783, rất nóng lòng được dùng thử tính năng này. Nhân tiện, một cách hay khác để làm điều đó là phân lớp UIView và chỉ cần thêm chiều rộng nội tại (tôi đoán nó sẽ có tác dụng tương tự, và nó có thể sẽ làm cho nó hoạt động tại thời điểm bảng phân cảnh)
Fattie

1
Mỗi khi tôi thử điều này, tôi kết thúc trong vòng lặp vô tận với Hành vi của UICollectionViewFlowLayout là lỗi không được xác định: /
Skodik.o Ngày

7

ĐANG LÀM VIỆC!!! Đã thử nghiệm trên IOS: 12.1 Swift 4.1

Tôi có một giải pháp rất đơn giản chỉ hoạt động mà không bị phá vỡ ràng buộc.

nhập mô tả hình ảnh ở đây

ViewControllerClass của tôi

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    let cellId = "CustomCell"

    var source = ["nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,"]

    override func viewDidLoad() {
        super.viewDidLoad()

        self.collectionView.delegate = self
        self.collectionView.dataSource = self
        self.collectionView.register(UINib.init(nibName: cellId, bundle: nil), forCellWithReuseIdentifier: cellId)

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

    }

}


extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.source.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? CustomCell else { return UICollectionViewCell() }
        cell.setData(data: source[indexPath.item])
        return cell
    }


}

Lớp CustomCell:

class CustomCell: UICollectionViewCell {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var widthConstraint: NSLayoutConstraint!

    override func awakeFromNib() {
        super.awakeFromNib()
        self.widthConstraint.constant = UIScreen.main.bounds.width
    }

    func setData(data: String) {
        self.label.text = data
    }

    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        return contentView.systemLayoutSizeFitting(CGSize(width: self.bounds.size.width, height: 1))
    }

}

Thành phần chính là systemLayoutSizeFitting chức năng trong Customcell. Và chúng ta cũng phải thiết lập chiều rộng của khung nhìn bên trong Cell với các ràng buộc.


6

Bạn phải thêm ràng buộc chiều rộng vào CollectionViewCell

class SelfSizingCell: UICollectionViewCell {

  override func awakeFromNib() {
      super.awakeFromNib()
      contentView.translatesAutoresizingMaskIntoConstraints = false
      contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
  }
}

Cuối cùng một người nào đó đã làm cho nó hoàn thiện trong hai dòng
Omar N Shamali

Điều này sẽ giới hạn ô ở một chiều rộng cố định, hầu như không tự định kích thước. Nếu bạn xoay thiết bị hoặc điều chỉnh kích thước màn hình trên iPad, nó sẽ vẫn cố định.
Thomas Verbeek

4
  1. Tập estimatedItemSizehợp bố cục luồng của bạn:

    collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
  2. Xác định ràng buộc chiều rộng trong ô và đặt nó bằng chiều rộng của superview:

    class CollectionViewCell: UICollectionViewCell {
        private var widthConstraint: NSLayoutConstraint?
    
        ...
    
        override init(frame: CGRect) {
            ...
            // Create width constraint to set it later.
            widthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0)
        }
    
        override func updateConstraints() {
            // Set width constraint to superview's width.
            widthConstraint?.constant = superview?.bounds.width ?? 0
            widthConstraint?.isActive = true
            super.updateConstraints()
        }
    
        ...
    }

Đầy đủ ví dụ

Đã thử nghiệm trên iOS 11.


3

Cá nhân tôi đã tìm thấy những cách tốt nhất để có một UICollectionView nơi AutoLayout xác định kích thước trong khi mỗi Ô có thể có kích thước khác nhau là triển khai chức năng UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath trong khi sử dụng một Ô thực tế để đo kích thước.

Tôi đã nói về điều này trong một trong những bài đăng trên blog của tôi

Hy vọng rằng điều này sẽ giúp bạn đạt được những gì bạn muốn. Tôi không chắc 100% nhưng tôi tin rằng không giống như UITableView, nơi bạn thực sự có thể có chiều cao ô hoàn toàn tự động bằng cách sử dụng kết hợp AutoLayout với

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionView không có cách để AutoLayout xác định kích thước vì UICollectionViewCell không nhất thiết phải lấp đầy toàn bộ chiều rộng của màn hình.

Nhưng đây là một câu hỏi dành cho bạn : Nếu bạn cần các ô chiều rộng toàn màn hình, tại sao bạn thậm chí còn bận tâm sử dụng UICollectionView thay vì một UITableView cũ tốt đi kèm với các ô tự động định kích thước?


Giữ nó, Aprit bên dưới đang nói rằng UICollectionViewFlowLayoutAutomaticSize hiện có sẵn trong bố cục luồng, trong iOS10 ...
Fattie

Tôi cũng chỉ thấy điều này. Rõ ràng đó là một điều bây giờ trong iOS 10. Tôi vừa xem cuộc nói chuyện WWDC tương ứng về điều này: developer.apple.com/videos/play/wwdc2016/219 Tuy nhiên, điều này sẽ hoàn toàn tự động kích thước ô để phù hợp với nội dung của nó. Tôi không chắc chắn làm thế nào bạn có thể nói với các tế bào (với AutoLayout) để lấp đầy chiều rộng màn hình kể từ khi bạn không thể thiết lập một hạn chế giữa UICollectionViewCell và mẹ của nó (ít nhất là không ở Storyboards)
xxtesaxx

đúng. chúng ta dường như không tiến gần hơn đến một giải pháp thực tế cho vấn đề vô cùng rõ ràng này. : /
Fattie

Theo bài nói chuyện, bạn vẫn có thể ghi đè lên sizeThatFits () hoặc favouriteLayoutAttributesFitting () và tự mình tính toán kích thước nhưng đó không phải là những gì OP yêu cầu. Dù sao, tôi vẫn quan tâm đến trường hợp sử dụng khi anh ấy / cô ấy cần một UICollectionViewCell có chiều rộng đầy đủ và tại sao sẽ là một vấn đề lớn như vậy nếu không sử dụng UITableView trong trường hợp cụ thể này (chắc chắn có thể có một số trường hợp nhất định nhưng sau đó tôi đoán bạn chỉ phải đối phó với làm một số tính toán cho mình)
xxtesaxx

nghĩ về điều đó, thật khó tin khi bạn không thể chỉ đơn giản đặt "cột == 1" (và sau đó có thể là cột == 2 khi thiết bị nằm nghiêng), với chế độ xem bộ sưu tập.
Fattie

3

Theo nhận xét của tôi về câu trả lời của Eric, giải pháp của tôi rất giống với giải pháp của anh ấy, nhưng tôi đã phải thêm một ràng buộc trong favouriteSizeFor ... để hạn chế kích thước cố định.

    override func systemLayoutSizeFitting(
        _ targetSize: CGSize, withHorizontalFittingPriority
        horizontalFittingPriority: UILayoutPriority,
        verticalFittingPriority: UILayoutPriority) -> CGSize {

        width.constant = targetSize.width

        let size = contentView.systemLayoutSizeFitting(
            CGSize(width: targetSize.width, height: 1),
            withHorizontalFittingPriority: .required,
            verticalFittingPriority: verticalFittingPriority)

        print("\(#function) \(#line) \(targetSize) -> \(size)")
        return size
    }

Câu hỏi này có một số điểm trùng lặp, tôi đã trả lời chi tiết tại đây và cung cấp một ứng dụng mẫu hoạt động tại đây.


2

Không chắc liệu điều này có đủ điều kiện là "câu trả lời thực sự tốt" hay không, nhưng đó là những gì tôi đang sử dụng để thực hiện điều này. Bố cục luồng của tôi là theo chiều ngang và tôi đang cố gắng điều chỉnh chiều rộng bằng tính năng tự động thanh toán, vì vậy nó tương tự với tình huống của bạn.

extension PhotoAlbumVC: UICollectionViewDelegateFlowLayout {
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // My height is static, but it could use the screen size if you wanted
    return CGSize(width: collectionView.frame.width - sectionInsets.left - sectionInsets.right, height: 60) 
  }
}

Sau đó, trong bộ điều khiển chế độ xem nơi ràng buộc tự động thanh toán được sửa đổi, tôi kích hoạt một Thông báo NSN.

NotificationCenter.default.post(name: NSNotification.Name("constraintMoved"), object: self, userInfo: nil)

Trong lớp con UICollectionView của tôi, tôi lắng nghe thông báo đó:

// viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(handleConstraintNotification(notification:)), name: NSNotification.Name("constraintMoved"), object: nil)

và làm mất hiệu lực của bố cục:

func handleConstraintNotification(notification: Notification) {
    self.collectionView?.collectionViewLayout.invalidateLayout()
}

Điều này gây ra sizeForItemAtđược gọi lại bằng cách sử dụng kích thước mới của chế độ xem bộ sưu tập. Trong trường hợp của bạn, nó sẽ có thể cập nhật với các ràng buộc mới có sẵn trong bố cục.


chỉ TBC Mark bạn có nghĩa là bạn đang thực sự thay đổi kích thước của một ô? (tức là, xuất hiện tế bào và nó được kích thước X, sau đó một cái gì đó xảy ra trong ứng dụng của bạn và nó trở nên kích thước Y ..?) thx
Fattie

Đúng. Khi người dùng kéo một phần tử trên màn hình, một sự kiện sẽ kích hoạt sẽ cập nhật kích thước của ô.
Mark Suman

1

Trên của bạn viewDidLayoutSubviews, hãy đặt thành estimatedItemSizechiều rộng đầy đủ (bố cục đề cập đến đối tượng UICollectionViewFlowLayout):

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.bounds.size.width, height: 120)
}

Trên ô của bạn, hãy đảm bảo rằng các ràng buộc của bạn chạm vào cả trên cùng và dưới cùng của ô (đoạn mã sau sử dụng Bản đồ để đơn giản hóa việc thiết lập các ràng buộc nhưng bạn có thể thực hiện với NSLayoutConstraint hoặc IB nếu muốn):

constrain(self, nameLabel, valueLabel) { view, name, value in
        name.top == view.top + 10
        name.left == view.left
        name.bottom == view.bottom - 10
        value.right == view.right
        value.centerY == view.centerY
    }

Thì đấy, các ô của bạn bây giờ sẽ tự động phát triển theo chiều cao!


0

Không có giải pháp nào phù hợp với tôi vì tôi cần chiều rộng động để thích ứng giữa chiều rộng iPhone.

    class CustomLayoutFlow: UICollectionViewFlowLayout {
        override init() {
            super.init()
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        override var itemSize: CGSize {
            set { }
            get {
                let width = (self.collectionView?.frame.width)!
                let height = (self.collectionView?.frame.height)!
                return CGSize(width: width, height: height)
            }
        }
    }

    class TextCollectionViewCell: UICollectionViewCell {
        @IBOutlet weak var textView: UITextView!

        override func prepareForReuse() {
            super.prepareForReuse()
        }
    }




    class IntroViewController: UIViewController, UITextViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UINavigationControllerDelegate {
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionView: UICollectionView!
        var collectionViewLayout: CustomLayoutFlow!

        override func viewDidLoad() {
            super.viewDidLoad()

            self.collectionViewLayout = CustomLayoutFlow()
            self.collectionView.collectionViewLayout = self.collectionViewLayout
        }

        override func viewWillLayoutSubviews() {
            self.collectionViewTopDistanceConstraint.constant = UIScreen.main.bounds.height > 736 ? 94 : 70

            self.view.layoutIfNeeded()
        }
    }

0

Từ iOS 10, chúng tôi đã có API mới về bố cục luồng để thực hiện điều đó.

Tất cả những gì bạn phải làm là đặt của bạn flowLayout.estimatedItemSizethành một hằng số mới UICollectionViewFlowLayoutAutomaticSize,.

Nguồn


Hmm, bạn khá chắc chắn rằng nó làm cho nó * có chiều rộng đầy đủ ? Vì vậy, về cơ bản là một cột?
Fattie

Nó có thể xảy ra nếu bạn đặt chiều rộng của UICollectionViewCell thành chiều rộng đầy đủ một cách rõ ràng.
Arpit Dongre

Không, nó không cố định chiều rộng thành chiều cao đầy đủ và bỏ qua giá trị kích thước ước tính trừ khi bạn làm theo câu trả lời của Eric ở trên hoặc của tôi bên dưới / sau.
Chris Conover

OK, rất tiếc sau đó điều này hoàn toàn không có liên quan đến vấn đề! hê hê! : O
Fattie

0

AutoLayout có thể được sử dụng để tự động định cỡ ô trong CollectionView bằng 2 bước đơn giản:

  1. Bật định cỡ ô động

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Có chế độ xem vùng chứa và đặt từ containerView.widthAnchor.constraint collectionView(:cellForItemAt:)để giới hạn chiều rộng của contentView thành chiều rộng của collectionView.
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
            }
            containerViewWidthAnchor.constant = maxWidth
            containerViewWidthAnchor.isActive = true
        }
    }

    ....
}

Vậy là bạn sẽ nhận được kết quả mong muốn. Tham khảo giá sau để biết mã đầy đủ:

Tham khảo / Tín dụng:

Ảnh chụp màn hình: nhập mô tả hình ảnh ở đây


-6

Bạn phải kế thừa lớp UICollectionViewDelegateFlowLayout trên collectionViewController của bạn. Sau đó thêm chức năng:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width, height: 100)
}

Sử dụng điều đó, bạn có kích thước chiều rộng của chiều rộng màn hình.

Và bây giờ bạn có một collectionViewController với các hàng dưới dạng tableViewController.

Nếu bạn muốn kích thước chiều cao của mỗi ô là động, có lẽ bạn nên tạo các ô tùy chỉnh cho mỗi ô bạn cần.


7
Đây chính xác không phải là câu trả lời cho câu hỏi :) Điều đó sẽ cung cấp một chiều cao cố định là 100, tức là, đó là cách một trong những lập trình trước khi tự động thanh toán tồn tại.
Fattie
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.