Plugin layout is a UICollectionViewLayout
designed to have a specific layout configuration for each UICollectionView
s section.
For each section, the layout asks it's delegate
for a proper Plugin
that will create proper UICollectionViewLayoutAttributes
and contribute determining the final size of contents.
PluginLayout
offers the possibility to combine different way of laying out objects for each section of its collection view.
It also offers some really neat UICollectionViewLayout
implementations out of the box, ready to be used and fully compatible with UICollectionViewFlowLayout
and UICollectionViewDelegateFlowLayout
.
This means that you can swap your current flow layout with one of those provided, implement some additional delegates methods if needed and be ready to go.
As normal UICollectionViewLayout
, PluginLayout
follows the usual 3 steps phases to calculate each of resulting UICollectionViewLayoutAttributes
that will be used to place (and reuse) each cell inside availble content space.
During the layout's prepare()
phase, the main layout ask every plugin available to create all attributes and give each one of them a proper frame
.
Each Plugin
must implement
func layoutAttributes(in section: Int, offset: inout CGPoint, layout: PluginLayout) -> [PluginLayoutAttributes]
where the offset
property (declared inout
) stores current layout global content size.
If current plugin needs to place a new attribute with a frame that will exceed current offset position, the offset
position needs to updated so that the hosting PluginLayout
can properly size the final content.
The collectionViewContentSize()
phase simply takes the offset
cached value and use it to size the content size. That value will represent the bottom-right-most CGPoint
that one or more attributes generated by all plugins intersect.
Note:
All default attributes are internally cached for performance reasons until UICollectionView
changes its external bounds (rotation) or until next reloadData()
.
A Plugin
is an object capable of generating and manipulating specific UICollectionViewLayoutAttributes
elements inside a section.
The Plugin is responsible of how those elements are placed inside its section and what class has to be used in order to generate them (as long as it's a PluginLayoutAttributes
subclass.). This is usually done by setting a frame
property on each attribute.
The attributes can either represents normal cells, supplementary view or decoration views.
A Plugin Is also responsible of any permutation on each attributes itself as the enclosing UICollectionView
scrolls, if those kind of permutation can't be expressed inside and Effect
object.
An Effect
is an object capable of manipulating a previously generated UICollectionViewLayoutAttributes
during collection view scrolling, independently from whatever Plugin
has originally generated it.
We can think the usual "sticky header / header pinning" of collection views and table views as an effect that is completely independent from how and where that attribute was originally placed by the plugin: the headers (or footers) with sticky effect will remain pinned to the top/bottom part of the collection regardless of their starting position.
Combining both Plugin
and Effect
objects can lead to completely new (and amazing) layouts that have "features" strictly isolated from each other and, more important, reusable in different contexts.
When applying more than one effect to an attribute, the order of application is important.
Each layout has to be intended as a single Plugin
that can be used with others and a complete PluginLayout
subclass that's completely configured to work out of the box for all those cases where every single section of the app should have the same layout behavior.
Each layout has a generic Delegate
property that must implement UICollectionViewDelegateFlowLayout
.
FlowLayout
is a complete replacement for original UICollectionViewFlowLayout
. It mimics the original Apple's behavior for each section, creating headers and footers like the original one.
The FlowLayoutPlugin
is needed in all those cases where some sections of the collection view should keep the original flow layout behavior, while assigning a different plugin to the others.
The complete FlowLayout
class may seem pointless, since in the end it's a reverse-engineered copy of the battle-tested UICollectionViewFlowLayout
; however, it's fully compatible with Effect
s and it should be used when a scrolling related layout effect is needed.
The delegate
property of this plugin is the standard UICollectionViewDelegateFlowLayout
.
GridLayoutPlugin
is a subclass of FlowLayoutPlugin
where elements are placed following a grid.
Each element can take a fraction of available width (for vertical-scrolling layouts) or height (for horizontal-scrolling ones), while the other dimension is calculated according to a specific aspectRatio
provided by delegate.
The delegate
property of this plugin must implement two additional methods:
func collectionView(_ collectionView: UICollectionView, layout: PluginLayout, lineFractionAt indexPath: IndexPath) -> Int
Given a single line in the layout and taking into account the spacing between its items and section insets, this value is used to divide the total line space available (width on vertical layouts and height on horizontal ones) so that resulting value can be applied to single item's dimension.
Example: in vertical scrolling layout, returning a value of 2
for every item will result in a two columns layout. A value of 1
will result in a UITableView
style layout.
func collectionView(_ collectionView: UICollectionView, layout: PluginLayout, aspectRatioAt indexPath: IndexPath) -> CGFloat
The desired aspect ratio of each item.
In vertical layouts, this value is used to calculate height
of each item ( width / ratio
)
In horizontal layouts, this value is used to calculate width
of each item ( height * ratio
)
A Staggered Layout is made by a pre-defined number of lines (columns in a vertical-scrolling layout) that gets independently filled by items with a specific size. Lines/columns are filled by iterating over the total amount of items and placing each one into the next column available; since item size can vary a lot between items, there is no guaranteed alignment at the end, but the total count of items per line/column will be the same (if total count is a multiple of column count, otherwise there will be a difference of 1 element between the first columns and the last ones).
A typical example of staggered layout is the Pinterest app, designed to have an infinite number of items. In that case, having the bottom part not aligned is not a big issue, since it's unlikely to ever get to the scroll bottom.
A mosaic layout is suited for items that have a desired size that doesn't have to be precise. Our algorithm takes full inspiration from Lightbox algorithm and this excellent post.
Layout is made by a precise number of line/columns; each line/column is divided by equally-spaced columns/lines, creating an invisible "grid". For this explanation, let's imagine a vertical scrolling layout, made by columns.
Each item is provided with a desired aspect ratio, placed in proper column by assigning current column width and then rounded in height to next line so that aspect ratio is respected as much as possible.
If previous columns ends at the same line and are contiguous, next item's width is sized to cover all contiguous columns, while its height is scaled by aspect ratio and rounded as before.
Next item is always placed into the topmost available column, so that the overall bottom line grows in the most uniform way possible.