UICollectionViewUICollectionView built using Compositional Layout.WispConfiguration.| Intuitive Drag Interaction | Tap to Dismiss |
|---|---|
This library supports installation via Swift Package Manager:
https://github.com/WispKit/Wisp.gitWispCompositionalLayout is designed to work almost identically to UICollectionViewCompositionalLayout.
You can use the same APIs you already know — the only difference is that you call them through .wisp.make.
That means every factory method available on UICollectionViewCompositionalLayout
(e.g. init(section:), init(sectionProvider:), or list(using:))
has its Wisp equivalent:
@MainActor
func make(section: NSCollectionLayoutSection) -> WispCompositionalLayout
@MainActor
func make(
section: NSCollectionLayoutSection,
configuration: UICollectionViewCompositionalLayoutConfiguration
) -> WispCompositionalLayout
@MainActor
func make(
sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider
) -> WispCompositionalLayout
@MainActor
func make(
sectionProvider: @escaping UICollectionViewCompositionalLayoutSectionProvider,
configuration: UICollectionViewCompositionalLayoutConfiguration
) -> WispCompositionalLayout
@MainActor
func list(using configuration: UICollectionLayoutListConfiguration) -> WispCompositionalLayout
So instead of calling UIKit’s initializer directly, you can simply use the .wisp.make(…) syntax:
// Multi-section layout
let layout = UICollectionViewCompositionalLayout.wisp.make { sectionIndex, layoutEnvironment in
// return your SectionProvider here
}
// Single-section layout
let simpleLayout = UICollectionViewCompositionalLayout.wisp.make {
// return your NSCollectionLayoutSection here
}
// List layout
let listLayout = UICollectionViewCompositionalLayout.wisp.make.list(using: .plain)
WispableCollectionViewWispableCollectionView is just like UICollectionView, but it takes a WispCompositionalLayout instead of UICollectionViewLayout.
Once you have your layout, pass it to WispableCollectionView:
let myCollectionView = WispableCollectionView(
frame: .zero,
collectionViewLayout: layout
)
Or inline it:
let myCollectionView = WispableCollectionView(
frame: .zero,
collectionViewLayout: .wisp.make {
// return your NSCollectionLayoutSection here
}
)
You can also build with list layout easily:
let myListView = WispableCollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewCompositionalLayout.wisp.list(using: .plain)
)
or you can simplify like this:
let myListView = WispableCollectionView(frame: .zero, collectionViewLayout: .wisp.list(using: .plain))
wisp.present( ... )No extra delegates or boilerplate needed.
class MyViewController: UIViewController, UICollectionViewDelegate {
// ...
let myCollectionView: WispableCollectionView(
frame: .zero,
collectionViewLayout: .wisp.make { ... }
)
// ...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let secondVC = MyViewController()
wisp.present(secondVC, collectionView: myCollectionView, at: indexPath)
// ⚠️ Note: The collection view must be a subview of the presenting view controller.
}
// ...
}
By default, a wisp-presented view controller can be dismissed with a drag gesture (pan gesture) or by tapping the background. However, if you want to dismiss explicitly at a specific moment in your code, you can call the public API:
func dismiss(
to indexPath: IndexPath? = nil,
animated: Bool = true
)
// Inside the presented view controller
self.wisp.dismiss(animated: true)
If indexPath is nil, the view will try to use the original indexPath used at the time of presentation. If you want the view to dismiss to a different indexPath, just provide it in the to parameter.
Example:
// Dismiss to a different cell than the one originally presented from
self.wisp.dismiss(to: IndexPath(item: 5, section: 0), animated: true)
Wisp provides a delegate so you can detect when a card restoration (returning animation) begins and ends.
This is useful because the restoring animation is not part of the actual view controller’s lifecycle —
the view controller is already dismissed when the card starts restoring.
You can set the delegate from the presenting view controller:
import Wisp
import UIKit
final class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
wisp.delegate = self
}
}
extension MyViewController: WispPresenterDelegate {
func wispWillRestore() {
print("Restoring will begin.")
}
func wispDidRestore() {
print("Restoring completed.")
}
}
When a Wisp-presented view controller is dismissed (via drag, tap, or programmatically), the restoring animation is handled internally by a captured snapshot view, not by the dismissed view controller itself. Therefore, UIKit’s lifecycle methods such as viewWillAppear or viewDidDisappear won’t notify you of this transition. Instead, you can rely on these two delegate methods:
wispWillRestore(): called when the card restoration beginswispDidRestore(): called when the restoration animation finishesYou can use this delegate to coordinate updates with your collection view or perform custom UI changes.
This repository includes a fully functional example app, WispExample, to demonstrate the features of the Wisp library. You can run it to see live examples of different transition styles and configurations.
Wisp.xcworkspace in Xcode.WispExample scheme.WispConfiguration allows you to tweak the animation and layout behavior.
From version 1.3.0, WispConfiguration has been refactored to use a DSL-based configuration style for better readability, maintainability, and future extensibility.
For details, see the WispConfiguration DSL Guide.
let configuration = WispConfiguration { config in
// Animation configuration
config.setAnimation { animation in
animation.speed = .fast
}
// Gesture configuration
config.setGesture { gesture in
gesture.allowedDirections = [.right, .down]
gesture.dismissByTap = false
}
// Layout configuration
config.setLayout { layout in
layout.presentedAreaInset = inset
layout.initialCornerRadius = 15
layout.finalCornerRadius = 30
}
}
All properties are optional and have default values.
For example, Use presentedAreaInset to customize the width and height of each card presented.
| fullscreen | formSheet style | card | small pop up |
|---|---|---|---|
MIT License. See LICENSE for more information.