UICollectionView粘滞标题在swift中

我试图创build一个粘性的补充标题,它始终保持最佳状态,并且不会响应滚动事件。 到目前为止,我发现的解决scheme仍然对滚动滚动有所反应,并使用自定义的stream布局进行修复,这可能也是解决我的问题的方法。

我想这样做的原因是,标题在其他地方使用,应该是可重用的。 我希望这可以这样解决,我不必创build一个分离的看法。

正如我在Swift中所做的那样,在Swift中有一个例子会很棒。

对于iOS 9 +最简单的解决scheme,因为它不需要编写UICollectionViewFlowLayout的子类。

在viewController中使用collectionView的viewDidLoad使用以下代码:

let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout layout?.sectionHeadersPinToVisibleBounds = true 

它也被@Antoine暗示。

我发现最终的解决scheme:

使用这个自定义stream程布局,可以修复这个粘性标题:

 class StickyHeaderCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? { var superAttributes: [UICollectionViewLayoutAttributes]? = super.layoutAttributesForElementsInRect(rect) as? [UICollectionViewLayoutAttributes] if superAttributes == nil { // If superAttributes couldn't cast, return return super.layoutAttributesForElementsInRect(rect) } let contentOffset = collectionView!.contentOffset var missingSections = NSMutableIndexSet() for layoutAttributes in superAttributes! { if (layoutAttributes.representedElementCategory == .Cell) { if let indexPath = layoutAttributes.indexPath { missingSections.addIndex(layoutAttributes.indexPath.section) } } } for layoutAttributes in superAttributes! { if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { if let indexPath = layoutAttributes.indexPath { missingSections.removeIndex(indexPath.section) } } } } missingSections.enumerateIndexesUsingBlock { idx, stop in let indexPath = NSIndexPath(forItem: 0, inSection: idx) if let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) { superAttributes!.append(layoutAttributes) } } for layoutAttributes in superAttributes! { if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { let section = layoutAttributes.indexPath!.section let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section) let firstCellIndexPath = NSIndexPath(forItem: 0, inSection: section)! let lastCellIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section)! let (firstCellAttributes: UICollectionViewLayoutAttributes, lastCellAttributes: UICollectionViewLayoutAttributes) = { if (self.collectionView!.numberOfItemsInSection(section) > 0) { return ( self.layoutAttributesForItemAtIndexPath(firstCellIndexPath), self.layoutAttributesForItemAtIndexPath(lastCellIndexPath)) } else { return ( self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstCellIndexPath), self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastCellIndexPath)) } }() let headerHeight = CGRectGetHeight(layoutAttributes.frame) var origin = layoutAttributes.frame.origin origin.y = min(contentOffset.y, (CGRectGetMaxY(lastCellAttributes.frame) - headerHeight)) // Uncomment this line for normal behaviour: // origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttributes.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttributes.frame) - headerHeight)) layoutAttributes.zIndex = 1024 layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size) } } } return superAttributes } override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } } 

要创build一个头像传统一样粘贴的布局,请更改以下行:

 origin.y = min(contentOffset.y, (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)) 

到这一行:

 origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)) 

希望这对其他人有用!

更新

更新来修复崩溃(感谢罗伯特·阿特金斯!)和一些更新Swift 1.2

tvOS和iOS 9

tvOS和iOS 9引入了sectionHeadersPinToVisibleBounds的属性sectionHeadersPinToVisibleBounds

2.0版本适合我

 override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var superAttributes:NSMutableArray = NSMutableArray(array: super.layoutAttributesForElementsInRect(rect)!) as NSMutableArray let contentOffset = collectionView!.contentOffset var missingSections = NSMutableIndexSet() for layoutAttributes in superAttributes { if (layoutAttributes.representedElementCategory == .Cell) { if let indexPath = layoutAttributes.indexPath { missingSections.addIndex(layoutAttributes.indexPath.section) } } } for layoutAttributes in superAttributes{ if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { if let indexPath = layoutAttributes.indexPath { missingSections.removeIndex(indexPath.section) } } } } missingSections.enumerateIndexesUsingBlock { idx, stop in let indexPath = NSIndexPath(forItem: 0, inSection: idx) let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) superAttributes.addObject(layoutAttributes!) } for la in superAttributes { let layoutAttributes = la as! UICollectionViewLayoutAttributes; if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { let section = layoutAttributes.indexPath.section let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section) let firstCellIndexPath = NSIndexPath(forItem: 0, inSection: section) let lastCellIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section) var firstCellAttributes:UICollectionViewLayoutAttributes var lastCellAttributes:UICollectionViewLayoutAttributes if (self.collectionView!.numberOfItemsInSection(section) > 0) { firstCellAttributes = self.layoutAttributesForItemAtIndexPath(firstCellIndexPath)! lastCellAttributes = self.layoutAttributesForItemAtIndexPath(lastCellIndexPath)! } else { firstCellAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstCellIndexPath)! lastCellAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastCellIndexPath)! } let headerHeight = CGRectGetHeight(layoutAttributes.frame) var origin = layoutAttributes.frame.origin origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttributes.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttributes.frame) - headerHeight)) ; layoutAttributes.zIndex = 1024; layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size) } } } return NSArray(array: superAttributes) as? [UICollectionViewLayoutAttributes] } override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } 

我通过testing空白部分的testing来解决我的崩溃问题。 我还添加了一些if let额外的斯威夫特风格点;-)。 这现在适用于我:

 class StickyHeaderCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? { var answer: [UICollectionViewLayoutAttributes] = super.layoutAttributesForElementsInRect(rect)! as [UICollectionViewLayoutAttributes] let contentOffset = collectionView!.contentOffset var missingSections = NSMutableIndexSet() for layoutAttributes in answer { if (layoutAttributes.representedElementCategory == .Cell) { if let indexPath = layoutAttributes.indexPath { missingSections.addIndex(layoutAttributes.indexPath.section) } } } for layoutAttributes in answer { if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { if let indexPath = layoutAttributes.indexPath { missingSections.removeIndex(indexPath.section) } } } } missingSections.enumerateIndexesUsingBlock { idx, stop in let indexPath = NSIndexPath(forItem: 0, inSection: idx) if let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) { answer.append(layoutAttributes) } } for layoutAttributes in answer { if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { let section = layoutAttributes.indexPath!.section let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section) let firstCellIndexPath = NSIndexPath(forItem: 0, inSection: section)! let lastCellIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section)! let (firstCellAttributes: UICollectionViewLayoutAttributes, lastCellAttributes: UICollectionViewLayoutAttributes) = { if (self.collectionView!.numberOfItemsInSection(section) > 0) { return ( self.layoutAttributesForItemAtIndexPath(firstCellIndexPath), self.layoutAttributesForItemAtIndexPath(lastCellIndexPath)) } else { return ( self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstCellIndexPath), self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastCellIndexPath)) } }() let headerHeight = CGRectGetHeight(layoutAttributes.frame) var origin = layoutAttributes.frame.origin origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttributes.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttributes.frame) - headerHeight)) layoutAttributes.zIndex = 1024 layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size) } } } return answer } override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } } 

Swift 2.2版本根据GregP的答案进行testing。 Greg的代码在lastCellIndexPath中抛出一个unwrap可选错误,因为section的数目最初为零。 所以我移动了numberOfItemsInSection> 0检查。

 override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let superAttributes:NSMutableArray = NSMutableArray(array: super.layoutAttributesForElementsInRect(rect)!) as NSMutableArray let contentOffset = collectionView!.contentOffset let missingSections = NSMutableIndexSet() for layoutAttributes in superAttributes { if (layoutAttributes.representedElementCategory == .Cell) { if let _ = layoutAttributes.indexPath { missingSections.addIndex(layoutAttributes.indexPath.section) } } } for layoutAttributes in superAttributes{ if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { if let indexPath = layoutAttributes.indexPath { missingSections.removeIndex(indexPath.section) } } } } missingSections.enumerateIndexesUsingBlock { idx, stop in let indexPath = NSIndexPath(forItem: 0, inSection: idx) let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) superAttributes.addObject(layoutAttributes!) } for la in superAttributes { let layoutAttributes = la as! UICollectionViewLayoutAttributes; if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { let section = layoutAttributes.indexPath.section let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section) if numberOfItemsInSection > 0{ let firstCellIndexPath = NSIndexPath(forItem: 0, inSection: section) let lastCellIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section) var firstCellAttributes:UICollectionViewLayoutAttributes var lastCellAttributes:UICollectionViewLayoutAttributes firstCellAttributes = self.layoutAttributesForItemAtIndexPath(firstCellIndexPath)! lastCellAttributes = self.layoutAttributesForItemAtIndexPath(lastCellIndexPath)! let headerHeight = CGRectGetHeight(layoutAttributes.frame) var origin = layoutAttributes.frame.origin origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttributes.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttributes.frame) - headerHeight)); layoutAttributes.zIndex = 1024; layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size) } } } } return NSArray(array: superAttributes) as? [UICollectionViewLayoutAttributes] } override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } 

如果你只有一个部分,这是一个简单的解决scheme。

 class StickyHeaderLayout: UICollectionViewFlowLayout { override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? { var attributes = super.layoutAttributesForElementsInRect(rect)! as! [UICollectionViewLayoutAttributes] let offset = collectionView?.contentOffset for attrs in attributes { if attrs.representedElementKind == nil { let indexPath = NSIndexPath(forItem: 0, inSection: attrs.indexPath.section) let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) attributes.append(layoutAttributes) } } for attrs in attributes { if attrs.representedElementKind == nil { continue } if attrs.representedElementKind == UICollectionElementKindSectionHeader { var headerRect = attrs.frame headerRect.size.height = headerHeight headerRect.origin.y = offset!.y attrs.frame = headerRect attrs.zIndex = 1024 break } } return attributes } } 

清洁Swift 2.3testing版伊琳娜的答案

 override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard var superAttributes = super.layoutAttributesForElementsInRect(rect) else { return super.layoutAttributesForElementsInRect(rect) } let contentOffset = collectionView!.contentOffset let missingSections = NSMutableIndexSet() for layoutAttributes in superAttributes { if (layoutAttributes.representedElementCategory == .Cell) { missingSections.addIndex(layoutAttributes.indexPath.section) } if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { missingSections.removeIndex(layoutAttributes.indexPath.section) } } } missingSections.enumerateIndexesUsingBlock { idx, stop in let indexPath = NSIndexPath(forItem: 0, inSection: idx) if let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath) { superAttributes.append(layoutAttributes) } } for layoutAttributes in superAttributes { if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { let section = layoutAttributes.indexPath.section let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section) let firstCellIndexPath = NSIndexPath(forItem: 0, inSection: section) let lastCellIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section) var firstCellAttributes:UICollectionViewLayoutAttributes var lastCellAttributes:UICollectionViewLayoutAttributes if (self.collectionView!.numberOfItemsInSection(section) > 0) { firstCellAttributes = self.layoutAttributesForItemAtIndexPath(firstCellIndexPath)! lastCellAttributes = self.layoutAttributesForItemAtIndexPath(lastCellIndexPath)! } else { firstCellAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstCellIndexPath)! lastCellAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastCellIndexPath)! } let headerHeight = CGRectGetHeight(layoutAttributes.frame) var origin = layoutAttributes.frame.origin origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttributes.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttributes.frame) - headerHeight)) ; layoutAttributes.zIndex = 1024; layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size) } } } return superAttributes } 

Swift 3版本试图避免!它是有道理的。

 class StickyHeaderCollectionViewFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard var superAttributes = super.layoutAttributesForElements(in: rect), let collectionView = collectionView else { return super.layoutAttributesForElements(in: rect) } let collectionViewTopY = collectionView.contentOffset.y + collectionView.contentInset.top let contentOffset = CGPoint(x: 0, y: collectionViewTopY) let missingSections = NSMutableIndexSet() superAttributes.forEach { layoutAttributes in if layoutAttributes.representedElementCategory == .cell && layoutAttributes.representedElementKind != UICollectionElementKindSectionHeader { missingSections.add(layoutAttributes.indexPath.section) } } missingSections.enumerate(using: { idx, stop in let indexPath = IndexPath(item: 0, section: idx) if let layoutAttributes = self.layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionHeader, at: indexPath) { superAttributes.append(layoutAttributes) } }) for layoutAttributes in superAttributes { if let representedElementKind = layoutAttributes.representedElementKind { if representedElementKind == UICollectionElementKindSectionHeader { let section = layoutAttributes.indexPath.section let numberOfItemsInSection = collectionView.numberOfItems(inSection: section) let firstCellIndexPath = IndexPath(item: 0, section: section) let lastCellIndexPath = IndexPath(item: max(0, (numberOfItemsInSection - 1)), section: section) let cellAttributes:(first: UICollectionViewLayoutAttributes, last: UICollectionViewLayoutAttributes) = { if (collectionView.numberOfItems(inSection: section) > 0) { return ( self.layoutAttributesForItem(at: firstCellIndexPath)!, self.layoutAttributesForItem(at: lastCellIndexPath)!) } else { return ( self.layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionHeader, at: firstCellIndexPath)!, self.layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionFooter, at: lastCellIndexPath)!) } }() let headerHeight = layoutAttributes.frame.height var origin = layoutAttributes.frame.origin // This line makes only one header visible fixed at the top // origin.y = min(contentOffset.y, cellAttributes.last.frame.maxY - headerHeight) // Uncomment this line for normal behaviour: origin.y = min(max(contentOffset.y, cellAttributes.first.frame.minY - headerHeight), cellAttributes.last.frame.maxY - headerHeight) layoutAttributes.zIndex = 1024 layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size) } } } return superAttributes } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } } 

我在我的项目中使用这个。 希望能帮助到你。

  func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSizeMake(UIScreen.mainScreen().bounds.width, 40) } 

当然。 你可以返回任何你想要的大小。