it-swarm.com.ru

В iOS 12, когда используются ячейки макета UICollectionView, используйте autolayout в 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

27
EvilHydra

Обратите внимание, что для всех решений нет необходимости явно вызывать 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

(несовершенный, см. улучшение DHennessy13 на нем)

На основании Питер Лапису ответ . 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/StackOverflow51375566/tree/AutoLayoutCollectionView .

Решение 4

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

Решение 5

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

20
Cœur

Вот еще одно решение, которое работает на примере кода 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 from UICollectionViewFlowLayouttimateItemSize не работает должным образом с iOS12, хотя он прекрасно работает с iOS 11. *

19
Larry

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

Смотрите также мой ответ здесь: https://stackoverflow.com/a/51585910/341994 \

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

Способ only такого рода состоит в том, чтобы реализовать метод делегата sizeForItemAt и указать размер self. Вы можете легко сделать это, позвонив 

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
}
9
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()
}
8
Samantha

Решение 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
DHennessy13

У нас была такая же проблема в нашем проекте. Мы также заметили различия между несколькими устройствами в 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).

0
Toka

Попробуй это

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

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

Добавление в viewDidAppear и viewWillAppear, конечно, будет работать. Но viewDidAppear вызовет глюк у пользователя.

0
Sreedeepkesav M S