DSL for UICollectionViewCompositionalLayout

Overview

ListKit

license MIT Platform Swift 5.4

DSL for UICollectionViewCompositionalLayout!

About

ListKit is DSL for building UICollectionViewCompositionalLayout. You can make UICollectionViewCompositionalLayout easy with ListKit. ListKit is Declarative and Component-Based. Also, ListKit supports diffable data source for UICollectionView!

Examples

You can checkout examples for ListKit at here:

renderer.render(of: Array(0..<10)) { index in
    Section(id: index) {
        HGroup(width: .fractionalWidth(1.0), height: .absolute(150)) {
            for _ in 0..<3 {
                ColorBox2Component(color: randomColor, width: .fractionalWidth(0.5), height: .fractionalHeight(1.0))
                VGroup(of: [0, 1], width: .fractionalWidth(0.25), height: .fractionalHeight(1.0)) { _ in
                    ColorBox2Component(color: randomColor, width: .fractionalWidth(1.0), height: .fractionalHeight(0.5))
                }
                VGroup(of: [0, 1], width: .fractionalWidth(0.25), height: .fractionalHeight(1.0)) { _ in
                    ColorBox2Component(color: randomColor, width: .fractionalWidth(1.0), height: .fractionalHeight(0.5))
                }
            }
        }
    }
    .orthogonalScrollingBehavior(.groupPaging)
    .boundarySupplementaryItem(SectionHeaderComponent(title: "Section \(index + 1)"))
}

Layout Elements

Section

Section(id: UUID()) {
    HGroup(width: .fractionalWidth(1.0), height: .absolute(150)) {
        for i in 0..<4 {
            ColorBoxComponent(color: colors[i], width: .fractionalWidth(1.0/4), height: .fractionalHeight(1.0))
        }
    }
}
.contentInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
.decorationItem(SectionBackgroundComponent())
.boundarySupplementaryItem(SectionHeaderComponent(title: "Section \(index + 1)"))

Section is a group of data items. You can define multiple sections in a layout. In UICollectionViewCompositionalLayout, Section has only one root Group.

Group(HGroup and VGroup)

In UICollectionViewCompositionalLayout, individual items are grouped into Group. Group has two types which are HGroup and VGroup. HGroup layouts items in horizontaly direction. VGroup layouts items in vertically. Group can have multiple items(components in ListKit) and groups.

Section(id: UUID()) {
    VGroup(of: [0, 1, 2], width: .fractionalWidth(1.0), height: .estimated(30)) { number in
        HGroup(of: [0, 1, 2], width: .fractionalWidth(1.0), height: .absolute(100)) { index in
            ColorBoxComponent(color: colors[(number * 3) + index], width: .fractionalWidth(1.0/3.0), height: .fractionalHeight(1.0))
        }
    }
}

Component

Component presents UI for the data item. It is the basic unit in ListKit. You can map a data into a component. You can define a component like below:

import UIKit
import ListKit

struct ColorBoxComponent: Component {
    var id: AnyHashable { UUID() }
    let color: UIColor
    let width: NSCollectionLayoutDimension
    let height: NSCollectionLayoutDimension
    
    public init(color: UIColor, width: NSCollectionLayoutDimension, height: NSCollectionLayoutDimension) {
        self.color = color
        self.width = width
        self.height = height
    }
    
    func contentView() -> UIView {
        return UIView(frame: .zero)
    }
    
    func layoutSize() -> NSCollectionLayoutSize {
        return NSCollectionLayoutSize(widthDimension: width, heightDimension: height)
    }
    
    func edgeSpacing() -> NSCollectionLayoutEdgeSpacing? {
        return nil
    }
    
    func contentInsets() -> NSDirectionalEdgeInsets {
        return .zero
    }
    
    func render(in content: UIView) {
        content.backgroundColor = color
    }
}

Component has a content view which is inherited from UIVIew. You can define more complex component with it's content view.

import UIKit
import SnapKit
import ListKit

struct EmojiBoxComponent: Component {
    let id: AnyHashable
    let emoji: String
    
    init(emoji: String) {
        self.id = emoji
        self.emoji = emoji
    }
    
    func contentView() -> EmojiBoxComponentContentView {
        EmojiBoxComponentContentView()
    }
    
    func layoutSize() -> NSCollectionLayoutSize {
        return NSCollectionLayoutSize(widthDimension: .absolute(30), heightDimension: .absolute(30))
    }
    
    func edgeSpacing() -> NSCollectionLayoutEdgeSpacing? {
        return nil
    }
    
    func contentInsets() -> NSDirectionalEdgeInsets {
        return .init(top: 2, leading: 2, bottom: 2, trailing: 2)
    }
    
    func render(in content: EmojiBoxComponentContentView) {
        content.label.text = emoji
    }
}

final class EmojiBoxComponentContentView: UIView {
    lazy var label: UILabel = {
        let label = UILabel(frame: .zero)
        label.font = UIFont.boldSystemFont(ofSize: 14)
        label.textColor = .white
        label.textAlignment = .center
        return label
    }()
    
    init() {
        super.init(frame: .zero)
        setupView()
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    func setupView() {
        addSubview(label)
        label.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        self.backgroundColor = .darkGray
        self.layer.borderWidth = 3
        self.layer.borderColor = UIColor.lightGray.cgColor
        self.layer.cornerRadius = 8.0
    }
}

Define Layout and Render it

Renderer

You can define layout in declarative way and render the layout with Renderer. Rederer is defined with DataSource.

var renderer: ComposeRenderer = ComposeRenderer(dataSource: PlainDataSource())

Also, you can set UICollectionViewDelegate and custom collection view cell.

/// Renderer's initializer.
public init(dataSource: DataSource, delegate: UICollectionViewDelegate? = nil, cellClass: AnyClass? = nil) {
    ...
}

Todo example use cellClass for handling swipe actions.

You can define layout and update it like below:

var emojiList: [String] = ["😊"] {
    didSet {
        render()
    }
}

override func render() {
    renderer.render(animated: true) {
        Section(id: Sections.main) {
            HGroup(of: emojiList, width: .fractionalWidth(1.0), height: .estimated(30)) { item in
                EmojiBoxComponent(emoji: item)
            }
        }
    }
}

DataSource

ListKit provides PlainDataSource and DiffableDataSource. PlainDataSource is used for UICollectionView that uses UICollectionViewFlowLayout. DiffableDataSource is used for UICollectionView that uses UICollectionViewDiffableDataSource and NSDiffableDataSourceSnapshot. The emoji example and Todo example use DiffableDataSource

Customizing DataSource

You can customize data source like below:

import UIKit
import ListKit
import SwipeCellKit

class TodoDataSource: DiffableDataSource, SwipeCollectionViewCellDelegate {
    override func configure(cell: UICollectionViewCell) {
        guard let swipableCell = cell as? SwipeCollectionViewCell else { return }
        swipableCell.delegate = self
    }
    
    func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
        guard orientation == .right else { return nil }
        
        guard let deletable = component(at: indexPath, to: Deletable.self) else { return nil }
        
        let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
            deletable.delete()
            action.fulfill(with: .delete)
        }
        
        deleteAction.image = UIImage(systemName: "trash")
        return [deleteAction]
    }
}

TodoDataSource is inherited from DiffableDataSource and customize it to use SwipeCellKit for swipe actions.

Iterable Data

ListKit provides render(of: [T]), HGroup(of: [T]) and VGroup(of: [T]) to handle iterable data and define dynamic layout with that data.

class ComplexLayoutViewController: BaseViewController {
    
    let colors: [UIColor] = [
        UIColor.red,
        UIColor.orange,
        UIColor.yellow,
        UIColor.green,
        UIColor.blue,
        UIColor.brown,
        UIColor.purple,
        UIColor.systemPink,
        UIColor.magenta
    ]
    
    var randomColor: UIColor {
        return colors.randomElement() ?? .cyan
    }
    
    override func render() {
        renderer.render(of: Array(0..<10)) { index in
            Section(id: index) {
                HGroup(width: .fractionalWidth(1.0), height: .absolute(150)) {
                    for _ in 0..<3 {
                        ColorBox2Component(color: randomColor, width: .fractionalWidth(0.5), height: .fractionalHeight(1.0))
                        VGroup(of: [0, 1], width: .fractionalWidth(0.25), height: .fractionalHeight(1.0)) { _ in
                            ColorBox2Component(color: randomColor, width: .fractionalWidth(1.0), height: .fractionalHeight(0.5))
                        }
                        VGroup(of: [0, 1], width: .fractionalWidth(0.25), height: .fractionalHeight(1.0)) { _ in
                            ColorBox2Component(color: randomColor, width: .fractionalWidth(1.0), height: .fractionalHeight(0.5))
                        }
                    }
                }
            }
            .orthogonalScrollingBehavior(.groupPaging)
            .boundarySupplementaryItem(SectionHeaderComponent(title: "Section \(index + 1)"))
        }
    }
}

Installation

ListKit only support Swift Package Manager.

dependencies: [
    .package(url: "https://github.com/ReactComponentKit/ListKit.git", from: "1.1.1"),
]

Requirements

  • Swift 5.4+
    • ListKit uses @resultBuilder which is available after Swift 5.4.
  • iOS 13.0+
  • Xcode 12.+

Inspired by and Respect

  • Carbon
    • Carbon is the awesome library for building user interfaces in UITableView and UICollectionView. It provides declarative and component-based way to buiild UI much like SwiftUI. I learned many things from Carbon to make ListKt. I want to give huge thanks to Carbon and respect it.

API reference

Section

  • Section(id: Hashable) { // building a group }
    • Section's initializer
  • func orthogonalScrollingBehavior(_ value: UICollectionLayoutSectionOrthogonalScrollingBehavior) -> Section
    • The section's scrolling behavior in relation to the main layout axis.
  • func interGroupSpacing(_ value: CGFloat) -> Section
    • The amount of space between the groups in the section.
  • func contentInsets(top: CGFloat = 0, leading: CGFloat = 0, bottom: CGFloat = 0, trailing: CGFloat = 0) -> Section
    • The amount of space between the content of the section and its boundaries.
  • func contentInsetsReference(_ value: UIContentInsetsReference) -> Section
    • @available(iOS 14.0, *)
    • The boundary to reference when defining content insets.
  • func supplementariesFollowContentInsets(_ value: Bool) -> Section
    • A Boolean value that indicates whether the section's supplementary items follow the specified content insets for the section.
  • func boundarySupplementaryItem<S: SupplementaryComponent>(_ value: S) -> Section
    • A supplementary item that it associated with the boundary edges of the section, such as headers and footers.
    • You can set multiple supplementary items with the chain of function calls.
  • func decorationItem<D: DecorationComponent>(_ value: D) -> Section
    • A decoration item that it anchored to the section, such as background decoration views.
    • You can set multiple supplementary items with the chain of function calls.
  • func visibleItemsInvalidationHandler(_ value: NSCollectionLayoutSectionVisibleItemsInvalidationHandler?) -> Section
    • A closure called before each layout cycle to allow modification of the items in the section immediately before they are displayed.

Group(HGroup and VGroup)

  • [V|H]Group(width: NSCollectionLayoutDimension, height: NSCollectionLayoutDimension) { // builing items }
  • [V|H]Group(of items: [T], width: NSCollectionLayoutDimension, height: NSCollectionLayoutDimension) { // builing items }
    • Group's initializer
  • func interItemSpacing(_ value: NSCollectionLayoutSpacing) -> Group
    • The amount of space between the items in the group.
  • func supplementaryItem<S: SupplementaryComponent>(_ value: S) -> Group
    • The supplementary item that is anchored to the group.
  • func edgeSpacing(top: NSCollectionLayoutSpacing = .fixed(0), leading: NSCollectionLayoutSpacing = .fixed(0), bottom: NSCollectionLayoutSpacing = .fixed(0), trailing: NSCollectionLayoutSpacing = .fixed(0)) -> Group
    • The amount of space added around the boundaries of the item between other items and this item's container.
  • func contentInsets(top: CGFloat = 0, leading: CGFloat = 0, bottom: CGFloat = 0, trailing: CGFloat = 0) -> Group
    • The amount of space added around the content of the item to adjust its final size after its position is computed.

Component

  • var id: AnyHashable { get }
    • Component's unique ID
  • func contentView() -> Content
    • return component's content view instance
  • func layoutSize() -> NSCollectionLayoutSize
    • The component's size expressed in width and height dimensions.
  • func edgeSpacing() -> NSCollectionLayoutEdgeSpacing?
    • The amount of space added around the boundaries of the item between other components and this component's container.
  • func contentInsets() -> NSDirectionalEdgeInsets
    • The amount of space added around the content of the component to adjust its final size after its position is computed.
  • func supplementComponents() -> [AnySupplementaryComponent]
    • An array of the supplementary items attached to the component.
  • func willDisplay(content: Content)
    • Component's content is about to be displayed in the collection view.
  • func didEndDisplay(content: Content)
    • Component's content was removed from the collection view.
  • func render(in content: Content)
    • Render data to component's content view

DataSource

  • func configure(cell: UICollectionViewCell)
    • Configure UICollectionViewCell if needed
  • func component<T>(at indexPath: IndexPath, to: T.Type) -> T?
    • Query the component at IndexPath and casting it to T type if there is.

MIT License

MIT License

Copyright (c) 2021 ListKit

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

You might also like...
MisterFusion is Swift DSL for AutoLayout. It is the extremely clear, but concise syntax, in addition, can be used in both Swift and Objective-C. Support Safe Area and Size Class.
MisterFusion is Swift DSL for AutoLayout. It is the extremely clear, but concise syntax, in addition, can be used in both Swift and Objective-C. Support Safe Area and Size Class.

MisterFusion MisterFusion makes more easier to use AutoLayout in Swift & Objective-C code. Features Simple And Concise Syntax Use in Swift and Objecti

A compact but full-featured Auto Layout DSL for Swift
A compact but full-featured Auto Layout DSL for Swift

Mortar allows you to create Auto Layout constraints using concise, simple code statements. Use this: view1.m_right |=| view2.m_left - 12.0 Instead of:

A Swift Autolayout DSL for iOS & OS X
A Swift Autolayout DSL for iOS & OS X

SnapKit is a DSL to make Auto Layout easy on both iOS and OS X. ⚠️ To use with Swift 4.x please ensure you are using = 4.0.0 ⚠️ ⚠️ To use with Swift

An NSPredicate DSL for iOS, OSX, tvOS, & watchOS. Inspired by SnapKit and lovingly written in Swift.
An NSPredicate DSL for iOS, OSX, tvOS, & watchOS. Inspired by SnapKit and lovingly written in Swift.

PrediKit A Swift NSPredicate DSL for iOS & OS X inspired by SnapKit, lovingly written in Swift, and created by that weird dude at KrakenDev. If you're

Bank Card Generator with Swift using SnapKit DSL 💳
Bank Card Generator with Swift using SnapKit DSL 💳

iCard BankCard & CreditCard generator with Swift 3 using SnapKit DSL iCard is a simple tool for generate Credit & Bank Card , it represent cards as UI

RResultBuilder is DSL library based on Result Builder
RResultBuilder is DSL library based on Result Builder

RResultBuilder is DSL library based on Result Builder Features Requirements Installation Usage Screenshot Example Contribute Meta Feat

Frank is a DSL for quickly writing web applications in Swift

Frank Frank is a DSL for quickly writing web applications in Swift with type-safe path routing. Sources/main.swift import Frank // Handle GET request

An awesome Swift CSS DSL library using result builders.

An awesome Swift CSS DSL library using result builders.

An awesome Swift HTML DSL library using result builders.

SwiftHtml An awesome Swift HTML DSL library using result builders. let doc = Document(.html5) { Html { Head { Meta()

📄 A Swift DSL for writing type-safe HTML/CSS in SwiftUI way

📄 swift-web-page (swep) Swep is a Swift DSL for writing type-safe HTML/CSS in SwiftUI way. Table of Contents Motivation Examples Safety Design FAQ In

A Swift DSL that allows concise and effective concurrency manipulation
A Swift DSL that allows concise and effective concurrency manipulation

NOTE Brisk is being mothballed due to general incompatibilities with modern version of Swift. I recommend checking out ReactiveSwift, which solves man

An NSPredicate DSL for iOS, OSX, tvOS, & watchOS. Inspired by SnapKit and lovingly written in Swift.
An NSPredicate DSL for iOS, OSX, tvOS, & watchOS. Inspired by SnapKit and lovingly written in Swift.

PrediKit A Swift NSPredicate DSL for iOS & OS X inspired by SnapKit, lovingly written in Swift, and created by that weird dude at KrakenDev. If you're

Tiny Swift DSL for Autolayout

SwiftAutoLayout SwiftAutoLayout is a tiny DSL for Autolayout intended to provide a more declarative way to express layout constraints. Here's a quick

Yet Another Swift Auto Layout DSL
Yet Another Swift Auto Layout DSL

FormationLayout Documentation FormationLayout is the top level layout class for one root view. FormationLayout takes a UIView as its rootView. transla

AutoLayout Micro DSL SPM from Chris Eidhof's article

EasyAutoLayout Intro EasyAutoLayout is a small framework built using the ideas presented in the article called Autolayout Micro DSL by Chris Eidhof. I

Sinatra-like DSL for developing web apps in Swift

Swiftra Swiftra is a library that provides DSLs like Sinatra. System Requirements DEVELOPMENT-SNAPSHOT-2016-02-08-a Example See swiftra-example. impor

Frank is a DSL for quickly writing web applications in Swift

Frank Frank is a DSL for quickly writing web applications in Swift with type-safe path routing. Sources/main.swift import Frank // Handle GET request

A DSL for Data Manipulation

Underscore.m About Underscore.m Underscore.m is a small utility library to facilitate working with common data structures in Objective-C. It tries to

Swifty DSL for programmatic Auto Layout in iOS
Swifty DSL for programmatic Auto Layout in iOS

WWLayout Easy to write auto layout constraints, with minimal extensions to standard namespaces. Feature Highlights Easy to use, readable API Backwards

Comments
  • [ISSUE-2] Fix crash on iOS 15

    [ISSUE-2] Fix crash on iOS 15

    Issue

    • #2

    Changes

    • [x] fix crash on SupplementaryComponent
    • [x] guard range of index path on querying component
    • [x] define func unregisterSupplementaryView(kind: String, withReuseIdentifier reuseIdentifier: String) extension for UICollectionView
    bug 
    opened by skyfe79 0
  • Support tracking lifetime of component

    Support tracking lifetime of component

    Motivation

    • We need to track the lifetime of the component. For example, when we have video content, we need to play or stop the video depending on the component life.

    Changes

    • rename DataSource to ListKitDataSource.
    • Define func willDisplay(content: Content) on Component.
    • Define func didEndDisplay(content: Content) on Component.
    • Change visibility of AnyComponent from internal to public.
    • Define ComponentLifeTimeTrackingDelegate to track the lifetime of the component and notify it to the component.
    enhancement 
    opened by skyfe79 0
  • Support for UICollectionViewListLayout?

    Support for UICollectionViewListLayout?

    Hi, thank you very much to simplify the process of defining and updating UICollectionView Compositional Layout 👍 Does ListKit also supports UICollectionView List Layout, which is available since iOS 14? I would be very happy about this because then we can use UIKit APIs for Swipe Actions and don't have to use libraries like SwipeCellKit...

    maybe like this:

    renderer.render {
        Section(id: Sections.main) {
            if todos.isEmpty {
                VGroup(width: .fractionalWidth(1.0), height: .estimated(30)) {
                    EmptyComponent(title: "Add New Todo!")
                }
            } else {
                List(of: todos) { todo in
                    TodoComponent(todo: todo) { [weak self] t in
                        self?.todos.removeAll(where: { $0 == t })
                    }
                }
                .listAppearance(.insetGrouped)
            }
        }
    }
    

    List is the class to create a new UICollectionViewListLayout with an Anppearance . It would be nice, if List can support both custom cells and default UICollectionViewListCell with content and background configuration.

    The following article is a good starting point: https://swiftsenpai.com/development/uicollectionview-list-basic/

    Let me know what you think about it. Carbon uses UITableView, but since iOS 14, there is no need to do this, because you can do everthing with UICollectionView.

    enhancement good first issue 
    opened by niklasgrewe 2
Owner
ReactComponentKit
ReactComponentKit is a library for building UIViewControllers based on Component, Redux and MVVM.
ReactComponentKit
CompositionalLayoutDSL, library to simplify the creation of UICollectionViewCompositionalLayout. It wraps the UIKit API and makes the code shorter and easier to read.

CompositionalLayoutDSL CompositionalLayoutDSL is a Swift library. It makes easier to create compositional layout for collection view. Requirements Doc

FABERNOVEL 44 Dec 27, 2022
Declaretive UICollectionViewCompositionalLayout interface to implement complex collection view layout.

CompositionalLayoutViewController Example To run the example project, clone the repo, and run pod install from the Example directory first. Requiremen

ONEinc 19 Dec 2, 2022
A simple integrated version of iOS 13 Compositional Layout, modified into a way similar to Functional Programming to generate UICollectionViewCompositionalLayout.

WWCompositionalLayout A simple integrated version of iOS 13 Compositional Layout, modified into a way similar to Functional Programming to generate UI

William-Weng 1 Jul 4, 2022
CollectionView - UICollectionView using UICollectionViewCompositionalLayout

CollectionView UICollectionView using UICollectionViewCompositionalLayout create

null 0 Jan 11, 2022
A project for studying of UICollectionViewCompositionalLayout

UICollectionViewCompositionalLayout A project for studying of UICollectionViewCo

donggyu 7 Jul 3, 2022
Swift UIKit E-Commerce (UgurShopping) No StoryBoard Firebase, FireStore, FirebaseAuth, KingFisher, SwiftEntryKit, ProgressHud, Alamofire UICollectionViewCompositionalLayout, NotificationCenter

Swift UIKit E-Commerce (UgurShopping) No StoryBoard Firebase, FireStore, FirebaseAuth, KingFisher, SwiftEntryKit, ProgressHud, Alamofire UICollectionViewCompositionalLayout, NotificationCenter

Ugur Hamzaoglu 2 Oct 16, 2022
A DSL to make animation easy on iOS with Swift.

This project is highly inspired by JHChainableAnimations, If you project is developed with Objective-C, use JHChainableAnimations instead. With DKChai

Draven 1.9k Dec 9, 2022
MusicKit is a framework and DSL for creating, analyzing, and transforming music in Swift.

MusicKit MusicKit is a framework and DSL for creating, analyzing, and transforming music in Swift. Examples Functional harmony let C5 = Pitch(midi: 72

Ben Guo 591 Oct 18, 2022
A declarative Auto Layout DSL for Swift :iphone::triangular_ruler:

Cartography ?? ?? Using Cartography, you can set up your Auto Layout constraints in declarative code and without any stringly typing! In short, it all

Robb Böhnke 7.3k Jan 4, 2023
An Impressive Auto Layout DSL for iOS, tvOS & OSX. & It is written in pure swift.

KVConstraintKit KVConstraintKit is a DSL to make easy & impressive Auto Layout constraints on iOS, tvOS & OSX with Swift Installation Using CocoaPods

Keshav Vishwkarma 90 Sep 1, 2022