web-dev-qa-db-ja.com

iOS 12では、UICollectionViewレイアウトセルは、nibでautolayoutを使用します

このような同じコード

collectionLayout.estimatedItemSize = CGSize(width: 50, height: 25)
collectionLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
collectionLayout.minimumInteritemSpacing = 10 

for _ in 0 ..< 1000 {
    let length = Int(arc4random() % 8)
    let string = randomKeyByBitLength(length)
    array.append(string!)
}
collectionView.reloadData()

セルの制約:

enter image description here

iOS 12で実行すると、違います。左のシミュレータはiOS 11、右のシミュレータはiOS 12です。

enter image description here

しかし、スクロールすると、セルのフレームは正常になります。


問題を再現するサンプルプロジェクト: https://github.com/Coeur/StackOverflow51375566

40
EvilHydra

すべてのソリューションについて、reloadDataviewDidLoadを明示的に呼び出す必要がないことに注意してください。これは自動的に行われます。

解決策1

Samantha ideainvalidateLayoutに非同期でインスパイアされたviewDidLoad

override func viewDidLoad() {
    super.viewDidLoad()

    //[...]

    for _ in 0 ..< 1000 {
        array.append(randomKeyByBitLength(Int(arc4random_uniform(8)))!)
    }

    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

解決策2

(不完全、DHennessy13の改善を参照)

Peter Lapisu answer に基づいています。 invalidateLayout in viewWillLayoutSubviews

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    collectionView.collectionViewLayout.invalidateLayout()
}

DHennessy13で述べたように、viewWillLayoutSubviewsを使用したこの現在のソリューションは、画面を回転させるときにレイアウトを無効にするため、不完全です。

このソリューションに関して DHennessy13の改善 に従うことができます。

解決策3

タイラー・シェファーの回答Swiftへのショーン・アウクスタック港 とサマンサのアイデアの組み合わせに基づいています。 CollectionViewをサブクラス化して、invalidateLayoutlayoutSubviewsを実行します。

class AutoLayoutCollectionView: UICollectionView {

    private var shouldInvalidateLayout = false

    override func layoutSubviews() {
        super.layoutSubviews()
        if shouldInvalidateLayout {
            collectionViewLayout.invalidateLayout()
            shouldInvalidateLayout = false
        }
    }

    override func reloadData() {
        shouldInvalidateLayout = true
        super.reloadData()
    }
}

このソリューションは、ViewControllerコードを変更する必要がないため、エレガントです。このサンプルプロジェクトのブランチAutoLayoutCollectionViewに実装しました https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView

解決策4

UICollectionViewCellのデフォルトの制約を書き換えます。 Larry answer を参照してください。

解決策5

collectionView(_:layout:sizeForItemAt:)を実装し、cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)を返します。 matt answer を参照してください。

36
Cœur

Cœur のコードサンプルで動作し、他の回答では機能しなかった特定のケースでも機能する別のソリューションを次に示します。以下のコードは、ViewController.SwiftCollectionViewCellサブクラスの以前の実装を置き換えます。

class CollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var label: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()

        contentView.translatesAutoresizingMaskIntoConstraints = false

        let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor)
        let rightConstraint = contentView.rightAnchor.constraint(equalTo: rightAnchor)
        let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor)
        let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint])
    }
}

これは ale84 from ICollectionViewFlowLayout expectedItemSizeはiOS11では正常に動作しますがiOS12では正常に動作しません* による回答に触発されました

27
Larry

問題は、ここに配置されている機能(内部制約に基づいてサイズを調整するコレクションビューセル)が存在しないことです存在しないneverは存在していません。 Appleはそうだと主張しますが、そうではありません。コレクションビューが導入され、この主張が最初になされて以来、私は毎年これについてバグを報告しています。バグは本物だからです。自己サイズのコレクションビューセルのようなものはありません。

こちらの私の回答もご覧ください: https://stackoverflow.com/a/51585910/341994 \

数年で、自己サイズのセルを使用しようとしてクラッシュしました。他の年では、クラッシュしませんが、レイアウトが間違っています。ただし、は機能しません

このようなことを行うonly方法は、デリゲートメソッドsizeForItemAtを実装し、サイズyourself。簡単に呼び出すことができます

cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)

事前に設定したモデルセル上。それが、ランタイムshouldがあなたのためにすべきことですが、そうではありません。

だから、ここに元の質問に対する私の解決策があります。文字列の単純な配列の代わりに、文字列とサイズのペアの配列を(タプルとして)生成しました。次に:

override func collectionView(_ collectionView: UICollectionView, 
    cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! MyCell
        cell.label.text = self.array[indexPath.row].0
        return cell
}

func collectionView(_ collectionView: UICollectionView, 
    layout collectionViewLayout: UICollectionViewLayout, 
    sizeForItemAt indexPath: IndexPath) -> CGSize {
        return self.array[indexPath.row].1
}
20
matt

私は同じ問題を抱えています。セルはスクロールされるまで(自動サイズの代わりに)推定サイズを使用します。 Xcode 9.xでビルドされた同じコードはiOS 11および12で完全に動作し、Xcode 10でビルドされたiOS 11では正しく動作しますが、iOS 12では動作しません。

これを解決する唯一の方法は、viewDidAppearでコレクションビューのレイアウトを無効にすることです。これにより、多少のびびりが発生するか、viewWillAppear内の非同期ブロックで(ソリューションの信頼性が不明です)。

override func viewDidLoad() {
    super.viewDidLoad()
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.estimatedItemSize = CGSize(width: 50, height: 50)
    layout?.itemSize = UICollectionViewFlowLayout.automaticSize
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // The following block also "fixes" the problem without jumpiness after the view has already appeared on screen.
    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // The following line makes cells size properly in iOS 12.
    collectionView.collectionViewLayout.invalidateLayout()
}
9
Samantha

Cœur のソリューション2は、レイアウトがユーザーの前で点滅したり更新したりするのを防ぎます。ただし、デバイスを回転させると問題が発生する可能性があります。 viewWillLayoutSubviewsで変数「shouldInvalidateLayout」を使用し、viewDidAppearでfalseに設定しています。

private var shouldInvalidateLayout = true

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    shouldInvalidateLayout = false
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    if shouldInvalidateLayout {
        collectionView.collectionViewLayout.invalidateLayout()
    }
}
6
DHennessy13

これを試して

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    DispatchQueue.main.async {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
}

viewDidAppearおよびviewWillAppearへの追加はもちろん機能します。ただし、viewDidAppearはユーザーに誤作動を引き起こします。

1

私たちのプロジェクトでも同じ問題がありました。また、iOS 12の複数のデバイスの違いに気づき、layoutIfNeededinvalidateLayoutの呼び出しが必要になりました。このソリューションは@ DHennessy13のアプローチに基づいていますが、少しハックのように見える状態を管理するためにブール値を必要としません。

ここでは、Rxコードに基づいています。基本的に、最初の行はデータが変更されるときです。subscribe内は、厄介なiOS 12 UIグリッチを修正するために必要なことです。

        viewModel.cellModels.asObservable()
            .subscribe(onNext: { [weak self] _ in
                // iOS 12 bug in UICollectionView for cell size
                self?.collectionView.layoutIfNeeded()

                // required for iPhone 8 iOS 12 bug cell size
                self?.collectionView.collectionViewLayout.invalidateLayout()
            })
            .disposed(by: rx.disposeBag)

編集:

ところで、それはiOS 12の既知の問題のようです: https://developer.Apple.com/documentation/ios_release_notes/ios_12_release_notes (UIKitセクション)。

0
Toka