programing

UICollectionView, 전체 너비 셀, 동적 높이 자동 레이아웃 허용?

magicmemo 2023. 6. 20. 21:31
반응형

UICollectionView, 전체 너비 셀, 동적 높이 자동 레이아웃 허용?

2021년 참고 사항!관련 @Ely 답변 참조UICollectionLayoutListConfiguration!!!!


로 으로직수.UICollectionView,

자동 레이아웃을 통해 동적 높이를 제어할 수 있지만 전체 너비 셀을 가질 수 있습니까?

이것은 아마도 "정말 좋은 답변이 없는 iOS에서 가장 중요한 질문"으로 생각됩니다.


중요:

99%의 경우 전체 폭 셀 + 자동 레이아웃 동적 높이를 달성하려면 테이블 뷰를 사용하면 됩니다.그 정도로 쉽습니다.


그러면 컬렉션 뷰가 필요한 곳의 예는 무엇입니까?

집합 뷰는 테이블 뷰보다 훨씬 강력합니다.

자동 레이아웃 동적 높이와 함께 집합 뷰를 사용해야 하는 간단한 예:

컬렉션 보기에서 두 레이아웃 사이에 애니메이션을 만드는 경우.예를 들어, 장치가 회전할 때 1열과 2열 레이아웃 사이에 있습니다.

그것은 iOS의 일반적인 관용구입니다.유감스럽게도 이 QA에서 제기된 문제를 해결해야만 달성할 수 있습니다. :-/

iOS 13+용 솔루션

Swift 5.1 및 iOS 13에서는 Compositional Layout 개체를 사용하여 문제를 해결할 수 있습니다.

다음 전체 샘플 코드는 다중 라인을 표시하는 방법을 보여줍니다.UILabel 내부에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")
    }

}

예상 디스플레이:

enter image description here


iOS 11+용 솔루션

5 및 을 사용하면 Swift 5.1 을 iOS 11 및 하 분 수 있 다 니 습 류 할 로 스 클 래 위 여 하 사 용 ▁swift class 니 다 있 습 ▁with ▁sub ▁can 수 ▁i ▁you 및 os , ▁i ▁5os▁swift▁11▁5을분할류▁11로UICollectionViewFlowLayout 설합니다를 합니다.estimatedItemSizeUICollectionViewFlowLayout.automaticSize 조정(자동 크기 조정)을 .UICollectionViewCell. 그런 다음 다시 정의해야 합니다.layoutAttributesForElements(in:)그리고.layoutAttributesForItem(at:)셀 너비를 설정하기 위해.마지막으로, 당신은 당신의 세포의 기능을 무시해야 할 것입니다.preferredLayoutAttributesFitting(_:)방법 및 높이 계산.

행 다코는음다시체을표보방여니줍법는하인라을중다드전▁▁to▁shows▁display▁how▁the다▁code니줍▁following보여▁complete다을방법을 표시하는 방법을 보여줍니다.UILabel 내부에UIcollectionViewCell에 의해 됨).UICollectionView 및 의안전구및역및▁and▁'역구▁areaUICollectionViewFlowLayout 예를 들어감):

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
    }

}

사용자 지정FlowLayout.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
    }

}

▁altern다▁here니구▁some에 대한 몇 가지 대안적인 구현이 있습니다.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
}

예상 디스플레이:

enter image description here

문제

채우려고 , 둘 다 할 수 .UICollectionViewFlowLayoutAutomaticSize.

다음을 사용하여 수행할 작업UICollectionView그래서 아래가 당신을 위한 해결책입니다.

해결책

단계-I: 셀의 예상 높이 계산

당신이 가지고 있는 것이 오직UILabelCollectionViewCell설정된 것보다numberOfLines=0되는 높이인 그고것예은높계다니습산했이를는의 높이를 했습니다.UIlable 가지 변수를 합니다.

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
}

만약 당신이CollectionViewCell에는 만포만 포함됩니다.UIImageView그리고 만약 그것이 높이에서 동적이어야 한다면 당신은 높이를 얻는 데 필요한 것보다.UIImage (당신의UIImageView가져야만 한다AspectRatio제약 조건)

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

계산된 높이보다 두 값이 모두 포함된 경우 이를 함께 추가합니다.

STEP-II: 크기 반환CollectionViewCell

추가UICollectionViewDelegateFlowLayout viewController

대리인 방법 구현

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

이것이 당신에게 도움이 되기를 바랍니다.

이 문제를 해결할 수 있는 몇 가지 방법이 있습니다.

뷰하고 한 가 방 은 웃 에 아 레 크 사 을 지 있 수 니 다 습 할 정 용 와 기 예 상 지 이 흐 법 집 름 뷰 합 다 ▁one ▁give 니 ▁an ▁layout ▁flow ▁use ▁you ▁the ▁and ▁is ▁can ion ▁way ▁view ▁size 있 ▁collect ▁estimated 한systemLayoutSizeFitting셀 크기를 계산합니다.

참고: 아래 설명에서 언급한 바와 같이 iOS 10부터는 더 이상 통화를 트리거하기 위한 예상 크기를 제공할 필요가 없습니다. func preferredLayoutAttributesFitting(_ layoutAttributes:) 감방에서이전(iOS 9)에서는 선호하는 LayoutAttributes를 호출하려면 예상 크기를 입력해야 합니다.

(스토리보드를 사용하고 있고 IB를 통해 컬렉션 뷰가 연결되어 있다고 가정)

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

단순한 세포의 경우 보통 충분합니다.에서 collection view cell을 덮어쓸 수 있습니다.func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes그러면 세포 크기를 더 세밀하게 제어할 수 있습니다.참고: 흐름 레이아웃에 추정 크기를 지정해야 합니다.

다음 그런다재의정을 재정의합니다.func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes올바른 크기를 반환합니다.

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
}

대신 크기 조정 셀을 사용하여 셀의 크기를 계산할 수 있습니다.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 an instance of your cell if it doesn't use a Nib
    // let sizingCell = MyCollectionViewCell()
    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)
}

이러한 사례는 완벽한 프로덕션 준비 사례는 아니지만 올바른 방향으로 시작할 수 있도록 지원해야 합니다.이것이 최선의 방법이라고 말할 수는 없지만, 여러 개의 레이블이 포함된 상당히 복잡한 셀에서도 여러 줄로 감쌀 수 있고 그렇지 않을 수 있습니다.

저는 그 문제에 대한 아주 쉬운 해결책을 찾았습니다.CollectionViewCell 내부에는 UIView()가 있는데, 이는 실제로 배경일 뿐입니다.전체 너비를 얻으려면 다음 앵커를 설정합니다.

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

"마법"은 첫 번째 줄에서 일어납니다.화면 너비에 맞춰 widthAnchor를 동적으로 설정했습니다.또한 CollectionView의 삽입값을 빼는 것도 중요합니다.그렇지 않으면 셀이 나타나지 않습니다.이러한 배경 보기를 원하지 않으면 보이지 않게 만듭니다.

FlowLayout은 다음 설정을 사용합니다.

layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

결과적으로 동적 높이를 가진 전체 너비 크기의 셀이 만들어집니다.

enter image description here

작동 중!!!IOS:12.1 Swift 4.1에서 테스트됨

저는 제약 없이 작동하는 매우 간단한 해결책을 가지고 있습니다.

enter image description here

내 보기 컨트롤러 클래스

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
    }


}

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

}

주성분은 Custom cell의 system Layout Size Fitting 기능입니다.또한 Cell 내부의 뷰 폭을 제약 조건으로 설정해야 합니다.

iOS 14 이상을 사용하는 경우에는UICollectionLayoutListConfiguration API를 사용할 수 UICollectionView수평 스와이프 컨텍스트 메뉴 및 자동 높이 셀과 같은 기능을 포함한 단일 열 테이블 뷰로 사용할 수 있습니다.

var collectionView: UICollectionView! = nil

override func viewDidLoad() {
    super.viewDidLoad()

    let config = UICollectionLayoutListConfiguration(appearance: .plain)
    let layout = UICollectionViewCompositionalLayout.list(using: config)
    collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
    collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    view.addSubview(collectionView)
}

예제 프로젝트를 포함하여 셀 및 데이터 소스를 구성하는 방법에 대한 자세한 내용은 Apple의 이 기사에서 확인할 수 있습니다.현대 컬렉션 보기 구현, 특히 단순 목록 레이아웃 작성으로 시작하는 섹션.

예제 프로젝트에는 자동 레이아웃을 기반으로 자동 높이 셀을 구성하는 방법을 보여주는 ConferenceNewsFeedViewController라는 컨트롤러가 포함되어 있습니다.

  1. estimatedItemSize흐름 레이아웃:

    collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
    
  2. 셀에서 너비 제약 조건을 정의하고 수퍼뷰의 너비와 동일하게 설정합니다.

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

전체 예

iOS 11에서 테스트됨.

CollectionViewCell에 너비 제약 조건을 추가해야 합니다.

class SelfSizingCell: UICollectionViewCell {

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

Eric의 답변에 대한 제 의견에 따르면, 제 솔루션은 그의 솔루션과 매우 유사하지만 선호하는 SizeFor에 제약 조건을 추가해야 했습니다...고정 치수에 구속하기 위해.

    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
    }

이 질문은 중복되는 내용이 많아서 여기서 자세히 답변했고, 작업 샘플 앱을 제공했습니다.

개인적으로 각 셀이 서로 다른 크기를 가질 수 있는 동안 자동 레이아웃이 크기를 결정하는 UICollectionView를 갖는 가장 좋은 방법은 실제 셀을 사용하여 크기를 측정하는 동안 UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath 기능을 구현하는 것입니다.

는 제 블로그 게시물 중 하나에서 이것에 대해 이야기했습니다.

이것이 당신이 원하는 것을 성취하는 데 도움이 되기를 바랍니다.100% 확신할 수는 없지만 AutoLayout을 함께 사용하면 실제로 완전히 자동으로 셀 높이를 설정할 수 있는 UITableView와 달리

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionViewCell이 화면의 전체 너비를 채울 필요가 없기 때문에 UICollectionView에는 AutoLayout이 크기를 결정할 수 있는 방법이 없습니다.

하지만 여기 질문이 있습니다.전체 화면 너비 셀이 필요한 경우 자동 크기 조정 셀과 함께 제공되는 오래된 UITableView에서 UICollectionView를 사용하는 이유는 무엇입니까?

이것이 "정말 좋은 답변"이 될 수 있을지는 모르겠지만, 이것이 제가 이를 달성하기 위해 사용하는 것입니다.저의 플로우 레이아웃은 수평이며, 자동 레이아웃으로 폭을 조절하려고 노력하고 있어서 당신의 상황과 비슷합니다.

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

그런 다음 자동 레이아웃 제약 조건이 수정되는 뷰 컨트롤러에서 NSNotification을 실행합니다.

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

UICollectionView 하위 클래스에서 알림을 수신합니다.

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

레이아웃을 무효화합니다.

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

이것이 원인입니다.sizeForItemAt집합 뷰의 새 크기를 사용하여 다시 호출됩니다.이 경우 레이아웃에서 사용할 수 있는 새로운 제약 조건을 고려하여 업데이트할 수 있어야 합니다.

2023년의 올바른 현대적 해결책은 구성 레이아웃을 사용하는 것이며, 이는 이 게시물의 다양한 답변에 문서화되어 있습니다.

타당한 예에 대한 강력한 문서는 Apple의 이 가이드와 관련 소스 코드를 참조하십시오.

https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views

예상 사이즈는 너무 작거나 너무 크면 안 되며, 그렇지 않으면 적재 시 약간 이상하게 보일 수 있습니다.

레이아웃을 다시 로드하거나 방향을 전환할 때 다음을 사용하여 레이아웃을 무효화합니다.

self.collectionView.collectionViewLayout.invalidateLayout()

기타 옵션

기본 레이아웃 특성을 사용하여 이 작업을 수행할 수도 있습니다.셀을 업데이트하고 콘텐츠 크기를 무효화한 후 적합하지만, 이는 유동적이거나 프리미엄이 아닌 매우 위험한 솔루션으로 이어집니다.

이 게시물이나 Apple 링크의 예를 따를 것을 강력히 권장합니다.

의 신의에viewDidLayoutSubviews을 설정합니다.estimatedItemSizeUICollectionViewFlowLayout은 다음과 같습니다.

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

셀에서 제약 조건이 셀의 상단과 하단에 모두 닿아야 합니다(다음 코드는 Cartography를 사용하여 제약 조건 설정을 단순화하지만, 원하는 경우 NSLayoutConstraint 또는 IB를 사용하여 설정할 수 있습니다).

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
    }

Voila, 당신들의 세포들은 이제 자동으로 키가 커질 것입니다!

AutoLayout은 CollectionView에서 셀의 자동 크기 조정에 사용할 수 있습니다. 두 가지 단계를 쉽게 수행할 수 있습니다.

  1. 동적 셀 크기 조정 사용

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. widthAnchor에서 from widthAnchor.. 제약 조건:collectionView(:cellForItemAt:)내용 보기의 너비를 집합 보기의 너비로 제한합니다.
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
        }
    }

    ....
}

이상으로 원하는 결과를 얻을 수 있습니다.전체 코드는 다음 통계를 참조하십시오.

참조/크레딧:

스크린샷:

또한 다이내믹 셀의 높이 문제에 직면하여 자동 레이아웃과 수동 높이 계산을 사용하여 문제를 해결할 수 있었습니다.추정된 크기를 사용하여 셀을 인스턴스화하거나 생성할 필요가 없습니다.

또한 이 솔루션은 여러 줄 레이블의 처리를 제공합니다.셀에 있는 모든 하위 뷰의 하위 뷰 높이 계산을 기반으로 합니다.

extension CollectionViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        let contentHorizontalSpaces = collectionLayout.minimumInteritemSpacing
            + collectionLayout.sectionInset.left
            + collectionLayout.sectionInset.right
        let newCellWidth = (collectionView.bounds.width - contentHorizontalSpaces) / 2
        let newHeight = Cell.getProductHeightForWidth(props: data[indexPath.row], width: newCellWidth)
        return CGSize(width: newCellWidth, height: newHeight)
    }
}

UICollectionViewDelegateFlowLayout은 다음과 같이 계산된 셀 크기를 사용합니다.getProductHeightForWidth방법:

extension Cell {

    class func getProductHeightForWidth(props: Props, width: CGFloat) -> CGFloat {
        // magic numbers explanation:
        // 16 - offset between image and price
        // 22 - height of price
        // 8 - offset between price and title
        var resultingHeight: CGFloat = 16 + 22 + 8
        // get image height based on width and aspect ratio
        let imageHeight = width * 2 / 3
        resultingHeight += imageHeight

        let titleHeight = props.title.getHeight(

            font: .systemFont(ofSize: 12), width: width
        )
        resultingHeight += titleHeight

        return resultingHeight
    }
}

저는 여기서 이야기를 만들었습니다: https://volodymyrrykhva.medium.com/uicollectionview-cells-with-dynamic-height-using-autolayout-a4e346b7bd2a

솔루션에 대한 전체 코드는 GitHub: https://github.com/ascentman/DynamicHeightCells 에 있습니다.

을 할 때UICollectionViewCompositionalLayout둘 다 .itemSize그리고.groupSize에서 지까높이까지.estimated:

제 문제는 제가 사용하고 있다는 것이었습니다..fractional(1) 목별에 height그리고..estimated(44)위해서group효과가 없었어요

extension TasksController {
    static func createLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { (sectionIndex: Int, _ NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            let height: CGFloat = 44

            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(height))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)


            let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(height))
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)

            let section = NSCollectionLayoutSection(group: group)
            return section
        }
    }
}

아이폰 폭을 조정하려면 동적 폭이 필요하기 때문에 어떤 솔루션도 작동하지 않았습니다.

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

저는 이 대답을 따라 간단하고 목표를 달성했습니다.또한 선택한 답변과 달리 iPad에 대한 보기를 조정할 수 있습니다.

하지만 나는 셀의 폭이 장치의 폭에 따라 바뀌기를 원했기 때문에 폭 제약을 추가하지 않고, 수정했습니다.preferredLayoutAttributesFitting방법은 다음과 같습니다.

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let targetSize = CGSize(width: UIScreen.main.bounds.size.width / ((UIDevice.current.userInterfaceIdiom == .phone) ? 1 : 2), height: 0);
    layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel);
    
    return layoutAttributes;
}

저는 동적 높이를 가진 세포에 대한 많은 복잡한 답을 보았습니다.하지만 몇 번의 구글 검색 후에 나는 이 간단한 답을 발견했습니다.

collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "LabelOnlyCell", bundle: nil), forCellWithReuseIdentifier: "LabelOnlyCell")
if let collectionViewLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
   collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}

중요 부품

collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

참조용 LabelOnlyCell 클래스

class LabelOnlyCell: UICollectionViewCell {

@IBOutlet weak var labelHeading: UILabel!
@IBOutlet weak var parentView: UIView!


override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
    
    self.parentView.translatesAutoresizingMaskIntoConstraints = false
    self.parentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
}

}

참조용 스크린샷.enter image description here

iOS 10부터, 우리는 그것을 하기 위해 플로우 레이아웃에 대한 새로운 API를 얻었습니다.

이 해야 할 은 당신의 설만하됩니를 입니다.flowLayout.estimatedItemSize상수로, 운상수로새로,▁a▁to로,로,UICollectionViewFlowLayoutAutomaticSize.

원천

솔루션은 https://www.advancedswift.com/autosizing-full-width-cells/ 에서 해제되었습니다.

사용자 정의 셀 클래스 내에 다음을 추가합니다.

class MyCustomCell: UICollectionViewCell {

    ...

    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
            
            var targetSize = targetSize
            targetSize.height = CGFloat.greatestFiniteMagnitude
            
            let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
            
            return size
       } 
}

수집 보기가 포함된 View 컨트롤러의 ViewDidLoad에 다음이 표시됩니다.

override func viewDidLoad() {
    super.viewDidLoad()
    
    ...

    let cellWidth = 200 // whatever your cell width is
    
    let layout = myCustomCollectionView.collectionViewLayout
        if let flowLayout = layout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(
                width: cellWidth,
                height: 200 //an estimated height, but this will change when the cell is created
            )
      }
}

이것이 제가 찾은 가장 간단한/짧은 솔루션입니다.

collectionViewController에서 UICollectionViewDelegateFlowLayout 클래스를 상속해야 합니다.그런 다음 다음 기능을 추가합니다.

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

이를 사용하면 화면 너비의 너비 크기를 알 수 있습니다.

이제 행이 테이블 뷰 컨트롤러로 사용되는 CollectionViewController가 있습니다.

각 셀의 높이 크기를 동적으로 지정하려면 필요한 각 셀에 대해 사용자 정의 셀을 만들어야 합니다.

언급URL : https://stackoverflow.com/questions/44187881/uicollectionview-full-width-cells-allow-autolayout-dynamic-height

반응형