我正在尝试创建一个粘性补充标头,它始终保持在顶部,并且不会响应滚动事件。到目前为止我发现的解决方案仍然对bounch滚动做出反应,并使用自定义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
}
我通过测试this gist中建议的空截面测试来修复我的崩溃。我还添加了几个if let
s以获得额外的Swift风格点;-)。这对我有用:
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可选错误,因为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
}
这是一个简单的解决方案,只有一个部分才有效。
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
}
}
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
}
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)
}
当然。你可以返回你想要的任何尺寸。