The layout effect to achieve
UICollectionView custom layout, is to implement its own UICollectionView layout,
Instead of using the system the UICollectionViewFlowLayout
The layout looks like this:
You pull it down, the top two pieces are adsorbed, the lattice is adsorbed
Pull it up, and the second piece on top snaps
The realization idea of layout effect
Realized effect:You pull it down, the top two pieces are adsorbed, and the lattice is adsorbed
The conventional wisdom is that
Control the direction of the gesture, only down, not up
Layout control is used here
UICollectionView is a subclass of UIScrollView,
The UIScrollView has a contentOffset as it rolls,
ContentOffset is a point, a CGPoint
Drop-down, give each view (two top view and grid) plus a translational transform, CGAffineTransform. Translation, it is good
And when you pull it down, it looks like it’s not moving, but it’s actually the two top views with the grid view, and the container view of the scrollView, scrolling in sync
Realized effect:Pull it up, and the second piece on top snaps
The conventional wisdom is that
The second block of the top consists of two widgets: a supplement view to the UICollectionView, and an independent control
When pulling up, the second top block of the supplementary view is pulled out of the interface, and the independent top block, which is hidden by default, is displayed
The layout control is used directly here
The second block at the top contains only one copy and serves as a SupplementaryView
As you pull up, use the contentOffset of UICollectionView to get your current position,
I’m going to add a translational radiation transform to hold the position
The second top block has to be on top of the grid view, and the zIndex of the second top block is bigger than the zIndex of the grid views, OK
The concrete realization of the layout effect
Custom layout, the system gives us three useful entry points
- Give the layout information to the supplementary view
All supplementary view layout information
func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
- Give the grid view layout information
Layout information for all grid views
func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
- View layout information for the visible area of the screen
Summary of Layout Information
func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
The general layout is very simple, do these three methods, done
The special thing about this layout effect is that it can be displayed in different ways as it scrolls, two of which are described above
The conventional wisdom is that
Trigger with func scrollViewDidScroll(_ scrollView: UIScrollView) listening to the contentOffset
ScrollViewDidScroll (_ scrollView: UIScrollView)
In this callback, the layout of the system is applied, and the effect is mediocre
Layout control, of course
Func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool,
Because the collectionView bounds property changes every time the user scrolls through the interface
ShouldInvalidateLayout (forBoundsChange:) is a method that should write a rule that determines when to refresh the layout.
Grid view collectionView bounds property change, shouldInvalidateLayout (forBoundsChange:) judgment through, again into the prepare () method of calculation.
override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { if oldBounds.size ! = newBounds.size { cache.removeAll(keepingCapacity: true) } return true }Copy the code
This is a scroll to refresh the layout
Func prepare()
In func prepare(), the layout information is initialized
override public func prepare() { guard let collectionView = collectionView, collectionView.numberOfItems(inSection: 0) > 0 else {return} Github prepareCache() contentHeight = 0 zIndex = 0 oldBounds = collectionView.bounds let itemSize = CGSize(width: collectionViewWidth, height: CellHeight) / / at the top of the first piece of the layout of the information let headerAttributes = CustomLayoutAttributes (forSupplementaryViewOfKind: Element.header.kind, with: IndexPath(item: 0, section: 0) ) prepareElement(size: headerSize, type: .header, attributes: HeaderAttributes) / / at the top of the block 2 layout information let menuAttributes = CustomLayoutAttributes (forSupplementaryViewOfKind: Element.menu.kind, with: IndexPath(item: 0, section: 0)) prepareElement(size: menuSize, type: .menu, attributes: MenuAttributes) // Grid view layout for item in 0.. < collectionView.numberOfItems(inSection: 0) { let cellIndexPath = IndexPath(item: item, section: 0) let attributes = CustomLayoutAttributes(forCellWith: cellIndexPath) let lineInterSpace = settings.minimumLineSpacing attributes.frame = CGRect( x: settings.minimumInteritemSpacing, y: contentHeight + lineInterSpace, width: itemSize.width, height: itemSize.height ) attributes.zIndex = zIndex contentHeight = attributes.frame.maxY cache[.cell]?[cellIndexPath] = Attributes += 1} // Make sure that the layout of the top 2 block zIndex, > lattice view cache[.menu]?.first?.value. ZIndex = zIndex}Copy the code
The layout information is aggregated and dynamically adjusted according to the offset contentOffset
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let collectionView = collectionView else { return nil } visibleLayoutAttributes.removeAll(keepingCapacity: true) for (type, elementInfos) in cache { for (indexPath, Attributes) in elementInfos {attributes. Transform =.identity UpdateSupplementaryViews (Type, attributes: Attributes, collectionView: collectionView, indexPath: IndexPath) // Only care about layout information that is displayed on screen // Layout information that is not on screen No need to worry if the attributes. Frame. Intersects the rect () {if type = =. The cell {/ / dynamic adjustment, the layout of the grid view information updateCells (attributes, collectionView: collectionView, indexPath: indexPath) } visibleLayoutAttributes.append(attributes) } } } return visibleLayoutAttributes }Copy the code
Adjust the layout information of the top two supplementaryViews dynamically
The corresponding translation affine transform is set according to the offset contentoffset. y
The first block on the top, we added an alpha control
private func updateSupplementaryViews(_ type: Element, attributes: CustomLayoutAttributes, collectionView: UICollectionView, indexPath: IndexPath) {
switch type {
case .header:
attributes.transform = CGAffineTransform(translationX: 0, y: contentOffset.y)
attributes.headerOverlayAlpha = min(settings.headerOverlayMaxAlphaValue, contentOffset.y / headerSize.height)
case .menu:
print(contentOffset.y)
if contentOffset.y < 0{
attributes.transform = CGAffineTransform(translationX: 0, y: attributes.initialOrigin.y - headerSize.height + contentOffset.y)
}
else{
attributes.transform = CGAffineTransform(translationX: 0, y: max(attributes.initialOrigin.y, contentOffset.y) - headerSize.height)
}
default:
break
}
}
Copy the code
Dynamic adjustment of grid view layout information
The corresponding translation affine transform is set according to the offset contentoffset. y
private func updateCells(_ attributes: CustomLayoutAttributes, collectionView: UICollectionView, indexPath: IndexPath) {
if contentOffset.y < 0{
attributes.transform = CGAffineTransform(translationX: 0, y: attributes.initialOrigin.y + contentOffset.y)
}
}
Copy the code
And finally,
System, UICollectionViewFlowLayout, standard of shelf,
Each section can have only one valid supplementary view header and footer
(Because the size of header and footer is divided by section)
So the layout here, one section with two headers
Actually, write your own layout UICollectionViewFlow,
Not only the position of each supplementary view and grid view, random placement
The number of supplementary views can also be allocated arbitrarily,
The maximum number of supplementary views = Kind * IndexPath
How many Kind, free to set
In this layout, this is how it’s handled
So let’s initialize,
override public func prepare() {
// ...
let headerAttributes = CustomLayoutAttributes(
forSupplementaryViewOfKind: Element.header.kind,
with: IndexPath(item: 0, section: 0)
)
// ...
let menuAttributes = CustomLayoutAttributes(
forSupplementaryViewOfKind: Element.menu.kind,
with: IndexPath(item: 0, section: 0))
prepareElement(size: menuSize, type: .menu, attributes: menuAttributes)
// ...
}
Copy the code
To register again,
public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { switch elementKind { case Element.header.kind: return cache[.header]? [indexPath] case CustomLayout.Element.menu.kind: return cache[.menu]? [indexPath] default: return nil } }Copy the code