J'essaie de créer un en-tête supplémentaire collant, qui reste en haut tout le temps et ne répond pas aux événements de défilement. Les solutions que j’ai trouvées jusqu’à présent réagissent toujours sur le défilement instantané et sont corrigées à l’aide d’un flux personnalisé, ce qui sera probablement également la solution au problème des mines.
La raison pour laquelle je le veux ainsi, c'est que l'en-tête est utilisé à d'autres endroits et doit être réutilisable. J'espère que cela pourra être résolu de cette façon et que je n'aurai pas à créer une vue séparée.
Comme je le fais à Swift, il serait bon d’avoir un exemple à Swift.
La solution finale que j'ai trouvée:
En utilisant cette présentation de flux personnalisée, il était possible de corriger cet en-tête collant:
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
}
}
Pour créer une mise en page où les en-têtes sont collants comme d'habitude, changez cette ligne:
Origin.y = min(contentOffset.y, (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight))
à cette ligne:
Origin.y = min(max(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight))
En espérant que cela soit utile pour les autres!
Mettre à jour
Mise à jour pour corriger un crash (merci à Robert Atkins!) Et quelques mises à jour de Swift 1.2
tvOS & iOS 9
tvOS et iOS 9 ont introduit la propriété sectionHeadersPinToVisibleBounds
qui peut être utilisée
La solution la plus simple pour iOS 9 +, car elle n'a pas besoin d'écrire sous-classe UICollectionViewFlowLayout.
Dans viewDidLoad de viewController avec collectionView, utilisez le code suivant:
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true
@ Antoine fait également allusion à cela.
fonctionne pour moi avec 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
}
Swift 2.2 version testée sur la base de la réponse de GregP. Le code de Greg renvoyait une erreur facultative de décompression dans lastCellIndexPath car le nombre de sections est initialement égal à zéro. J'ai donc déplacé le numberOfItemsInSection> 0 check up.
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
}
J'ai corrigé mon plantage en testant les sections vides comme indiqué dans this Gist . J'ai aussi ajouté quelques if let
s pour des points de style Swift supplémentaires ;-). Cela fonctionne maintenant pour moi:
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
}
}
Cleaner Swift 2.3 version testée de la réponse d'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
}
Voici une solution simple qui fonctionne si vous n’avez qu’une section.
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
}
}
La version 3 de Swift essayant d'éviter! Est ce qui a du sens.
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
}
}