我正在尝试创建一个粘性补充标题,它始终保持在顶部并且不会响应滚动事件。到目前为止,我发现的解决方案仍然对弹跳滚动做出反应,并使用自定义 flowLayout 进行修复,这也可能是我的问题的修复。
我想要这样的原因是标题在其他地方使用并且应该是可重用的。我希望这可以通过这种方式解决,并且我不必创建单独的视图。
当我在 Swift 中这样做时,在 Swift 中有一个示例会很棒。
我正在尝试创建一个粘性补充标题,它始终保持在顶部并且不会响应滚动事件。到目前为止,我发现的解决方案仍然对弹跳滚动做出反应,并使用自定义 flowLayout 进行修复,这也可能是我的问题的修复。
我想要这样的原因是标题在其他地方使用并且应该是可重用的。我希望这可以通过这种方式解决,并且我不必创建单独的视图。
当我在 Swift 中这样做时,在 Swift 中有一个示例会很棒。
iOS 9 + 最简单的解决方案,因为它不需要编写 UICollectionViewFlowLayout 的子类。
在带有 collectionView 的 viewController 的 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 也暗示了这一点。
我找到的最终解决方案:
使用这个自定义流布局可以修复这个粘性标题:
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
}
}
要创建一个标题像传统一样粘性的布局,请更改此行:
origin.y = min(contentOffset.y, (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight))
到这一行:
origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight))
希望这对其他人有用!
更新
更新以修复崩溃(感谢 Robert Atkins!)和 Swift 1.2 的一些更新
tvOS 和 iOS 9
tvOS 和 iOS 9 引入了sectionHeadersPinToVisibleBounds
可以使用的属性
使用 swift 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
}
我按照这个 gist中的建议通过测试空部分来修复我的崩溃。if let
我还为额外的 Swift 风格点添加了几个s ;-)。这现在对我有用:
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
}
}
基于 GregP 的回答测试的 Swift 2.2 版本。Greg 的代码在 lastCellIndexPath 处抛出了一个unwrap optional 错误,因为节数最初为零。所以我移动了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
}
Cleaner Swift 2.3 测试版的 Irina 的答案
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
}
对于想要在组合布局上使用粘性标题的人:
(更改高度值以满足您的需要)
let headerKind = UICollectionView.elementKindSectionHeader
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(100))
let headerElement = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: headerKind, alignment: .top)
// for sticky header
headerElement.pinToVisibleBounds = true
section.boundarySupplementaryItems = [headerElement]
如果您只有一个部分,这是一个简单的解决方案。
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 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)
}
当然。你可以返回任何你想要的尺寸。