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


120

수직에서 UICollectionView,

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

이것은 아마도 "정말 좋은 대답이없는 iOS에서 가장 중요한 질문"이라고 생각합니다.


중대한:

99 %의 경우 전체 너비 셀 + 자동 레이아웃 동적 높이를 얻으려면 테이블보기사용하기 만하면됩니다 . 그렇게 쉽습니다.


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

컬렉션 뷰는 테이블 뷰보다 훨씬 더 강력합니다.

전체 너비 셀 + 자동 레이아웃 동적 높이를 얻기 위해 실제로 컬렉션 뷰 사용해야하는 간단한 예 :

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

이는 iOS에서 일반적이고 일반적인 관용구입니다. 불행히도이 QA에서 제기 된 문제를 해결해야만 달성 할 수 있습니다. :-/


예 UICollectionViewDelegateFlowLayout 클래스 가능합니다
선일 Prajapati

이것은 실제로 tableView를 사용할 수 있음을 의미하지 않습니까? 이것을 지나치게 복잡하게하려면 어떤 추가 기능이 필요합니까?
Catalina T.

1
안녕하세요 @CatalinaT. , 컬렉션 뷰에는 테이블 뷰에 비해 많은 추가 기능이 있습니다. 간단한 예는 물리학을 추가하는 것입니다. (내 말이 무엇인지 모르겠다면 iOS에서 "탄력"목록을 만드는 방법입니다.)
Fattie

당신은 또한 어떻게 든 물리학 (같은 적용 할 수 있습니다 SpringDampinginitialSpringVelocity있는 tableView를 사용하여이 탄력 목록에서 봐 ..... 표 셀을) ,,,, pastebin.com/pFRQhkrX 당신은 애니메이션을 적용 할 수 있습니다 TableViewCell테이블에서 willDisplayCell:(UITableViewCell *)cell당신이 받아 들일 수 @Fattie 위양 방법 다른 도움말로 문제가 해결 대답은 사람 같은 문제를 찾고 있습니다
Dhiru

아, 목록보기를 사용하여 Bouncy 목록을 사용해 보셨습니까? @Fattie
Dhiru

답변:


123

1. 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")
    }

}

예상 디스플레이 :

여기에 이미지 설명 입력


2. iOS 11+ 용 솔루션

Swift 5.1 및 iOS 11을 사용하면 하위 클래스를 UICollectionViewFlowLayout만들고 estimatedItemSize속성을로 설정할 수 있습니다 UICollectionViewFlowLayout.automaticSize(이는 자동 크기 조정을 처리 할 시스템을 알려줍니다 UICollectionViewCell). 그런 다음 재정의 layoutAttributesForElements(in:)하고 layoutAttributesForItem(at:)셀 너비를 설정해야합니다. 마지막으로 셀의 preferredLayoutAttributesFitting(_:)메서드 를 재정의 하고 높이를 계산해야합니다.

다음 전체 코드는 UILabel전체 너비 안에 여러 줄 을 표시하는 방법을 보여줍니다 UIcollectionViewCell( UICollectionView의 안전 영역 및의 삽입에 의해 제한됨 UICollectionViewFlowLayout).

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
    }

}

다음은에 대한 몇 가지 대체 구현입니다 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
}

예상 디스플레이 :

여기에 이미지 설명 입력


이것은 섹션 헤더를 추가 할 때까지 잘 작동했습니다. 섹션 헤더가 잘못 배치되어 iOS 11에서 다른 셀을가립니다.하지만 iOS 12에서는 잘 작동합니다. 섹션 헤더를 추가하는 데 문제가 있습니까?
Nakul

정말 고맙습니다. 당신은 내 주를 구했습니다. 🙇♂️
에 탕

1
CollectionView 크기가 변경 될 때마다 맨 위로 스크롤
Sajith

1
환상적인 소식, @ImanouPetit! 현상금이 더 컸으면 좋겠어요! Compositional Layout 개체에 대한 환상적인 정보, 감사합니다.
Fattie

1
안녕하세요, 저는 이것을 시도하고 있습니다. 위에서했던 것처럼 viewdidload에서 레이아웃을 설정하더라도 collectionview를 nil이 아닌 레이아웃 매개 변수로 초기화해야한다는 충돌이 발생합니다. 내가 놓친 것이 있습니까? 이것은 아이폰 OS (13)에 대한 엑스 코드 (11)이다
geistmate

29

문제

자동 높이를 찾고 있고 너비가 가득 차고 싶을 때 둘 다 사용할 수는 없습니다 UICollectionViewFlowLayoutAutomaticSize.

UICollectionView아래를 사용하여 원하는 솔루션이 있습니다.

해결책

Step-I : 예상되는 Cell 높이 계산

1.를 설정하고 의 예상 높이를 계산 한 것 이상 만 UILabel 있는 CollectionViewCell경우 세 개의 매개 변수를 모두 전달합니다.numberOfLines=0UIlable

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. 귀하의 경우 CollectionViewCell에만 포함 UIImageView하고 고도의 동적 있어야하는데의 경우보다 당신의 높이를 얻을 필요가 UIImage (당신 UIImageView이 있어야합니다 AspectRatio제약)

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

3. 계산 된 높이보다 둘 다 포함되어 있으면 함께 더합니다.

STEP-II : 크기 반환CollectionViewCell

1.UICollectionViewDelegateFlowLayout viewController에 델리게이트 추가

2. 델리게이트 메서드 구현

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

도움이 되었기를 바랍니다.


1
자동 레이아웃으로 셀 높이가 세로로 증가하는 가로 스크롤 UICollectionView를 가질 수 있습니까?
nr5

나는 동일하지만, 반 폭 및 동적 높이 달성하고 싶다
미 히어 메타

return CGSize(width: view.frame.width/2, height: expectedHeight)또한 너비를 반환하는 것보다 Padding을 유지하십시오. 그렇지 않으면 두 셀이 단일 행에 맞지 않습니다.
Dhiru

@ nr5 솔루션을 얻었습니까? 내 요구 사항은 당신과 동일합니다. 감사.
Maulik Bhuptani 2010 년

@MaulikBhuptani 자동 레이아웃으로 완전히 할 수 없었습니다. 사용자 지정 레이아웃 클래스를 만들고 일부 클래스 메서드를 재정의해야했습니다.
nr5

18

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

한 가지 방법은 컬렉션보기 흐름 레이아웃에 예상 크기를 제공하고 셀 크기를 계산하는 것입니다.

참고 : 아래 주석에서 언급했듯이 iOS 10부터는 셀 호출을 트리거하기 위해 더 이상 예상 크기를 제공 할 필요가 없습니다 func preferredLayoutAttributesFitting(_ layoutAttributes:). 이전 (iOS 9)에서는 prefferedLayoutAttributes 셀을 쿼리하려는 경우 예상 크기를 제공해야했습니다.

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

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

일반적으로 충분할 단순한 세포의 경우. 여전히 크기가 잘못된 경우 컬렉션보기 셀에서을 재정의 할 수 있습니다 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 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)
}

이것이 완벽한 프로덕션 준비 예제는 아니지만 올바른 방향으로 시작해야합니다. 이것이 최선의 방법이라고 말할 수는 없지만 여러 레이블을 포함하는 상당히 복잡한 셀에서도 여러 줄로 줄 바꿈되거나 줄 바꿈되지 않을 수도 있습니다.


@Fattie 'esitmatedItemSize'가 동적 크기의 컬렉션 뷰 셀과 어떻게 관련이 없는지 명확하지 않으므로 여러분의 생각을 듣고 싶습니다. (하지 풍자)
에릭 머피

또한이 답변에 도움이 될만한 구체적인 내용 및 / 또는 표준이라고 생각하는 내용이 누락 된 경우 알려주세요. 나는 현상금에 대해 논쟁하지 않을 것이며,이 질문을 보는 다른 사람들에게 도움이되고 싶습니다.
에릭 머피

1
(estimatedItemSize를 포함하거나 포함하지 않는 것은 현재 iOS에서 아무런 차이가 없으며 "전폭 + 동적 높이"문제에서 전혀 도움이되지 않습니다.) 끝에있는 코드, 펜촉은 요즘 거의 사용되지 않습니다. 동적 높이와 거의 관련이 없습니다 (예 : 몇 가지 동적 높이 텍스트보기에서). 감사하지만
Fattie

이 솔루션에도 도달했지만 제 경우에는 iOS 11, Xcode 9.2에서 셀 내용이 셀 자체에서 분리 된 것처럼 보였습니다. 이미지를 고정 차원의 전체 높이나 너비로 확장 할 수 없었습니다. 나는 CV가 preferredLayoutAttributesFitting. 그런 다음 예상 항목 크기, FWIW의 고정 차원에 바인딩하여 해당 함수에 내 자신의 제약 조건을 추가했습니다. 아래 내 대답을 참조하십시오.
Chris Conover

16

이 문제에 대한 매우 쉬운 해결책을 찾았습니다. 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

결과는 동적 높이가있는 전체 너비 크기의 셀입니다.

여기에 이미지 설명 입력


1
거룩한 소-놀랍습니다! 너무 분명해! 훌륭한 !
Fattie 2008

1
TBC @ inf1783, UICollectionView에서이 작업을 수행하고 있습니까? (테이블 뷰가 아님)-맞습니까?
Fattie 2008

1
예 @Fattie, 그것은 UICollectionView입니다)
inf1783

환상적인 @ inf1783, 이것을 시도하기를 기다릴 수 없습니다. 그건 그렇고, 그렇게하는 또 다른 좋은 방법은 UIView를 하위 클래스로 만들고 단순히 고유 너비를 추가하는 것입니다 (동일한 효과가있을 것이라고 생각하며 스토리 보드 시간에도 작동하게 만들 것입니다)
Fattie

1
내가 이것을 시도 할 때마다 UICollectionViewFlowLayout의 동작이 정의되지 않았습니다 오류 : /
Skodik.o

7

일!!! IOS : 12.1 Swift 4.1에서 테스트 됨

제약 조건 위반없이 작동하는 매우 간단한 솔루션이 있습니다.

여기에 이미지 설명 입력

내 ViewControllerClass

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

}

주성분은 CustomcellsystemLayoutSizeFitting 함수입니다. 또한 제약 조건으로 Cell 내부 뷰의 너비를 설정해야합니다.


6

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

class SelfSizingCell: UICollectionViewCell {

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

마침내 누군가 두 줄로 완벽하게 만들었습니다
Omar N Shamali

이것은 셀을 고정 된 너비로 제한하고 거의 자체 크기를 조정하지 않습니다. iPad에서 기기를 회전하거나 화면 크기를 조정하면 고정 된 상태로 유지됩니다.
Thomas Verbeek

4
  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에서 테스트되었습니다.


3

개인적으로 AutoLayout이 크기를 결정하는 UICollectionView를 갖는 가장 좋은 방법은 각 Cell이 다른 크기를 가질 수 있지만 실제 Cell을 사용하여 크기를 측정하는 동안 UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath 함수를 구현하는 것입니다.

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

바라건대 이것은 당신이 원하는 것을 달성하는 데 도움이 될 것입니다. 100 % 확실하지는 않지만 AutoLayout을 사용하여 실제로 완전 자동 셀 높이를 가질 수있는 UITableView와는 달리

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionView는 UICollectionViewCell이 화면의 전체 너비를 반드시 채우는 것은 아니기 때문에 AutoLayout이 크기를 결정하도록하는 방법이 없습니다.

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


그것을 잡고, Aprit 아래 ... UICollectionViewFlowLayoutAutomaticSize이 iOS10에서, 플로우 레이아웃에서 사용할 수 있음을 말하고있다
Fattie

나는 또한 이것을 보았다. 분명히 iOS 10 에서는이 문제가 발생 합니다. 이에 대한 WWDC 이야기를 방금 시청했습니다. developer.apple.com/videos/play/wwdc2016/219 그러나 이것은 콘텐츠에 맞게 셀 크기를 완전히 자동으로 조정합니다. 나는 당신이 화면 너비를 채우기 위해 (자동 레이아웃과) 셀을 알 수있는 방법이 아니에요 이후 당신이 할 수없는 설정 (적어도하지 스토리 보드로) UICollectionViewCell와 부모 사이의 제약
xxtesaxx

권리. 우리는이 믿을 수 없을 정도로 명백한 문제에 대한 실제 해결책에 더 가깝지 않은 것 같습니다. : /
Fattie

이야기에 따르면 여전히 sizeThatFits () 또는 preferredLayoutAttributesFitting ()을 덮어 쓰고 크기를 스스로 계산할 수 있지만 OP가 요청한 것이 아닙니다. 어쨌든, 나는 그가 전체 너비 UICollectionViewCell이 필요한 경우 와이 특정 경우에 UITableView를 사용하지 않는 것이 왜 그렇게 큰 문제인지에 대한 사용 사례에 관심이 있습니다 (특정 사례가있을 수 있지만 그런 다음 추측 당신은 단지 몇 가지 계산 자신) 일을 처리해야
xxtesaxx을

그것에 대해 생각해 보면, 컬렉션 뷰로 단순히 "columns == 1"(그리고 장치가 옆에있을 때 아마도 column == 2)로 설정할 수 없다는 것은 매우 놀랍습니다.
Fattie 2017-06-05

3

Eric의 답변에 대한 내 의견에 따르면 내 솔루션은 그의 솔루션과 매우 유사하지만 고정 차원으로 제한하기 위해 preferredSizeFor ...에 제약 조건을 추가해야했습니다.

    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
    }

이 질문에는 많은 중복이 있으며 여기에서 자세히 답변하고 여기 에서 작동하는 샘플 앱을 제공했습니다 .


2

이것이 "정말 좋은 대답"인지 확실하지 않지만, 이것을 달성하기 위해 사용하고 있습니다. 내 흐름 레이아웃은 수평이고 자동 레이아웃으로 너비를 조정하려고하므로 상황과 비슷합니다.

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컬렉션 뷰의 새 크기를 사용하여 다시 호출됩니다. 귀하의 경우 레이아웃에서 사용 가능한 새로운 제약 조건을 고려하여 업데이트 할 수 있어야합니다.


그냥 TBC Mark 당신은 실제로 세포의 크기를 변경 한다는 의미 입니까? (즉, 셀이 표시되고,이 크기의 X, 무언가 앱에서 발생되며,이 크기 Y ..이됩니까?) 들으
Fattie

예. 사용자가 화면에서 요소를 드래그하면 셀 크기를 업데이트하는 이벤트가 발생합니다.
Mark Suman 2017 년

1

viewDidLayoutSubviews의 설정 estimatedItemSize전체 폭 (레이아웃이 UICollectionViewFlowLayout 객체를 참조)에 :

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

셀에서 제약 조건이 셀의 맨 위와 맨 아래에 닿아 야합니다 (다음 코드는지도 ​​제작을 사용하여 제약 조건 설정을 단순화하지만 원하는 경우 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
    }

짜잔, 당신의 세포는 이제 높이가 자동으로 증가합니다!


0

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

iOS 10부터는이를위한 흐름 레이아웃에 대한 새로운 API가 있습니다.

당신이해야 할 일은 flowLayout.estimatedItemSize새로운 상수 인 UICollectionViewFlowLayoutAutomaticSize.

출처


흠, 당신은 그것을 * 전체 너비로 만드는 것이 확실 합니까? 그래서 본질적으로 하나의 열?
Fattie 2017-06-03

UICollectionViewCell의 너비를 명시 적으로 전체 너비로 설정하면 발생할 수 있습니다.
Arpit Dongre

아니요, 너비를 전체 높이로 고정하지 않으며 위의 Eric의 답변을 따르거나 아래 / 나중에 광산을 따르지 않는 한 예상 크기 값을 무시합니다.
Chris Conover

좋아요, 불행히도 이것은 문제와 전혀 관련이 없습니다! 헤! : O
Fattie 19-07-04

0

AutoLayout은 CollectionView의 셀 크기를 2 단계로 쉽게 조정하는 데 사용할 수 있습니다.

  1. 동적 셀 크기 조정 활성화

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. 컨테이너 뷰가 있고 containerView.widthAnchor.constraint를 설정하여 collectionView(:cellForItemAt:)contentView의 너비를 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
        }
    }

    ....
}

원하는 결과를 얻을 수 있습니다. 전체 코드는 다음 요점을 참조하십시오.

참조 / 크레딧 :

스크린 샷 : 여기에 이미지 설명 입력


-6

collectionViewController에서 UICollectionViewDelegateFlowLayout 클래스를 상속해야합니다. 그런 다음 함수를 추가하십시오.

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

그것을 사용하면 화면 너비의 너비 크기가 있습니다.

그리고 이제 tableViewController로 행이있는 collectionViewController가 있습니다.

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


7
이것은 정확히 질문에 대한 답이 아닙니다. 100의 고정 높이를 제공합니다. 즉, 자동 레이아웃이 존재하기 전에 프로그래밍 된 방식입니다.
Fattie 2017-06-05
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.