{"id":21847525,"url":"https://github.com/scribd/livecollections","last_synced_at":"2025-04-06T03:09:21.438Z","repository":{"id":43121633,"uuid":"148819270","full_name":"scribd/LiveCollections","owner":"scribd","description":"Automatically perform UITableView and UICollectionView animations between two sets of immutable data. It supports generic data types and is fully thread-safe.","archived":false,"fork":false,"pushed_at":"2024-05-11T17:07:59.000Z","size":2541,"stargazers_count":342,"open_issues_count":0,"forks_count":14,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-03-30T02:05:21.149Z","etag":null,"topics":["animation","animations","framework","immutable-datastructures","ios","ios-swift","open-source","swift","swift-framework","swift-library","thread-safe","uicollectionview","uitableview"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scribd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-14T17:11:27.000Z","updated_at":"2025-02-11T22:26:22.000Z","dependencies_parsed_at":"2023-11-08T03:53:23.375Z","dependency_job_id":"783d5bfd-300d-44bf-93eb-8b1d0abe04e2","html_url":"https://github.com/scribd/LiveCollections","commit_stats":{"total_commits":77,"total_committers":3,"mean_commits":"25.666666666666668","dds":"0.20779220779220775","last_synced_commit":"f2169509d4c872739ac795a80daa50baa404ce41"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scribd%2FLiveCollections","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scribd%2FLiveCollections/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scribd%2FLiveCollections/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scribd%2FLiveCollections/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scribd","download_url":"https://codeload.github.com/scribd/LiveCollections/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247427006,"owners_count":20937201,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","animations","framework","immutable-datastructures","ios","ios-swift","open-source","swift","swift-framework","swift-library","thread-safe","uicollectionview","uitableview"],"created_at":"2024-11-27T23:18:27.192Z","updated_at":"2025-04-06T03:09:21.403Z","avatar_url":"https://github.com/scribd.png","language":"Swift","readme":"\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/LiveCollections_main_logo.png\" alt=\"LiveCollections logo\"\u003e\n\n\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/LiveCollections_Animated.png\" alt=\"Single section collection view class graph\"\u003e\n\n\u003cb\u003eLiveCollections\u003c/b\u003e 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.\n\nUsing 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.\n\nIn 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).\n\nFull detail for the use case of each scenario \u003ca href=\"https://medium.com/p/59ea1eda2b2d\"\u003ecan be found in the blog post on Medium\u003c/a\u003e, 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.\n\n\u003chr\u003e\n\n\u003ch2\u003eSwift Version\u003c/h2\u003e\n\nThis project has been upgraded to be compatible with Swift 5.5\n\u003cbr\u003e\n\n\u003ch2\u003eiOS Version\u003c/h2\u003e\n\nThis minimum supported version is iOS 11.0\n\u003cbr\u003e\n\n\u003chr\u003e\n\n\n\u003ch2\u003eImporting With SwiftPM\u003c/h2\u003e\n\n\u003cbr\u003e\nhttps://github.com/scribd/LiveCollections 1.0.3\n\u003cbr\u003e\n\u003cbr\u003e\n\n\n\u003ch2\u003eImporting With Carthage\u003c/h2\u003e\n\n\u003cbr\u003e\ngithub \"scribd/LiveCollections\" \"1.0.3\"\n\u003cbr\u003e\n\u003cbr\u003e\n\n\n\u003ch2\u003eImporting With CocoaPods\u003c/h2\u003e\n\n\u003cbr\u003e\npod 'LiveCollections', '~\u003e 1.0.3'\n\u003cbr\u003e\nor\n\u003cbr\u003e\npod 'LiveCollections'\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003chr\u003e\n\n\u003ch1\u003eThe Main Classes\u003c/h1\u003e\n\n\u003cul\u003e \n    \u003cli\u003e\u003cb\u003eCollectionData\u0026lt;YourType\u0026gt;\u003c/b\u003e\u003c/li\u003e\n    \u003cli\u003e\u003cb\u003eCollectionSectionData\u0026lt;YourSectionType\u0026gt;\u003c/b\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\nBy 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 \u003cb\u003eCollectionData\u003c/b\u003e or \u003cb\u003eCollectionSectionData\u003c/b\u003e object directly using the supplied `count`, `subscript`, and `isEmpty` thread-safe accessors.\n\nTo prepare your data to be used in \u003cb\u003eCollectionData\u003c/b\u003e, you just need to adopt the protocol \u003cb\u003eUniquelyIdentifiable\u003c/b\u003e.\nThe requirements for \u003cb\u003eCollectionSectionData\u003c/b\u003e will be detailed in scenerios 5 and 6 a bit later on.\n\n\u003cBR\u003e\n    \n\u003cb\u003eUpdates made easy!\u003c/b\u003e\n\nOnce you create an instance of CollectionData, animating your table or collection view becomes just a single line of code:\n\n```swift\nfunc yourDataUpdateFunction(_ updatedData: [YourDataType]) {\n    collectionData.update(updatedData)\n}\n\n``` \n\u003cBR\u003e\n\nYou'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.\n\n\u003cBR\u003e\n    \n\u003ch2\u003eAdopting the protocol UniquelyIdentifiable\u003c/h2\u003e\n\nThe crux of being able to use \u003cb\u003eCollectionData\u003c/b\u003e as your data source and get all of the benefits of LiveCollections, is by adopting the protocol \u003cb\u003eUniquelyIdentifiable\u003c/b\u003e. It's what allows the private delta calculator in the framework to determine all of the positional moves of your data objects.\n\n```swift\npublic protocol UniquelyIdentifiable: Equatable {\n    associatedtype RawType\n    associatedtype UniqueIDType: Hashable\n    var rawData: RawType { get }\n    var uniqueID: UniqueIDType { get }\n}\n```\n\nSince 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).\n\nHere's a simple example of how it can apply to a custom data class:\n\n```swift\nimport LiveCollections\n\nstruct Movie: Equatable {\n    let id: UInt\n    let title: String\n}\n\nextension Movie: UniquelyIdentifiable {\n    typealias RawType = Movie\n    var uniqueID: UInt { return id }\n}\n```\n\nNote: 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.\n\n\u003cBR\u003e\n\u003ch2\u003eAdopting the protocol NonUniquelyIdentifiable\u003c/h2\u003e\n\nSupport has been added for non-unique sets of data as well. \n\n```swift\npublic protocol NonUniquelyIdentifiable: Equatable {\n    associatedtype NonUniqueIDType: Hashable\n    var nonUniqueID: NonUniqueIDType { get }\n}\n```\n\nBy 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.\n\nSince the data is wrapped in a new struct, to access your original object you'll need to call the `rawData` getter like so:\n\n```\nlet data = collectionData[indexPath.item].rawData\n```\n\n\u003ci\u003eNote:This will use \"best guess\" logic, and the identifiers will be determined based on array order.\u003c/i\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003chr\u003e\n\nListed 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.\n\u003cbr\u003e\n\u003ch2\u003eScenario 1: A UICollectionView with one section\u003c/h2\u003e\n\n\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/single_section_collection_view.png\" alt=\"Single section collection view class graph\"\u003e\n\n```swift\nfinal class YourClass {\n    private let collectionView: UICollectionView\n    private let collectionData: CollectionData\u003cYourData\u003e\n\n    init(_ data: [YourData]) {\n        collectionData = CollectionData(data)\n        ...\n        super.init()\n        collectionData.view = collectionView\n    }\n\n    func someMethodToUpdateYourData(_ data: [YourData]) {\n        collectionData.update(data)\n    }\n}\n\nextension YourClass: UICollectionViewDelegate {\n  \n    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -\u003e Int {\n        return collectionData.count\n    }  \n    \n    // etc\n}\n```\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 2: A UTableView with one section\u003c/h2\u003e\n\u003cbr\u003e\nThe same as scenario 1 but swap in UITableView.\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 3: A UICollectionView with multiple sections, each section has its own CollectionData\u003c/h2\u003e\n\n\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/discrete_sections_collection_view.png\" alt=\"Multiple discrete sections collection view class graph\"\u003e\n\nor\n\n\u003cimg 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\"\u003e\n\n```swift\nfinal class YourClass {\n    private let collectionView: UICollectionView\n    private let dataList: [CollectionData\u003cYourData\u003e]\n\n    init() {\n        // you can also assign section later on if that better fits your class design\n        dataList = [\n             CollectionData\u003cYourData\u003e(section: 0),\n             CollectionData\u003cYourData\u003e(section: 1),\n             CollectionData\u003cYourData\u003e(section: 2)\n        ]\n        ...\n        super.init()\n        \n        // Optionally apply a synchronizer to multiple sections to have them\n        // perform their animations in the same block when possible\n        let synchronizer = CollectionDataSynchronizer(delay: .short)\n        dataList.forEach { $0.synchronizer = synchronizer }\n    }\n\n    func someMethodToUpdateYourData(_ data: [YourData], section: Int) {\n        let collectionData = dataList[section]\n        collectionData.update(data)\n    }\n}\n\nextension YourClass: UICollectionViewDelegate {\n  \n    func numberOfSections(in collectionView: UICollectionView) -\u003e Int {\n        return dataList.count\n    }\n\n    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -\u003e Int {\n        let collectionData = dataList[section]\n        return collectionData.count\n    }  \n    \n    // let item = collectionData[indexPath.row]\n    // etc\n}\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 4: A UITableView with multiple sections, each section has its own CollectionData\u003c/h2\u003e\n\u003cbr\u003e\nThe same as scenario 3 but swap in UITableView.\n\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003chr\u003e\n\u003ch1\u003eUsing unique data across multiple sections? Adopt the protocol UniquelyIdentifiableSection\u003c/h1\u003e\n\nWhen data items are uniquely represented across the entire view, they may move between sections. To handle these animations, you can instead use \u003cb\u003eCollectionSectionData\u003c/b\u003e and create a data item that adopts \u003cb\u003eUniquelyIdentifiableSection\u003c/b\u003e.\n\n```swift\npublic protocol UniquelyIdentifiableSection: UniquelyIdentifiable {\n    associatedtype DataType: UniquelyIdentifiable\n    var items: [DataType] { get }\n}\n```\n\nAs you can see, it still ultiately relies on the base data type that adopts \u003cb\u003eUniquelyIdentifiable\u003c/b\u003e. This new object helps us wrap the section changes.\n\nNote: 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]`.\n\n```swift\nimport LiveCollections\n\nstruct MovieSection: Equatable {\n    let sectionIdentifier: String\n    let movies: [Movie]\n}\n\nextension MovieSection: UniquelyIdentifiableSection {\n    var uniqueID: String { return sectionIdentifier }\n    var items: [Movie] { return movies }\n    var hashValue: Int { return items.reduce(uniqueID.hashValue) { $0 ^ $1.hashValue } }\n}\n```\n\u003chr\u003e\n\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 5: A UICollectionView with multiple sections and a singular data source\u003c/h2\u003e\n\n\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/multi_section_collection_view.png\" alt=\"Multiple section collection view class graph\"\u003e\n\n```swift\nfinal class YourClass {\n    private let collectionView: UICollectionView\n\n    private let collectionData: CollectionSectionData\u003cYourSectionData\u003e\n\n    init(_ data: [YourSectionData]) {\n        collectionData = CollectionSectionData(data)\n        ...\n        super.init()\n        collectionData.view = collectionView\n    }\n\n    func someMethodToUpdateYourData(_ data: [YourSectionData]) {\n        collectionData.update(data)\n    }\n}\n\nextension YourClass: UICollectionViewDelegate {\n  \n    func numberOfSections(in collectionView: UICollectionView) -\u003e Int {\n        return collectionData.sectionCount\n    }\n\n    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -\u003e Int {\n        return collectionData.rowCount(forSection: section)\n    }  \n    \n    // let item = collectionData[indexPath]\n    // etc\n}\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 6: A UITableView with multiple sections and a singular data source\u003c/h2\u003e\n\u003cbr\u003e\nThe same as scenario 5 but swap in UITableView (I bet you didn't see that coming).\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 7: A Table of Carousels\u003c/h2\u003e\n\n\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/table_of_carousels.png\" alt=\"Table of carousels class graph\"\u003e\n\n\u003cb\u003eTable view data source \u003c/b\u003e\n\n```swift\nfinal class YourClass {\n    private let tableView: UITableView\n    private let collectionData: CollectionData\u003cYourData\u003e\n    private let carouselDataSources: [SomeCarouselDataSource]\n\n    init(_ data: [YourData]) {\n        collectionData = CollectionData(data)\n        ...\n        super.init()\n        collectionData.view = tableView\n    }\n\n    func someMethodToUpdateYourData(_ data: [YourData]) {\n        collectionData.update(data)\n    }\n    \n    // some function that fetches a carousel data source based on identifier\n    private func _carouselDataSource(for identifier: Int) -\u003e SomeCarouselDataSource {\n        ....\n    }\n}\n\nextension YourClass: UITableViewDelegate {\n\n    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -\u003e UITableViewCell {\n        // hook up SomeCarouselDataSource to the cell containing the collection view\n        // ideally wrap this logic into a private func rather than exposing these variables\n\n        ...\n        let carouselRow = collectionData[indexPath.row]\n        let carouselDataSource = _carouselDataSource(for: carouselRow.identifier)\n        cell.collectionView.dataSource = carouselDataSource\n        cell.collectionView.delegate = carouselDataSource\n        carouselDataSource.collectionData.view = cell.collectionView\n        ...\n    }\n     \n     // etc\n}\n\nextension YourClass: CollectionDataManualReloadDelegate {\n    \n    // IndexPathPair is a struct that contains both the source index path for the original data set\n    // and the target index path of the updated data set. You may need to know one or both pieces \n    // of information to determine if you want to handle the reload.\n    \n    func willHandleReload(at indexPathPair: IndexPathPair) -\u003e Bool {\n        return true\n    }\n    \n    func reloadItems(at indexPaths: [IndexPath], indexPathCompletion: @escaping (IndexPath) -\u003e Void) {\n\n        indexPaths.forEach { indexPath in\n            let carouselRow = collectionData[indexPath.item]\n            let carouselDataSource = _carouselDataSource(for: carouselRow.identifier)\n            \n            let itemCompletion = {\n                indexPathCompletion(indexPath)\n            }\n            \n            carouselDataSource.update(with: carouselRow.movies, completion: itemCompletion)\n        }\n    }\n    \n    func preferredRowAnimationStyle(for rowDelta: IndexDelta) -\u003e AnimationStyle {\n         return .preciseAnimation\n    }\n}\n```\n\n\u003cb\u003eA table view cell to contain the collection view\u003c/b\u003e\n\n```swift\nfinal class CarouselTableViewCell: UITableViewCell {\n    private let collectionView: UICollectionView\n    ...\n}\n```\n\n\u003cb\u003eCarousel data source\u003c/b\u003e\n\n```swift\nfinal class SomeCarouselDataSource: UICollectionViewDelegate {\n    private let collectionView: UICollectionView\n    private let collectionData: CollectionData\u003cYourData\u003e\n\n    func someMethodToUpdateYourData(_ data: [YourData], completion: (() -\u003e Void)?) {\n        collectionData.update(data, completion: completion)\n    }\n}\n\nextension SomeCarouselDataSource: CollectionDataReusableViewVerificationDelegate {\n    func isDataSourceValid(for view: DeltaUpdatableView) -\u003e Bool {\n        guard let collectionView = view as? UICollectionView,\n            collectionView.delegate === self,\n            collectionView.dataSource === self else {\n                return false\n        }\n        \n        return true\n    }\n}\n\nextension SomeCarouselDataSource: UICollectionViewDataSource {\n    \n    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -\u003e UICollectionViewCell {\n        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SomeCarouselCell.reuseIdentifier, for: indexPath)\n\n        // NOTE: Very imporatant guard. Any time you need to fetch data from an index, first guard that it\n        //       is the correct collection view. Dequeueing table view cells means that we can get into \n        //       situations where views temporarily point to outdated data sources.\n        guard collectionView === collectionData.view else {\n            return cell\n        }\n        \n        let item = collectionData[indexPath.item]\n        ...\n    }\n}\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 8: A Sectioned Table of Carousels (carousels can move between sections)\u003c/h2\u003e\n\n\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/sectioned_table_of_carousels.png\" alt=\"Sectioned table of carousels class graph\"\u003e\n\nAlmost everything is the same as the previous example, except that our table view uses CollectionSectionData.\n\n```swift\nfinal class YourClass {\n    private let tableView: UITableView\n    private let collectionData: CollectionSectionData\u003cYourSectionData\u003e\n    private let carouselDataSources: [SomeCarouselDataSource]\n    \n    ...\n}\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch2\u003eScenario 9: Using a Data Factory\u003c/h2\u003e\n\n\u003cimg src=\"https://github.com/scribd/LiveCollections/blob/master/ReadMe/using_a_data_factory.png\" alt=\"Using a data factory class graph\"\u003e\n\nA data factory you build must conform to the protocol \u003cb\u003eUniquelyIdentifiableDataFactory\u003c/b\u003e. It's role is to simply take in a value of `RawType` and build a new object. This can be as simple as a wrapper class that modifies the equatability function, or could be a complex factory that injects multiple data services that fetch metadata for the RawType item.\n\nWhat using a data factory means is that your update method on \u003cb\u003eCollectionData\u003c/b\u003e takes in [RawType], but returns a value of [UniquelyIdentifiableType] (your new data class) when requesting values via `subscript`. This also saves your data source from needing to know about any custom types you are building to customize your view.\n\nThe `buildQueue` property will default to nil via an extension, and is only needed if your data needs to be build on a specific thread. Otherwise ignore it.\n\n```swift\npublic protocol UniquelyIdentifiableDataFactory {\n\n    associatedtype RawType\n    associatedtype UniquelyIdentifiableType: UniquelyIdentifiable\n\n    var buildQueue: DispatchQueue? { get } // optional queue if your data is thread sensitive\n    func buildUniquelyIdentifiableDatum(_ rawType: RawType) -\u003e UniquelyIdentifiableType\n}\n```\n\nHere is the example I use in the sample app. It takes in an injected controller that looks up whether a movie is currently playing in theaters, and creates a new object that includes this data.  The equatability function includes this metadata, and thus changes the conditions of what constitutes a `reload` action in the view animation.\n\n```swift\nimport LiveCollections\n\nstruct DistributedMovie: Hashable {\n    let movie: Movie\n    let isInTheaters: Bool\n}\n\nextension DistributedMovie: UniquelyIdentifiable {\n    var rawData: Movie { return movie }\n    var uniqueID: UInt { return movie.uniqueID }\n}\n\nstruct DistributedMovieFactory: UniquelyIdentifiableDataFactory {\n\n    private let inTheatersController: InTheatersStateInterface\n    \n    init(inTheatersController: InTheatersStateInterface) {\n        self.inTheatersController = inTheatersController\n    }\n    \n    func buildUniquelyIdentifiableDatum(_ movie: Movie) -\u003e DistributedMovie {\n        let isInTheaters = inTheatersController.isMovieInTheaters(movie)\n        return DistributedMovie(movie: movie, isInTheaters: isInTheaters)\n    }\n}\n```\n\nOnce you build your factory, the only real change to your code is injecting it into the initializer:\n\n```swift\nfinal class YourClass {\n    private let collectionView: UICollectionView\n    private let collectionData: CollectionData\u003cMovie\u003e\n\n    init(_ movies: [Movie]) {\n        let factory = DistributedMovieFactory(inTheatersController: InTheatersController()) \n        collectionData = CollectionData(dataFactory: factory, rawData: movies)\n        ...\n        super.init()\n        collectionData.view = collectionView\n    }\n\n    func someMethodToUpdateYourData(_ movies: [Movie]) {\n        collectionData.update(movies)\n    }\n}\n\nextension YourClass: UICollectionViewDelegate {\n  \n    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -\u003e Int {\n        return collectionData.count\n    }  \n    \n    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -\u003e UICollectionViewCell {\n    \n        ...\n        // note the different type being returned\n        let distributedMovie = collectionData[indexPath.item]\n        ...\n    }\n    \n    // etc\n}\n```\n\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003ch2\u003eScenario 10: Non-unique data in a single section\u003c/h2\u003e\n\nUse the typealiased data struct `NonUniqueCollectionData` with your non-unique data.\n\n```swift\nfinal class YourClass {\n    private let collectionView: UICollectionView\n    private let collectionData: NonUniqueCollectionData\u003cYourData\u003e\n\n    init(_ data: [YourData]) {\n        collectionData = CollectionData(data)\n        ...\n        super.init()\n        collectionData.view = collectionView\n    }\n\n    func someMethodToUpdateYourData(_ data: [YourData]) {\n        collectionData.update(data)\n    }\n}\n\nextension YourClass: UICollectionViewDelegate {\n  \n    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -\u003e Int {\n        return collectionData.count\n    }  \n    \n    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -\u003e UICollectionViewCell {\n    \n        ...\n        // note that your data is wrapped in a unique type, it must be fetched with rawData\n        let movie = collectionData[indexPath.item].rawData\n        ...\n    }\n\n    // etc\n}\n```\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003ch2\u003eScenario 11: Non-unique data in multiple sections\u003c/h2\u003e\n\nUse the typealiased data struct `NonUniqueCollectionSectionData` with your non-unique section data.\n\n```swift\nfinal class YourClass {\n    private let collectionView: UICollectionView\n    private let collectionData: NonUniqueCollectionSectionData\u003cYourSectionData\u003e\n\n    init(_ data: [YourSectionData]) {\n        collectionData = CollectionData(view: view, sectionData: data)\n        ...\n        super.init()\n    }\n\n    func someMethodToUpdateYourData(_ data: [YourData]) {\n        collectionData.update(data)\n    }\n}\n\nextension YourClass: UICollectionViewDelegate {\n  \n    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -\u003e UICollectionViewCell {\n    \n        ...\n        // note that your data is wrapped in a unique type, it must be fetched with rawData\n        let movie = collectionData[indexPath.item].rawData\n        ...\n    }\n\n    // etc\n}\n```\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003ch2\u003eScenario 12: Manual timing of the animation\u003c/h2\u003e\n\nIn every previous case we have assigned the view object to the `CollectionData` object. If you choose to omit this step, you can still get the benefits of LiveCollections caltulations.\n\nSimply do the following:\n\n```swift\nlet delta = collectionData.calculateDelta(data)\n\n// perform any analysis or analytics on the delta\n\nlet updateData = {\n    self.collectionData.update(data)\n}\n\n// when the time is right, call...\ncollectionView.performAnimations(section: collectionData.section, delta: delta, updateData: updateData)\n```\n\nNote: This is unavailable for \u003cb\u003eCollectionSectionData\u003c/b\u003e as the animations occur in multiple steps and the timing of the updates is very specific. \n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003ch2\u003eScenario 13: Custom table view animations\u003c/h2\u003e\n\nBy default LiveCollections performs a preset selection of table view animations: delete (.bottom), insert (.fade), reload (.fade), reloadSection (.none).\n\nAs of 0.9.8 there is support for overriding these defaults and setting your own values. \n\nThere is a new accessor on `CollectionData` to set up your view instead of using `collectionData.view = tableView`.\n\n```swift\nlet rowAnimations = TableViewAnimationModel(deleteAnimation: .right,\n                                            insertAnimation: .right,\n                                            reloadAnimation: .middle)\n\ncollectionData.setTableView(tableView,\n                            rowAnimations: rowAnimations,\n                            sectionReloadAnimation: .top)\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003ch2\u003eScenario 14: Multiple data sources pointing at the same table view with custom animations\u003c/h2\u003e\n\nIf you have multiple data souces each animating a section of a table view, you can give each of them custom animations. They can even be different per section if that's what you really want.\n\n```swift\nlet sectionZeroRowAnimations = TableViewAnimationModel(deleteAnimation: .left,\n                                                       insertAnimation: .left,\n                                                       reloadAnimation: .left)\n\ndataList[0].setTableView(tableView, rowAnimations: sectionZeroRowAnimations)\n\nlet sectionOneRowAnimations = TableViewAnimationModel(deleteAnimation: .right,\n                                                      insertAnimation: .right,\n                                                      reloadAnimation: .right)\n\ndataList[1].setTableView(tableView, rowAnimations: sectionOneRowAnimations)\n\nlet sectionTwoRowAnimations = TableViewAnimationModel(deleteAnimation: .top,\n                                                      insertAnimation: .top,\n                                                      reloadAnimation: .top)\n\ndataList[2].setTableView(tableView, rowAnimations: sectionTwoRowAnimations)\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003ch2\u003eScenario 15: Custom table view animations for data across all sections\u003c/h2\u003e\n\n`CollectionSectionData` has a new initializer.\n\n```swift\nlet rowAnimations = TableViewAnimationModel(deleteAnimation: .right,\n                                            insertAnimation: .right,\n                                            reloadAnimation: .right)\n\nlet sectionAnimations = TableViewAnimationModel(deleteAnimation: .left,\n                                                insertAnimation: .left,\n                                                reloadAnimation: .left)\n\nreturn CollectionSectionData\u003cMovieSection\u003e(tableView: tableView,\n                                           sectionData: initialData,\n                                           rowAnimations: rowAnimations,\n                                           sectionAnimations: sectionAnimations)\n```\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\n\u003chr\u003e\n\u003cbr\u003e\n\nI hope this covers nearly all of the use cases out there, but if you find a gap in what this framework offers, I'd love to hear your suggestions and feedback.\n\nHappy animating!\n\n\u003chr\u003e\n\n\u003cimg width=\"204\" height=\"81\" src=\"https://www.themoviedb.org/assets/1/v4/logos/408x161-powered-by-rectangle-green-bb4301c10ddc749b4e79463811a68afebeae66ef43d17bcfd8ff0e60ded7ce99.png\"\u003e\n\nSpecial thanks to \u003cb\u003eThe Movie Database\u003c/b\u003e. All of the images and data in the sample application were retrieved from their open source API. It's an excellent tool that helped save a lot of time and hassle. Using their data, the examples demonstrate how you can take existing data and extend it to use LiveCollections.\n\n\u003ci\u003eThis product uses the TMDb API but is not endorsed or certified by TMDb.\u003c/i\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscribd%2Flivecollections","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscribd%2Flivecollections","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscribd%2Flivecollections/lists"}