iOS 12、UICollectionView布局cells时,在nib中使用autolayout
In iOS 12, when does the UICollectionView layout cells, use autolayout in 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()
单元格限制:
当我 运行 它在 iOS 12 时,它是不同的。
左侧模拟器是 iOS 11,右侧是 iOS 12:
但是,当我滚动它时,单元格的框架将是正常的。
我有同样的问题,单元格使用估计大小(而不是自动大小)直到滚动。使用 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()
}
对于所有解决方案,请注意无需在 viewDidLoad
中显式调用 reloadData
:它会自动发生。
解决方案 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改进)
基于Peter Lapisu answer。 invalidateLayout
在 viewWillLayoutSubviews
.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
正如 DHennessy13 所指出的,当前 viewWillLayoutSubviews
的解决方案并不完善,因为它会在旋转屏幕时使布局无效。
关于此解决方案,您可以关注 。
解决方案 3
基于 Tyler Sheaffer answer, Shawn Aukstak port to Swift 和 Samantha 想法的结合。将您的 CollectionView 子类化以在 layoutSubviews
.
上执行 invalidateLayout
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 代码。我已经在这个示例项目 https://github.com/Coeur/Whosebug51375566/tree/AutoLayoutCollectionView.
的分支 AutoLayoutCollectionView 上实现了它
解决方案 4
重写 UICollectionViewCell 默认约束。参见 。
解决方案 5
实施 collectionView(_:layout:sizeForItemAt:)
和 return cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
。参见 。
的解决方案 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()
}
}
我们在项目中遇到了同样的问题。我们还注意到 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(在 UI 工具包部分)。
这是另一个适用于 Cœur 代码示例的解决方案,也适用于我的特定情况,而其他答案则没有。下面的代码替换了 ViewController.swift
中 CollectionViewCell
子类的先前实现:
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 UICollectionViewFlowLayout estimatedItemSize does not work properly with iOS12 though it works fine with iOS 11.*
的回答
试试这个
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
添加到 viewDidAppear
和 viewWillAppear
当然可以。但是 viewDidAppear
会给用户带来故障。
问题在于此处设置的功能 - 根据 UICollectionViewFlowLayout 中的内部约束调整自身大小的集合视图单元 - 不存在。它从未存在过。 Apple 声称它有,但事实并非如此。自从引入集合视图并首次提出此声明以来,我每年都会就此提交错误;而且我的错误报告从未关闭,因为错误是真实的。没有自动调整集合视图单元格之类的东西。
另请参阅我的回答:
在某些年里,尝试使用自动调整大小的单元格失败了。在其他年份,它不会崩溃,但会导致布局错误。但它不起作用。
只有做这种事情的方法是实现委托方法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
}
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])
}}
我试过了,对我有用。试试这个,如果您需要任何帮助,请告诉我。
与此相同的代码
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()
单元格限制:
当我 运行 它在 iOS 12 时,它是不同的。 左侧模拟器是 iOS 11,右侧是 iOS 12:
但是,当我滚动它时,单元格的框架将是正常的。
我有同样的问题,单元格使用估计大小(而不是自动大小)直到滚动。使用 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()
}
对于所有解决方案,请注意无需在 viewDidLoad
中显式调用 reloadData
:它会自动发生。
解决方案 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改进)
基于Peter Lapisu answer。 invalidateLayout
在 viewWillLayoutSubviews
.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
正如 DHennessy13 所指出的,当前 viewWillLayoutSubviews
的解决方案并不完善,因为它会在旋转屏幕时使布局无效。
关于此解决方案,您可以关注
解决方案 3
基于 Tyler Sheaffer answer, Shawn Aukstak port to Swift 和 Samantha 想法的结合。将您的 CollectionView 子类化以在 layoutSubviews
.
invalidateLayout
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 代码。我已经在这个示例项目 https://github.com/Coeur/Whosebug51375566/tree/AutoLayoutCollectionView.
的分支 AutoLayoutCollectionView 上实现了它解决方案 4
重写 UICollectionViewCell 默认约束。参见
解决方案 5
实施 collectionView(_:layout:sizeForItemAt:)
和 return cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
。参见
private var shouldInvalidateLayout = true
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
shouldInvalidateLayout = false
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if shouldInvalidateLayout {
collectionView.collectionViewLayout.invalidateLayout()
}
}
我们在项目中遇到了同样的问题。我们还注意到 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(在 UI 工具包部分)。
这是另一个适用于 Cœur 代码示例的解决方案,也适用于我的特定情况,而其他答案则没有。下面的代码替换了 ViewController.swift
中 CollectionViewCell
子类的先前实现:
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 UICollectionViewFlowLayout estimatedItemSize does not work properly with iOS12 though it works fine with iOS 11.*
的回答试试这个
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
添加到 viewDidAppear
和 viewWillAppear
当然可以。但是 viewDidAppear
会给用户带来故障。
问题在于此处设置的功能 - 根据 UICollectionViewFlowLayout 中的内部约束调整自身大小的集合视图单元 - 不存在。它从未存在过。 Apple 声称它有,但事实并非如此。自从引入集合视图并首次提出此声明以来,我每年都会就此提交错误;而且我的错误报告从未关闭,因为错误是真实的。没有自动调整集合视图单元格之类的东西。
另请参阅我的回答:
在某些年里,尝试使用自动调整大小的单元格失败了。在其他年份,它不会崩溃,但会导致布局错误。但它不起作用。
只有做这种事情的方法是实现委托方法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
}
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])
}}
我试过了,对我有用。试试这个,如果您需要任何帮助,请告诉我。