[英]Swift: How to refresh UICollectionView layout after rotation of the device
我使用 UICollectionView (flowlayout) 构建了一个简单的布局。 使用self.view.frame.width
将每个单元格的宽度设置为屏幕宽度
但是当我旋转设备时,单元格不会更新。
我找到了一个 function,它在方向改变时被调用:
override func willRotateToInterfaceOrientation(toInterfaceOrientation:
UIInterfaceOrientation, duration: NSTimeInterval) {
//code
}
但我无法找到更新 UICollectionView 布局的方法
主要代码在这里:
class ViewController: UIViewController , UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout{
@IBOutlet weak var myCollection: UICollectionView!
var numOfItemsInSecOne: Int!
override func viewDidLoad() {
super.viewDidLoad()
numOfItemsInSecOne = 8
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
//print("orientation Changed")
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numOfItemsInSecOne
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellO", forIndexPath: indexPath)
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
let itemSize = CGSize(width: self.view.frame.width, height: 100)
return itemSize
}}
添加此功能:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myCollection.collectionViewLayout.invalidateLayout()
}
当您更改方向时,将调用此函数。
更好的选择是调用invalidateLayout()
而不是reloadData()
因为它不会强制重新创建单元格,因此性能会稍微好一点:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
myCollection.collectionViewLayout.invalidateLayout()
}
您也可以通过这种方式使其无效。
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[self.collectionView.collectionViewLayout invalidateLayout];
}
viewWillLayoutSubviews() 对我不起作用。 viewDidLayoutSubviews() 也没有。 两者都使应用程序进入无限循环,我使用打印命令进行了检查。
工作的方法之一是
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Reload here
}
要更新UICollectionViewLayout
,也可以使用traitCollectionDidChange
方法:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard previousTraitCollection != nil else { return }
collectionView?.collectionViewLayout.invalidateLayout()
}
当UICollectionLayout
检测到边界更改时,它会询问是否需要重新路由 Invalidate 布局。 您可以直接重写该方法。 UICollectionLayout
可以在适当的时候调用invalidateLayout
方法
class CollectionViewFlowLayout: UICollectionViewFlowLayout{
/// The default implementation of this method returns false.
/// Subclasses can override it and return an appropriate value
/// based on whether changes in the bounds of the collection
/// view require changes to the layout of cells and supplementary views.
/// If the bounds of the collection view change and this method returns true,
/// the collection view invalidates the layout by calling the invalidateLayout(with:) method.
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return (self.collectionView?.bounds ?? newBounds) != newBounds
}
}
请理解这一点
traitCollectionDidChange(previousTraitCollection :) 在 iPad 旋转时不会被调用,因为尺寸类在纵向和横向都是 .regular 。 只要集合视图大小发生变化,就会调用 viewWillTransition(to:with:) 。
此外,如果您的应用程序支持多任务处理,您不应该使用 UIScreen.mainScreen().bounds,因为它可能不会占据整个屏幕,最好使用 collectionView.frame.width。
调用viewWillLayoutSubviews
不是最佳选择。 首先尝试调用invalidateLayout()
方法。
如果您遇到The behaviour of the UICollectionViewFlowLayout is not defined
错误,您需要根据新布局验证视图中的所有元素是否已更改其大小。 (请参阅示例代码中的可选步骤)
这是代码,让你开始。 根据创建 UI 的方式,您可能必须尝试找到正确的视图来调用recalculate
方法,但这应该会引导您迈出第一步。
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
/// (Optional) Additional step 1. Depending on your layout, you may have to manually indicate that the content size of a visible cells has changed
/// Use that step if you experience the `the behavior of the UICollectionViewFlowLayout is not defined` errors.
collectionView.visibleCells.forEach { cell in
guard let cell = cell as? CustomCell else {
print("`viewWillTransition` failed. Wrong cell type")
return
}
cell.recalculateFrame(newSize: size)
}
/// (Optional) Additional step 2. Recalculate layout if you've explicitly set the estimatedCellSize and you'll notice that layout changes aren't automatically visible after the #3
(collectionView.collectionViewLayout as? CustomLayout)?.recalculateLayout(size: size)
/// Step 3 (or 1 if none of the above is applicable)
coordinator.animate(alongsideTransition: { context in
self.collectionView.collectionViewLayout.invalidateLayout()
}) { _ in
// code to execute when the transition's finished.
}
}
/// Example implementations of the `recalculateFrame` and `recalculateLayout` methods:
/// Within the `CustomCell` class:
func recalculateFrame(newSize: CGSize) {
self.frame = CGRect(x: self.bounds.origin.x,
y: self.bounds.origin.y,
width: newSize.width - 14.0,
height: self.frame.size.height)
}
/// Within the `CustomLayout` class:
func recalculateLayout(size: CGSize? = nil) {
estimatedItemSize = CGSize(width: size.width - 14.0, height: 100)
}
/// IMPORTANT: Within the `CustomLayout` class.
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
guard let collectionView = collectionView else {
return super.shouldInvalidateLayout(forBoundsChange: newBounds)
}
if collectionView.bounds.width != newBounds.width || collectionView.bounds.height != newBounds.height {
return true
} else {
return false
}
}
我的代码:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.collectionView.collectionViewLayout.invalidateLayout()
}
会正常工作!!!
您可以使用更新您的 UICollectionView 布局
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if isLandscape {
return CGSizeMake(yourLandscapeWidth, yourLandscapeHeight)
}
else {
return CGSizeMake(yourNonLandscapeWidth, yourNonLandscapeHeight)
}
}
它对我有用。 这是 Objective-C 中的代码:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[collectionView.collectionViewLayout invalidateLayout];
}
我也遇到了一些问题,但后来使用以下方法解决了:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionViewFlowLayoutSetup(with: view.bounds.size.width)
collectionView?.collectionViewLayout.invalidateLayout()
collectionViewFlowLayoutSetup(with: size.width)
}
fileprivate func collectionViewFlowLayoutSetup(with Width: CGFloat){
if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: Width, height: 300)
}
}
我通过在屏幕方向更改时设置通知并重新加载根据屏幕方向设置 itemsize 并将 indexpath 设置为前一个单元格来解决此问题。 这也适用于 flowlayout。 这是我写的代码:
var cellWidthInLandscape: CGFloat = 0 {
didSet {
self.collectionView.reloadData()
}
}
var lastIndex: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
cellWidthInLandscape = UIScreen.main.bounds.size.width
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func rotated() {
// Setting new width on screen orientation change
cellWidthInLandscape = UIScreen.main.bounds.size.width
// Setting collectionView to previous indexpath
collectionView.scrollToItem(at: IndexPath(item: lastIndex, section: 0), at: .right, animated: false)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
// Getting last contentOffset to calculate last index of collectionViewCell
lastIndex = Int(scrollView.contentOffset.x / collectionView.bounds.width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Setting new width of collectionView Cell
return CGSize(width: cellWidthInLandscape, height: collectionView.bounds.size.height)
}
我使用以下方法解决了这个问题
override func viewDidLayoutSubviews() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
collectionView.collectionViewLayout.invalidateLayout()
collectionView.collectionViewLayout = flowLayout
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
yourCollectionView?.setCollectionViewLayout(UICollectionViewLayout(), animated: true)
}
除了其他人已经提到的内容之外,如果您自己创建集合视图而不是继承 UICollectionViewController,请不要忘记设置:
collectionView.translatesAutoresizingMaskIntoConstraints = false
设置约束时。
同时,在横向更改为纵向方向后,所选单元格未居中。 我这样解决这个问题:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
//Scroll to current Item after the orientation change. Sometimes the current item is not centered.
coordinator.animate(alongsideTransition: { context in
self.housesCollectionView.collectionViewLayout.invalidateLayout()
self.housesCollectionView.scrollToItem(at: self.selectedHouseIndexPath, at: .left, animated: true)
})
}
试试这个:
class CollectionViewFlowLayout: UICollectionViewFlowLayout {
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
if let collectionView = collectionView {
context.invalidateFlowLayoutDelegateMetrics = collectionView.bounds.size != newBounds.size
}
return context
}
}
文件:
此属性的默认值为 false。 如果由于任何项目的大小更改而使布局无效,请将此属性设置为 true。 当此属性设置为 true 时,流布局对象会重新计算其项目和视图的大小,并根据需要查询委托对象以获取该信息。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.