Swift:如何在设备旋转后刷新UICollectionView布局

时间:2016-04-27 08:30:25

标签: ios swift uicollectionview flowlayout

我使用UICollectionView(flowlayout)来构建一个简单的布局。 使用self.view.frame.width

将每个单元格的宽度设置为屏幕宽度

但是当我旋转设备时,单元格不会更新。

enter image description here

我找到了一个函数,在调整方向时会调用它:

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

15 个答案:

答案 0 :(得分:50)

添加此功能:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews() 
    myCollection.collectionViewLayout.invalidateLayout()
}

更改方向时,将调用此函数。

答案 1 :(得分:42)

更好的选择是调用invalidateLayout()而不是reloadData(),因为它不会强制重新创建单元格,因此性能会略好一些:

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

答案 2 :(得分:11)

此外,您可以通过这种方式使其无效。

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [self.collectionView.collectionViewLayout invalidateLayout]; 
}

答案 3 :(得分:8)

viewWillLayoutSubviews()对我不起作用。 viewDidLayoutSubviews()也没有。两者都使应用程序进入无限循环,我使用打印命令检查。

其中一种方法是

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Reload here
}

答案 4 :(得分:4)

也可以使用

来更新UICollectionViewLayout traitCollectionDidChange方法:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)

    guard let previousTraitCollection = previousTraitCollections else {
        return
    }
    collectionView?.collectionViewLayout.invalidateLayout()
}

答案 5 :(得分:3)

您可以使用

更新您的UICollectionView布局
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    if isLandscape {
        return CGSizeMake(yourLandscapeWidth, yourLandscapeHeight)
    }
    else {
        return CGSizeMake(yourNonLandscapeWidth, yourNonLandscapeHeight)
    }
}

答案 6 :(得分:3)

我的代码:

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

将正常工作!

答案 7 :(得分:1)

我也遇到了一些问题,但是后来使用

解决了
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)
        }

    }

答案 8 :(得分:1)

我通过设置屏幕方向更改时的通知并重新加载根据屏幕方向设置项目大小的单元格并为上一个单元格设置索引路径来解决此问题。这也适用于flowlayout。这是我写的代码:

use Log;

class CardController extends Controller
{

  public function processcard(Request $request){
    $card_number = $request->card_number;
    $email = $request->email;

    $result = "";

    if($card_number == "ABC004") $nonVar += 4; #simulate an error

    #do processing here in the cards
    #some will have $result = "SUCCESS"
    #some will have $result = "CARD NOT VALID", etc.

    log::info("card number: ".$card_number);
    log::info("activate_status: ".$activate_status);
    log::info("******");
    return response()->json(['card_number' =>  $card_number, 'result' => $result]);
  }

}

答案 9 :(得分:1)

UICollectionLayout检测到边界变化时,会询问是否需要重新布线无效布局。您可以直接重写该方法。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
    }
}

答案 10 :(得分:1)

对我有用。这是Objective-C中的代码:

- (void)viewDidLayoutSubviews {
  [super viewDidLayoutSubviews];
  [collectionView.collectionViewLayout invalidateLayout];
}

答案 11 :(得分:1)

请理解这一点

旋转iPad时将不会在

traitCollectionDidChange(previousTraitCollection :)上调用它,因为在纵向和横向方向上size类都是.regular的。每当集合视图大小更改时,都会调用viewWillTransition(to:with:)。

如果您的应用支持多任​​务处理,因为它可能不会占据整个屏幕,那么您也不应使用UIScreen.mainScreen()。bounds,最好为此使用collectionView.frame.width。

答案 12 :(得分:0)

致电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
        }
    }

答案 13 :(得分:0)

我使用以下方法解决了问题

override func viewDidLayoutSubviews() {
        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            collectionView.collectionViewLayout.invalidateLayout()
            collectionView.collectionViewLayout = flowLayout
        }
    }

答案 14 :(得分:-1)

试试这个:

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 时,流布局对象会重新计算其项目和视图的大小,并根据需要查询委托对象以获取该信息。