In iOS 12, when does the UICollectionView layout cells, use autolayout in nib

后端 未结 7 1653
一整个雨季
一整个雨季 2020-11-28 03:08

Same code like this

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


        
相关标签:
7条回答
  • 2020-11-28 03:17

    The problem is that the feature being posited here — collection view cells that size themselves based on their internal constraints in a UICollectionViewFlowLayout — does not exist. It has never existed. Apple claims that it does, but it doesn't. I have filed a bug on this every year since collection views were introduced and this claim was first made; and my bug reports have never been closed, because the bug is real. There is no such thing as self-sizing collection view cells.

    See also my answer here: https://stackoverflow.com/a/51585910/341994

    In some years, trying to use self-sizing cells has crashed. In other years, it doesn't crash but it gets the layout wrong. But it doesn't work.

    The only way to do this sort of thing is to implement the delegate method sizeForItemAt and supply the size yourself. You can easily do that by calling

    cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
    

    on a model cell that you have configured in advance. That is what the runtime should be doing for you — but it doesn't.

    So here's my solution to the original question. Instead of a simple array of strings, we have generated an array of string-size pairs (as a tuple). Then:

    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
    }
    
    0 讨论(0)
  • 2020-11-28 03:18

    Here's another solution that works on Cœur's code sample, and also worked for my particular case, where the other answers didn't. The code below replaces the previous implementation of the CollectionViewCell subclass in ViewController.swift:

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

    This is inspired by the answer by ale84 from UICollectionViewFlowLayout estimatedItemSize does not work properly with iOS12 though it works fine with iOS 11.*

    0 讨论(0)
  • 2020-11-28 03:21

    Try this

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
    
        DispatchQueue.main.async {
            self.collectionView.collectionViewLayout.invalidateLayout()
        }
    }
    

    Adding to viewDidAppear and viewWillAppear will of course work. But viewDidAppear will cause a glitch gor the user.

    0 讨论(0)
  • 2020-11-28 03:32

    We had the same problem on our project. We also noticed differences between the multiple devices in iOS 12, requiring a call to layoutIfNeeded & invalidateLayout. The solution is based on @DHennessy13 's approach but doesn't require a boolean to manage states that seemed slightly hacky.

    Here it is based on an Rx code, basically the first line is when the data is changing, inside the subscribe is what needs to be done to fix the nasty iOS 12 UI glitch:

            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)
    

    Edit:

    By the way, it seems to be a known issue in iOS 12: https://developer.apple.com/documentation/ios_release_notes/ios_12_release_notes (in UIKit section).

    0 讨论(0)
  • 2020-11-28 03:34

    I have the same problem, cells use the estimated size (instead of automatic size) until scrolled. The same code built with Xcode 9.x runs perfectly fine on iOS 11 and 12, and built in Xcode 10 it runs correctly on iOS 11 but not iOS 12.

    The only way I’ve found so far to fix this is to invalidate the collection view’s layout either in viewDidAppear, which can cause some jumpiness, or in an async block inside viewWillAppear (not sure how reliable that solution is).

    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()
    }
    
    0 讨论(0)
  • 2020-11-28 03:37

    For all solutions, note that there is no need to explicitly call reloadData in viewDidLoad: it will happen automatically.

    Solution 1

    Inspired by Samantha idea: invalidateLayout asynchronously in viewDidLoad.

    override func viewDidLoad() {
        super.viewDidLoad()
    
        //[...]
    
        for _ in 0 ..< 1000 {
            array.append(randomKeyByBitLength(Int(arc4random_uniform(8)))!)
        }
    
        DispatchQueue.main.async {
            self.collectionView.collectionViewLayout.invalidateLayout()
        }
    }
    

    Solution 2

    (imperfect, see DHennessy13 improvement on it)

    Based on Peter Lapisu answer. invalidateLayout in viewWillLayoutSubviews.

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

    As noted by DHennessy13, this current solution with viewWillLayoutSubviews is imperfect as it will invalidateLayout when rotating the screen.

    You may follow DHennessy13 improvement regarding this solution.

    Solution 3

    Based on a combination of Tyler Sheaffer answer, Shawn Aukstak port to Swift and Samantha idea. Subclass your CollectionView to perform invalidateLayout on layoutSubviews.

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

    This solution is elegant as it doesn't require to change your ViewController code. I've implemented it on branch AutoLayoutCollectionView of this sample project https://github.com/Coeur/StackOverflow51375566/tree/AutoLayoutCollectionView.

    Solution 4

    Rewrite UICollectionViewCell default constraints. See Larry answer.

    Solution 5

    Implement collectionView(_:layout:sizeForItemAt:) and return cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize). See matt answer.

    0 讨论(0)
提交回复
热议问题