Skip to content

stefanomondino/PluginLayout

Repository files navigation

PluginLayout

Plugin layout is a UICollectionViewLayout designed to have a specific layout configuration for each UICollectionViews 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.

TLDR

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.

Key concepts

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().

Plugin

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.

Effect

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.

Included Layouts

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

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 Effects and it should be used when a scrolling related layout effect is needed.

The delegate property of this plugin is the standard UICollectionViewDelegateFlowLayout.

GridLayout

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)

StaggeredLayout

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.

MosaicLayout

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.

Demo

Custom Effect

About

A fully customizable, composable layout for UICollectionView

Resources

License

Stars

Watchers

Forks

Packages

No packages published