LiveCollections
Automatically perform UITableView and UICollectionView animations between two sets of immutable data. It supports generic data types and is fully thread-safe.
Install / Use
/learn @scribd/LiveCollectionsREADME
<b>LiveCollections</b> is an open source framework that makes using UITableView and UICollectionView animations possible in just a few lines of code. Given two sets of data, the framework will automatically perform all of the calculations, build the line item animation code, and perform it in the view.
Using one of the two main classes CollectionData or CollectionSectionData, you can build a fully generic, immutable data set that is thread safe, timing safe, and highly performant. Simply connect your view to the data object, call the update method and that's it.
In the sample app, there are a number of use case scenarios demonstrated, and the sample code of each one can be found by looking up the respective controller (e.g. ScenarioThreeViewController.swift).
Full detail for the use case of each scenario <a href="https://medium.com/p/59ea1eda2b2d">can be found in the blog post on Medium</a>, which you can read if you want the full explanation. Below, I am just going to show the class graph and the minimum code needed for each case.
<hr> <h2>Swift Version</h2>This project has been upgraded to be compatible with Swift 5.5 <br>
<h2>iOS Version</h2>This minimum supported version is iOS 11.0 <br>
<hr> <h2>Importing With SwiftPM</h2> <br> https://github.com/scribd/LiveCollections 1.0.3 <br> <br> <h2>Importing With Carthage</h2> <br> github "scribd/LiveCollections" "1.0.3" <br> <br> <h2>Importing With CocoaPods</h2> <br> pod 'LiveCollections', '~> 1.0.3' <br> or <br> pod 'LiveCollections' <br> <br> <hr> <h1>The Main Classes</h1> <ul> <li><b>CollectionData<YourType></b></li> <li><b>CollectionSectionData<YourSectionType></b></li> </ul>By using one of these two classes to hold your data, every time you update with new data it will automatically calculate the delta between your update and the current data set. If you assign a view to either object, then it will pass both the delta and data to the view and perform the animation automatically. All updates are queued, performant, thread safe, and timing safe. In your UITableViewDataSource and UICollectionViewDataSource methods, you simply need to fetch your data from the <b>CollectionData</b> or <b>CollectionSectionData</b> object directly using the supplied count, subscript, and isEmpty thread-safe accessors.
To prepare your data to be used in <b>CollectionData</b>, you just need to adopt the protocol <b>UniquelyIdentifiable</b>. The requirements for <b>CollectionSectionData</b> will be detailed in scenerios 5 and 6 a bit later on.
<BR><b>Updates made easy!</b>
Once you create an instance of CollectionData, animating your table or collection view becomes just a single line of code:
func yourDataUpdateFunction(_ updatedData: [YourDataType]) {
collectionData.update(updatedData)
}
<BR>
You'll notice that the data being passed in is a Swift immutable type, and at no point do you ever need to worry about what the difference is between the new data being passed in and the existing data.
<BR> <h2>Adopting the protocol UniquelyIdentifiable</h2>The crux of being able to use <b>CollectionData</b> as your data source and get all of the benefits of LiveCollections, is by adopting the protocol <b>UniquelyIdentifiable</b>. It's what allows the private delta calculator in the framework to determine all of the positional moves of your data objects.
public protocol UniquelyIdentifiable: Equatable {
associatedtype RawType
associatedtype UniqueIDType: Hashable
var rawData: RawType { get }
var uniqueID: UniqueIDType { get }
}
Since UniquelyIdentifiable inherits from Equatable, making your base class adopt Equatable gives you an auto-synthesized equatability function (or you can write a custom == func if needed).
Here's a simple example of how it can apply to a custom data class:
import LiveCollections
struct Movie: Equatable {
let id: UInt
let title: String
}
extension Movie: UniquelyIdentifiable {
typealias RawType = Movie
var uniqueID: UInt { return id }
}
Note: Take a look at Scenario 9 below to see an example where RawType is not simply the Self type. We will use a different type if we want to have different equatability functions for the same RawType object in different views, or if we want to create a new object that includes additional metadata.
<BR> <h2>Adopting the protocol NonUniquelyIdentifiable</h2>Support has been added for non-unique sets of data as well.
public protocol NonUniquelyIdentifiable: Equatable {
associatedtype NonUniqueIDType: Hashable
var nonUniqueID: NonUniqueIDType { get }
}
By adopting this protocol and using one of the two type aliases NonUniqueCollectionData or NonUniqueCollectionSectionData, a factory will be built under the hood that will transform your non-unique data into a UniquelyIdentifiable type. See Scenarios 10 and 11.
Since the data is wrapped in a new struct, to access your original object you'll need to call the rawData getter like so:
let data = collectionData[indexPath.item].rawData
<i>Note:This will use "best guess" logic, and the identifiers will be determined based on array order.</i> <br> <br>
<hr>Listed below is a summation of the relevant code you'll need in your app for each of the scneario in the sample app. These reflect most of the use cases you will encounter. <br>
<h2>Scenario 1: A UICollectionView with one section</h2> <img src="https://github.com/scribd/LiveCollections/blob/master/ReadMe/single_section_collection_view.png" alt="Single section collection view class graph">final class YourClass {
private let collectionView: UICollectionView
private let collectionData: CollectionData<YourData>
init(_ data: [YourData]) {
collectionData = CollectionData(data)
...
super.init()
collectionData.view = collectionView
}
func someMethodToUpdateYourData(_ data: [YourData]) {
collectionData.update(data)
}
}
extension YourClass: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return collectionData.count
}
// etc
}
<br>
<br>
<br>
<h2>Scenario 2: A UTableView with one section</h2>
<br>
The same as scenario 1 but swap in UITableView.
<br>
<br>
<br>
<h2>Scenario 3: A UICollectionView with multiple sections, each section has its own CollectionData</h2>
<img src="https://github.com/scribd/LiveCollections/blob/master/ReadMe/discrete_sections_collection_view.png" alt="Multiple discrete sections collection view class graph">
or
<img src="https://github.com/scribd/LiveCollections/blob/master/ReadMe/discrete_sections_collection_view_with_synchronizer.png" alt="Multiple discrete sections collection view class graph with two sections synchronized">final class YourClass {
private let collectionView: UICollectionView
private let dataList: [CollectionData<YourData>]
init() {
// you can also assign section later on if that better fits your class design
dataList = [
CollectionData<YourData>(section: 0),
CollectionData<YourData>(section: 1),
CollectionData<YourData>(section: 2)
]
...
super.init()
// Optionally apply a synchronizer to multiple sections to have them
// perform their animations in the same block when possible
let synchronizer = CollectionDataSynchronizer(delay: .short)
dataList.forEach { $0.synchronizer = synchronizer }
}
func someMethodToUpdateYourData(_ data: [YourData], section: Int) {
let collectionData = dataList[section]
collectionData.update(data)
}
}
extension YourClass: UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return dataList.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let collectionData = dataList[section]
return collectionData.count
}
// let item = collectionData[indexPath.row]
// etc
}
<br>
<br>
<br>
<h2>Scenario 4: A UITableView with multiple sections, each section has its own CollectionData</h2>
<br>
The same as scenario 3 but swap in UITableView.
<br>
<br>
<hr>
<h1>Using unique data across multiple sections? Adopt the protocol UniquelyIdentifiableSection</h1>
When data items are uniquely represented across the entire view, they may move between sections. To handle these animations, you can instead use <b>CollectionSectionData</b> and create a data item that adopts <b>UniquelyIdentifiableSection</b>.
public protocol UniquelyIdentifiableSection: UniquelyIdentifiable {
associatedtype DataType: UniquelyIdentifiable
var items: [DataType] { get }
}
As you can see, it still ultiately relies on the base data type that adopts <b>UniquelyIdentifiable</b>. This new object helps us wrap the section changes.
Note: Since UniquelyIdentifiableSection inherits from UniquelyIdentifiable, that means that each section will also require its own uniqueID to track section changes. These IDs do not have to be unique from those of the underlying items: [DataType].
import LiveCollections
struct MovieSection: Equatable {
let sectionIdentifier: String
let movies: [Movie]
}
extension MovieSection: UniquelyIdentifiableSection {
var uniqueID: String { return sectionIdentifier }
var items: [Movie] { return movies }
var hashValue: Int { return items.reduce(uniqueID.hashValue) { $0
Related Skills
openhue
347.2kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
347.2kElevenLabs text-to-speech with mac-style say UX.
weather
347.2kGet current weather and forecasts via wttr.in or Open-Meteo
Better-Prompt
Publishable Prompt Engineering skill package that compiles a user request into a ready-to-use high-quality Prompt, with support for diagnosis, module injection, debugging, and evaluation.
