π Expandable, hierarchical, flexible, declarative UICollectionView with diffable data sources & SwiftUI-like tree items builder [Swift 5.1, iOS & iPadOS 13].
Last Update: 18/July/2021.
π Expandable, hierarchical, flexible, declarative UICollectionView with diffable data sources & SwiftUI-like tree items builder [Swift 5.1, iOS & iPadOS 13].
Please wait while the .gif
files are loading...
ExpandableCollectionViewManager
class, provide its parent view controller and specify the Folders
and Items
.Folders
, Items
, Navigation
, appeareances
and also custom animations
.Function Builders
make it very easy to create a tree of interconnected itemsExpandableCollectionViewManager
and fill it in with items.Diffable Data Sources
and Collection View Compositional Layout
which provides high performance and tested backing APi.Function Builder
types (such as For Each
that applies a group of modifiers to a set of items, rather than copy-pasting the same modifiers or managing boilerplate configuration), decoratable content views for items and the list goes on.Create a property that will hold the ExpandableCollectionViewManager
reference and the instantiate it via one of the designated initializers:
let expVCManager = ExpandableCollectionViewManager(parentViewController: self)
Here self
is the reference to the UIViewController
where the property is used.
To pre-fill the collection view, you may use the trailing closure syntax for one of the designated initializers:
let expVCManager = ExpandableCollectionViewManager(parentViewController: self) {
Folder(title: "ToDo") {
Item(title: "Compose Email")
.setImage(systemName: "paperplane")
Item(title: "Watch Netflix")
}
Item(title: "Buy new iPhone")
Item(title: "Cleanup")
}
You can add more items by using appendItems
method:
expVCManager.appendItems {
Folder(title: "Marvel") {
Item(title: "Spider-Man")
Item(title: "Iron Man")
Item(title: "Black Widow")
}
Folder(title: "DC") {
Item(title: "Batman")
Item(title: "Batgirl")
Item(title: "Nightwing")
}
}
There are a number of item modifiers that can be applied to them to change their behaviour or appearance:
expVCManager.appendItems {
Folder(title: "Marvel") {
Item(title: "Spider-Man")
.setImage(systemName: "tray.full.fill") // 1
Item(title: "Iron Man")
Item(title: "Black Widow")
}
.setTintColor(.systemOrange) // 2
.isExpanded(true) // 3
.isItemsCountVisible(true) // 4
Folder(title: "DC") {
Item(title: "Batman")
Item(title: "Batgirl")
Item(title: "Nightwing")
}
.isChevronVisible(false) // 5
}
Let's break down the API's line by line:
Item
and/or Folder
Folder
's iconFolder
is automatically expanded
Folder
's item counter label thatActions
allow to specify a closure that will be executed on item tap action:
Item(title: "Spider-Man")
.setImage(systemName: "spider.fill")
.setAction { [weak self] indexPath, title in
self?.showWarning("Item \(title) that is at \(indexPath) index path has been tapped.")
}
Actions have two input parameters an item's index path
and title
. That should make the API more or less flexible for action handling use cases.
You can specify cells' unfold animation. It's expressed as enum type, where the default is .simple
, which is the default diffing animation
(hide/show). However, there is a an another option that you to use one of the pre-built animations or implement and inject your own.
expVCManager.unfoldAnimation = .custom(AnimationFactory.moveDown(duration: 0.415)) // 1
expVCManager.unfoldAnimation = .custom(AnimationFactory.verticalUnfold(duration: 0.435)) // 2
Let's break the sample code down. The AnimationFactory
is a built-in type that has a number of pre-implemented animations.
moveDown
animation simply moves each unfolded item to the direction of unfolding with the specified duration.verticalUnfold
animation is a bit more complicated: it applies a series of affine transformations to each unfolded cell.To define an inject your own animations, you need to use the following typealias
that defines the required closure's signature for the .custom
unfold animation type:
let delayFactor = 0.015
let slideOutAnimation: ExpandableItem.Animation = { cell, indexPath, collectionView, completion in
UIView.animate(
withDuration: duration,
delay: delayFactor * Double(indexPath.row),
options: [.curveEaseInOut],
animations: {
cell.transform = CGAffineTransform(translationX: collectionView.bounds.width, y: 0)
}, completion: completion)
}
expVCManager.unfoldAnimation = .custom(slideOutAnimation)
Here we used the ExpandalbeItem.Animation
typealias to get the right closureβs signature and then we implemented our custom cell animation. The final step is that we injected it via the .custom(slideOutAnimation)
type.
The component also supports custom handler for view controller transitioning. It can be specified via the following property:
expVCManager.onCellTapHandler = { _, destinationViewController in
// 1. Prepare presentation part of the destination view controller
let navController = UINavigationController(rootViewController: destinationViewController)
// 2. Present the view controller
self.present(navController, animated: true)
}
That makes different kinds of view controller navigation logic to work on a per-item basis. Which means for one view controller bound item we can use UINavigationController
to present its view controller, wheres for some other item we can use modal view controller presentation style.
There is yet an another modifier for Item
type that allows to specify a UIViewController
type:
Item(title: "Spider-Man")
.setImage(systemName: "spider.fill")
.setViewControllerType(IssuesViewController.self) { [weak self] viewController in
viewController.comics = self?.endpoint(\.latest.spiderman.comics)
}
Here we specified the target view controller type that needs to be transitioned when the Spider-Man
cell is tapped and its configuration code, where we, for example can inject data, perform any other operation or simply omit.
MenuBar
β File
β Swift Packages
β Add Package Dependency...
https://github.com/jVirus/expandable-collection-view-kit
and hit Next
.After specifying which version do you want to install, the package will be downloaded and attached to your project.
If you already have a Package.swift
or you are building your own package simply add a new dependency:
dependencies: [
.package(url: "https://github.com/jVirus/expandable-collection-view-kit", from: "1.0.0")
]
You can always use copy-paste the sources method π. Or you can compile the framework and include it with your project.
Your contributions are always appreciated. There are many ways how you help with the project:
Overall guidelies are:
The project is available under MIT Licence