全幅セルのUICollectionViewは、自動レイアウトの動的な高さを許可しますか?


120

垂直ではUICollectionView

全幅のセルを持つことは可能ですが、動的な高さ自動レイアウトで制御できますか?

これは、おそらく「本当に良い答えがないiOSで最も重要な質問」だと私は思います。


重要:

99%のケースでは、全幅のセル+自動レイアウトの動的な高さを実現するには、テーブルビューを使用するだけです。とても簡単です。


では、コレクションビューが必要な場所の例は何でしょうか。

コレクションビューは、テーブルビューよりも信じられないほど強力です。

全幅のセル+自動レイアウトの動的な高さを実現するために実際にコレクションビュー使用する必要がある簡単な例:

コレクションビューで2つのレイアウト間をアニメーション化する場合。たとえば、デバイスが回転するときの1列と2列の間のレイアウト。

これは、iOSで一般的で通常のイディオムです。残念ながら、それはこのQAで提起された問題を解決することによってのみ達成できます。:-/


はい、UICollectionViewDelegateFlowLayoutクラスで可能です
Sunil Prajapati

これは、実際にtableViewを使用できることを意味しませんか?これを複雑にするには、どのような追加機能が必要ですか?
カタリナ

1
こんにちは@CatalinaT。、コレクションビューには、テーブルビューよりも多くの機能が追加されています。簡単な例は、物理の追加です。(私が何を言っているのかわからない場合は、iOSで「弾む」リストを作成する方法です。)
Fattie

あなたも何とか物理学(のように適用できるSpringDampinginitialSpringVelocity,,,,のtableViewを使用して、この弾むリストを見て.....表のセル内)をpastebin.com/pFRQhkrX あなたがアニメーション化できるTableViewCellの表にwillDisplayCell:(UITableViewCell *)cell、あなたが受け入れることができる@Fattie delgate方法同じ問題を探している他の人を助けるためにあなたの問題を解決した答え
Dhiru

ああ、あなたはリストビューを使ってバウンシーリストを試しましたか?@Fattie
Dhiru

回答:


122

1. iOS 13以降のソリューション

Swift 5.1とiOS 13では、問題を解決するために構成レイアウトオブジェクトを使用できます。

次の完全なサンプルコードは、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全幅の内側に複数行を表示する方法を示していますUIcollectionViewCellUICollectionView「安全領域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 2018年

どうもありがとうございます。あなたは私の週を救った。🙇‍♂️
ガエタン

1
サイズが変わるたびにCollectionViewが上にスクロールします
Sajith

1
素晴らしいニュース、@ ImanouPetit!賞金がもっと大きければいいのに!コンポジションレイアウトオブジェクトに関する素晴らしい情報、ありがとうございます。
Fattie

1
こんにちは、私はこれを試しています。上記のようにviewdidloadでレイアウトを設定していますが、collectionviewを非nilレイアウトパラメーターで初期化する必要があることを示すクラッシュが発生します。行方不明のものはありますか?これはiOS 13向けのXcode 11です
。– geistmate

29

問題

あなたは自動高さを探していて、幅をフルにしたいのですが、両方を使用することはできませんUICollectionViewFlowLayoutAutomaticSize

使用したいUICollectionViewので、以下が解決策です。

解決

ステップI:セルの予想される高さを計算する

1.を設定しただけUILabel で、の期待される高さを計算した場合は、3つのパラメーターすべてを渡します。CollectionViewCellnumberOfLines=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

同じですが、半分の幅と動的な高さを実現したい
Mihir Mehta

return CGSize(width: view.frame.width/2, height: expectedHeight)また、幅を返すよりもパディングを考慮に入れてください。そうしないと、2つのセルが1行に収まりません。
Dhiru

@ nr5解決策を得ましたか?私の要件はあなたと同じです。ありがとう。
Maulik Bhuptani、

@MaulikBhuptani自動レイアウトでは完全にはできませんでした。カスタムLayoutクラスを作成し、いくつかのクラスメソッドをオーバーライドする必要がありました。
nr5 2018年

18

この問題に対処する方法はいくつかあります。

1つの方法は、コレクションビューのフローレイアウトに推定サイズを与え、セルサイズを計算することです。

注:以下のコメントで述べたように、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」が動的にサイズ設定されるコレクションビューのセルとどのように関連しているかが不明なので、ご意見をお聞かせください。(皮肉ではない)
Eric Murphey 2017年

さらに、この回答に役立つ具体的な情報や標準的なものがない場合は、お知らせください。賞金については主張しませんが、この質問を閲覧する他の人の役に立ちたいと思います。
Eric Murphey 2017年

1
(estimatedItemSizeを含めても含めなくても、現在のiOSでは違いがなく、どちらにしても、「全幅+動的高さ」の問題ではまったく役に立ちません。)最後に、nibはほとんど使用されておらず、動的な高さとの関連性が低い(たとえば、いくつかの動的な高さのテキストビューから)。ありがとう
Fattie

私もこの解決策にたどり着きましたが、私の場合、iOS 11、Xcode 9.2では、セルのコンテンツがセル自体から切り離されているように見えました-画像を固定サイズの高さまたは幅いっぱいに拡大できませんでした。さらに、CVはの設定の制約であることに気付きましたpreferredLayoutAttributesFitting。次に、上記の関数に独自の制約を追加し、estimatedItemSize、FWIWからの固定次元にバインドしました。以下の私の回答を参照してください。
Chris Conover、2017

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 2018

1
TBC @ inf1783だけ、UICollectionViewでこれを行っていますか?(テーブルビューではありません)-正しいですか?
Fattie、2018

1
@Fattieはい、それはUICollectionViewです;)
inf1783 2018

素晴らしい@ 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))
    }

}

主な成分は、CustomcellのsystemLayoutSizeFitting関数です。また、制約付きでセル内のビューの幅を設定する必要があります。


6

CollectionViewCellに幅の制約を追加する必要があります

class SelfSizingCell: UICollectionViewCell {

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

最終的に誰かが2行で完璧に
仕上げました

これにより、セルが固定幅に制限され、セルフサイズはほとんどありません。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を使用する最良の方法を見つけましたが、各セルは異なるサイズを持つことができますが、実際のセルを使用してサイズを測定しながら、UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath関数を実装することです。

私は私のブログ投稿の一つでこれについて話しました

うまくいけば、これはあなたが望むものを達成するのに役立ちます。100%確実ではありませんが、AutoLayoutと組み合わせてセルの高さを完全に自動化できるUITableViewとは異なり、

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionViewCellは必ずしも画面の幅全体を埋めるわけではないため、UICollectionViewにはAutoLayoutにサイズを決定させるような方法はありません。

しかし、ここであなたに質問があります:全画面幅のセルが必要な場合、自動サイズ変更セルが付属している古き良きUITableViewよりもUICollectionViewを使用するのはなぜですか?


それを保持し、以下のApritは、UICollectionViewFlowLayoutAutomaticSizeがフローレイアウトで、iOS10で利用できるようになったと言っています...
Fattie

私もこれを見ました。明らかにそれはiOS 10でのことです。私はこれについての対応するWWDCの話を見ました:developer.apple.com/videos/play/wwdc2016/219ただし、これは完全にセルをコンテンツに合わせて自動サイズ調整します。UICollectionViewCellとその親(少なくともStoryBoardsではない)の間に制約を設定できないため、セルに(AutoLayoutを使用して)画面の幅を満たすように指示する方法がわかりません
xxtesaxx

正しい。私たちはこの信じられないほど明白な問題の実際の解決策に近づいていないようです。:/
Fattie

話によると、それでもsizeThatFits()またはpreferredLayoutAttributesFitting()を上書きして自分でサイズを計算することはできますが、それはOPが求めていることではないと思います。とにかく、私は彼/彼女が全幅のUICollectionViewCellを必要とする場合のユースケースと、この特定のケースでUITableViewを使用しないことがなぜ非常に大きな問題になるのか(特定のケースがあることを確認してから、私は推測します)自分でいくつかの計算を行う必要があります)
xxtesaxx

考えてみると、コレクションビューで "columns == 1"(デバイスが横向きの場合はおそらくcolumn == 2)に設定できないのは非常にすばらしいことです。
Fattie

3

エリックの答えに関する私のコメントによると、私の解決策は彼の解決策に非常に似ていますが、固定サイズに制約するために、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マークだけでは、実際にセルのサイズを変更しているのですか?(つまり、セルが表示され、サイズがXの場合、アプリで何かが発生し、サイズがYになる..?)thx
Fattie

はい。ユーザーが画面上の要素をドラッグすると、セルのサイズを更新するイベントが発生します。
マークスマン

1

viewDidLayoutSubviewsestimatedItemSizeを全幅に設定します(レイアウトは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です。

ソース


うーん、あなたはそれが*全幅になることをかなり確信していますか?それで、本質的に1つのカラム?
Fattie

UICollectionViewCellの幅を明示的に全幅に設定した場合に発生することがあります。
Arpit Dongre 2017年

いいえ、それは幅を完全な高さに固定せず、上記のエリックの答えに従うか、または下/後で私の場合を除いて、推定サイズ値を無視します。
Chris Conover、2017

OK、残念ながら、これは問題とはまったく関係がありません!へえ!:O
Fattie

0

AutoLayoutは、2つの簡単なステップでCollectionViewのセルの自動サイズ調整に使用できます。

  1. 動的セルサイズ設定を有効にする

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. コンテナービューを作成し、containerView.widthAnchor.constraintをfrom 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の固定された高さを与えます、すなわち、それはautolayoutが存在する前にプログラムされた方法です。
Fattie
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.