В iOS 12, когда ячейки макета UICollectionView, используйте автозапуск в nib

Такой же код

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

Ответы

Ответ 1

Для всех решений обратите внимание, что нет необходимости явно вызывать reloadData в viewDidLoad: это произойдет автоматически.

Решение 1

Вдохновлен идеей Саманты: invalidateLayout асинхронно в 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

(imperfect, see DHennessy13 improvement on it)

Основано на ответе Питера Лапису. invalidateLayout в viewWillLayoutSubviews.

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

Как отмечает DHennessy13, это текущее решение с viewWillLayoutSubviews несовершенно, поскольку при вращении экрана будет недействительным Layout.

Вы можете следить за улучшением DHennessy13 в отношении этого решения.

Решение 3

На основе сочетания ответа Тайлера Шеффера, от порта Шона Аукстака до Свифта и идеи Саманты. Подкласс вашего CollectionView для выполнения invalidateLayout на 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()
    }
}

Это элегантное решение, поскольку оно не требует изменения кода ViewController. Я реализовал это в ветке AutoLayoutCollectionView этого примера проекта https://github.com/Coeur/Qaru51375566/tree/AutoLayoutCollectionView.

Решение 4

Перепишите ограничения по умолчанию для UICollectionViewCell. Смотрите Ларри ответ.

Решение 5

Реализуйте collectionView(_:layout:sizeForItemAt:) и верните cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize). Смотрите матовый ответ.

Ответ 2

Здесь другое решение, которое работает на примере кода Cœur, а также работало для моего конкретного случая, когда другие ответы не выполнялись. Код ниже заменяет предыдущую реализацию подкласса CollectionViewCell в 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])
    }
}

Это вызвано ответом ale84 от UICollectionViewFlowLayout, оцененныйItemSize не работает должным образом с iOS12, хотя он отлично работает с iOS 11. *

Ответ 3

Проблема в том, что размещаемая здесь функция - ячейки представлений коллекции, размер которых определяется на основе их внутренних ограничений в UICollectionViewFlowLayout, - не существует. Этого никогда не было. Apple утверждает, что это так, но это не так. Я подал ошибку на этом каждый год, так как представления коллекции были введены, и это требование было сначала сделано; и мои отчеты об ошибках никогда не были закрыты, потому что ошибка реальна. Ячейки представления коллекции с самоизмерением не существуют.

Смотрите также мой ответ здесь: fooobar.com/questions/16054950/...

В некоторые годы попытки использовать саморазмерные ячейки потерпели крах. В другие годы он не падает, но неправильно отображает его. Но это не работает.

Единственный способ сделать это - реализовать метод делегата sizeForItemAt и указать размер самостоятельно. Вы можете легко сделать это, позвонив

cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)

в ячейке модели, которую вы предварительно настроили. Это то, что среда выполнения должна делать для вас, но это не так.

Так что вот мое решение оригинального вопроса. Вместо простого массива строк мы сгенерировали массив пар размера строки (в виде кортежа). Тогда:

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
}

Ответ 4

У меня та же проблема, ячейки используют расчетный размер (вместо автоматического размера) до прокрутки. Тот же код, созданный с Xcode 9.x, отлично работает на iOS 11 и 12, а встроенный Xcode 10 работает корректно на iOS 11, но не на iOS 12.

Единственный способ, который Ive нашел, чтобы исправить это, - это сделать недействительным макет вида коллекций либо в 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()
}

Ответ 5

Решение 2 Cœur предотвращает мигание или обновление макета перед пользователем. Но это может вызвать проблемы при повороте устройства. Я использую переменную "shouldInvalidateLayout" в viewWillLayoutSubviews и устанавливаю ее false в viewDidAppear.

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

Попробуй это

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

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

viewDidAppear добавление в viewDidAppear и viewWillAppear будет работать. Но viewDidAppear вызовет сбой пользователя.

Ответ 7

У нас была такая же проблема в нашем проекте. Мы также заметили различия между несколькими устройствами в iOS 12, требующие вызова layoutIfNeeded & invalidateLayout. Решение основано на подходе @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).