2015-09-04 14 views
14

我正在开发Android上已开发的应用程序的iOS版本。此应用具有自上浆(固定的宽度,但不同的高度)的下面2列的网格单元:如何开发自定义UICollectionViewLayout,它具有与自定义单元格交错的列?

Staggered 2 column layout on Android

在Android版本实现,这是容易的,因为谷歌提供了一种用于其RecyclerView一个StaggeredGridLayoutManager。您可以指定列数和滚动方向,然后完成。

默认UICollectionView布局UICollectionViewFlowLayout不允许交错的布局我在找,所以我必须要实现自定义布局。我观看了2个关于这个主题的WWDC视频(What's New in Table and Collection ViewsAdvanced User Interfaces with Collection Views),我或多或少都知道它应该如何实现。

第1步。首先计算布局的近似值。

enter image description here

步骤2.然后将细胞被创建并与自动布局尺寸。

enter image description here

步骤3.然后这样的布局被更新,控制器通知的小区尺寸。

enter image description here

我的疑惑来试图代码这些步骤时。我找到了一个tutorial,它解释了创建具有交错列的自定义布局,但它不使用自动布局来获取单元格的大小。这给我留下了以下问题:

在第2步中,如何以及何时可以获取单元格大小?

在步骤3中,我如何以及何时可以通知更改的布局?

+0

嗨根据你的问题,你可以创建[UICollectionView](http://www.appcoda.com/ios-programming-uicollectionview-tutorial/)。 但用[自定义单元格](http://corsarus.com)创建自定义[UICollectionViewLayout](http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12)/2015 /收集 - 视图 - 用自施胶细胞/)。 –

回答

2

我意识到这不是一个完整的答案,但有关您的步骤2和3的一些指针可以在UICollectionViewLayout的子类注释中找到。

我认为你已经划分了UICollectionViewFlowLayout,因为我的头顶上我相信这是一个很好的起点调整布局以获得你想要的交错外观。

对于第2步layoutAttributesForElementsInRect(_:)应提供自我大小的单元格的布局属性。

对于第3步,您的布局将使用更改的单元格大小调用shouldInvalidateLayoutForPreferredLayoutAttributes(_:withOriginalAttributes:)

4

我想指出的是,正如你所提到的,RayWenderlich PinInterest Layout正是帮助你实现这种布局的教程。

回答您的问题 - 有关于本教程:

在步骤2,我什么时候能获得细胞大小和如何?

为了获得单元格高度,实现了在自定义UICollectionViewLayoutprepareLayout方法中调用的委托方法。这种方法被称为一次(或两次,我只是试图运行它与print声明,我有两个电话)。 prepareLayout的要点是初始化单元格的frame属性,换句话说,提供每个单元格的确切大小。我们知道,宽度是恒定的,并且仅height是变化的,所以在这条线的prepareLayout

let cellHeight = delegate.collectionView(collectionView!, 
     heightForItemAtIndexPath: indexPath, withWidth: width) 

我们获得从在该实施UICollectionViewController委托方法的单元的高度。这发生在我们想要在collectionView中显示的所有单元格中。在获取并修改每个单元格的高度后,我们会缓存结果以便稍后检查。

之后,对于collectionView来获取屏幕上每个单元格的大小,它所需要做的就是查询缓存中的信息。这是在您的自定义UICollectionViewLayout类的layoutAttributesForElementsInRect方法中完成的。

该方法由UICollectionViewController自动调用。当UICollectionViewController需要屏幕上显示的单元格的布局信息时(例如滚动或第一次加载时),您将从缓存中返回已填充到prepareLayout中的属性。

总结您的问题:在第2步中,如何以及何时可以获取单元大小?在您的自定义UICollectionViewFlowLayoutprepareLayout方法内获得每个单元的大小,并在您的UICollectionView的生命周期的早期计算:

答案。

在步骤3中,我如何以及何时可以通知更改的布局?

请注意,本教程不考虑新的细胞来在运行时补充说:

注:由于prepareLayout()被调用时集合视图的布局是无效的,也有一个典型的许多情况下,在这里你可能需要重新计算属性。例如,UICollectionView的边界可能会改变 - 例如,当方向改变时 - 或者项目可能被添加或从集合中移除。这些情况超出了本教程的范围,但重要的是在一个不平凡的实现中注意它们。

就像他写的,这是一个不平凡的实现,你可能需要。然而,如果您的数据集很小(或用于测试目的),您可能会采用一个微不足道的(非常低效的)实现。当由于屏幕旋转或添加/删除单元而需要使布局无效时,可以清除自定义UICollectionViewFlowLayout中的缓存,以强制prepareLayout重新初始化布局属性。

例如,当你要调用reloadData上的CollectionView,也使您的自定义布局类的电话,删除缓存:

cache.removeAll() 
+0

他们是,根据我写的和根据教程。所有大小的计算都在'prepareLayout'中完成。 –

0

的“单元格大小”由UICollectionViewLayoutAttribute的定义布局子类,这意味着您可以在每次有机会触摸它时修改它。您可以将每个属性的大小设置为您所需的大小。

例如,您可以在layoutAttributesOfElementsInRect(:)中执行此操作,在将它们传递给collectionView之前计算正确的大小并配置所有属性。您也可以在layoutAttributeOfItemAtIndexPath(:)中执行此操作,在创建每个属性时进行计算。

此外,考虑通过数据源提供所需的大小,以便每个属性可以很容易地通过索引获取它们的大小。

因为如果你想拥有的单元尺寸来布局子视图在一个单元格,执行它在的CollectionView委托方法:的CollectionView:ItemAtIndexPath:

希望这有助于。

2

First example of custom layout

在步骤2中,我什么时候能获得小区大小如何和? 您需要计算prepareLayout()方法中每个单元的高度。每个单元格的计算结果应该分配给UICollectionViewLayoutAttributes变量,并将其放入集合NSDictionary中,其中键为NSIndexPath(每个单元格),值为UICollectionViewLayoutAttributes变量。

例子:

- (void)prepareLayout { 
    [_layoutMap removeAllObjects]; 
    _totalItemsInSection = [self.collectionView numberOfItemsInSection:0]; 
    _columnsYoffset = [self initialDataForColumnsOffsetY]; 

    if (_totalItemsInSection > 0 && self.totalColumns > 0) { 
     [self calculateItemsSize]; 

     NSInteger itemIndex = 0; 
     CGFloat contentSizeHeight = 0; 

     while (itemIndex < _totalItemsInSection) { 
      NSIndexPath *targetIndexPath = [NSIndexPath indexPathForItem:itemIndex inSection:0]; 
      NSInteger columnIndex = [self columnIndexForItemAtIndexPath:targetIndexPath]; 

      // you need to implement this method and perform your calculations 
      CGRect attributeRect = [self calculateItemFrameAtIndexPath:targetIndexPath]; 
      UICollectionViewLayoutAttributes *targetLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:targetIndexPath]; 
      targetLayoutAttributes.frame = attributeRect; 

      contentSizeHeight = MAX(CGRectGetMaxY(attributeRect), contentSizeHeight); 
      _columnsYoffset[columnIndex] = @(CGRectGetMaxY(attributeRect) + self.interItemsSpacing); 
      _layoutMap[targetIndexPath] = targetLayoutAttributes; 

      itemIndex += 1; 
     } 

     _contentSize = CGSizeMake(self.collectionView.bounds.size.width - self.contentInsets.left - self.contentInsets.right, 
            contentSizeHeight); 
    } 
} 

不要忘记实现以下方法:当你调用reloadData()mehtod或的invalidateLayout()

- (NSArray <UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { 
    NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributesArray = [NSMutableArray new]; 

    for (UICollectionViewLayoutAttributes *layoutAttributes in _layoutMap.allValues) { 
     if (CGRectIntersectsRect(layoutAttributes.frame, rect)) { 
      [layoutAttributesArray addObject:layoutAttributes]; 
     } 
    } 

    return layoutAttributesArray; 
} 

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 
    return _layoutMap[indexPath]; 
} 

这些方法将被触发。

在步骤3中,我如何以及何时可以通知更改的布局? 只需调用self.collectionView.collectionViewLayout.invalidateLayout()和prepareLayout()方法就会再次被调用,因此您可以重新计算所需的所有参数。

你可以找到我的有关自定义UICollectionViewLayout完全教程这里https://octodev.net/custom-collectionviewlayout/

教程包含两种语言实现:斯威夫特和Objective-C。 会很乐意回答你所有的问题。