{"id":20604865,"url":"https://github.com/allaboutapps/datasource","last_synced_at":"2025-06-23T18:33:34.644Z","repository":{"id":3566528,"uuid":"49724490","full_name":"allaboutapps/DataSource","owner":"allaboutapps","description":"Simplifies the setup of UITableView data sources using type-safe descriptors for cells and sections. Animated diffing built-in.","archived":false,"fork":false,"pushed_at":"2024-05-08T17:23:26.000Z","size":823,"stargazers_count":74,"open_issues_count":6,"forks_count":10,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-12T06:34:58.850Z","etag":null,"topics":["datasource","diffing","id-allaboutapps-ios","ios","swift","uitableview","uitableviewcell","uitableviewdelegate"],"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/allaboutapps.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":"2016-01-15T14:45:43.000Z","updated_at":"2024-06-21T18:20:16.000Z","dependencies_parsed_at":"2024-11-16T09:26:18.216Z","dependency_job_id":"7da4a3b1-6ddd-4b53-aa87-fb461eae2376","html_url":"https://github.com/allaboutapps/DataSource","commit_stats":{"total_commits":156,"total_committers":19,"mean_commits":8.210526315789474,"dds":"0.21794871794871795","last_synced_commit":"a54cc442a034aa9325d7840bfb8ea34e8a403dff"},"previous_names":["mbuchetics/datasource"],"tags_count":50,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allaboutapps%2FDataSource","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allaboutapps%2FDataSource/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allaboutapps%2FDataSource/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allaboutapps%2FDataSource/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/allaboutapps","download_url":"https://codeload.github.com/allaboutapps/DataSource/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248993153,"owners_count":21195146,"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":["datasource","diffing","id-allaboutapps-ios","ios","swift","uitableview","uitableviewcell","uitableviewdelegate"],"created_at":"2024-11-16T09:25:19.019Z","updated_at":"2025-04-15T02:26:55.587Z","avatar_url":"https://github.com/allaboutapps.png","language":"Swift","readme":"# DataSource\n\n[![Swift 5](https://img.shields.io/badge/Language-Swift%205-orange.svg)](https://developer.apple.com/swift)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage)\n[![CocoaPods compatible](https://img.shields.io/cocoapods/v/MBDataSource.svg)](https://cocoapods.org/pods/MBDataSource)\n\nFramework to simplify the setup and configuration of `UITableView` data sources and cells. It allows a type-safe setup of `UITableViewDataSource` and (optionally) `UITableViewDelegate`. `DataSource` also provides out-of-the-box diffing and animated deletions, inserts, moves and changes.\n\n## Usage\n\nAn example app is included demonstrating DataSource's functionality. The example demonstrates various uses cases ranging from a simple list of strings to more complex uses cases such as setting up a dynamic form.\n\n### Getting Started\n\nCreate a `DataSource` with a `CellDescriptor` that describes how the `UITableViewCell` (in this case a `TitleCell`) is configured using a data model (`Example`).\nAdditionally, we also add a handler for `didSelect` which handles the `didSelectRowAtIndexPath` method of `UITableViewDelegate`.\n\n```swift\nlet dataSource: DataSource = {\n    DataSource(\n        cellDescriptors: [\n            CellDescriptor\u003cExample, TitleCell\u003e()\n                .configure { (example, cell, indexPath) in\n                    cell.textLabel?.text = example.title\n                    cell.accessoryType = .disclosureIndicator\n                }\n                .didSelect { (example, indexPath) in\n                    self.performSegue(withIdentifier: example.segue, sender: nil)\n                    return .deselect\n                }\n            ])\n    }()\n)\n```\n\nNext, setup your `dataSource` as the `dataSource` and `delegate` of `UITableView`.\n\n```swift\ntableView.dataSource = dataSource\ntableView.delegate = dataSource\n```\n\nNext, create and set the models. Don't forget to call `reloadData`.\n\n```swift\ndataSource.sections = [\n    Section(items: [\n        Example(title: \"Random Persons\", segue: \"showRandomPersons\"),\n        Example(title: \"Form\", segue: \"showForm\"),\n        Example(title: \"Lazy Rows\", segue: \"showLazyRows\"),\n        Example(title: \"Diff \u0026 Update\", segue: \"showDiff\"),\n    ])\n]\n\ndataSource.reloadData(tableView, animated: false)\n```\n\n### Sections\n\n`DataSource` can also be used to configure section headers and footers. Similar to `CellDescriptors` you can define one or more `SectionDescriptors`:\n\n```swift\nlet dataSource: DataSource = {\n    DataSource(\n        cellDescriptors: [\n            CellDescriptor()\n                .configure { (person, cell, indexPath) in\n                    cell.configure(person: person)\n                }\n        ],\n        sectionDescriptors: [\n            SectionDescriptor\u003cString\u003e()\n                .header { (title, _) in\n                    .title(title)\n                }\n        ])\n}()\n```\n\nSections headers and footers can have custom views (`.view(...)`) or simple titles (`.title(...)`). Delegate methods such as `heightForHeaderInSection` are supported as well (`headerHeight`).\n\n### Diffing\n\nDiffing and animated changes between two sets of data are supported if your data models implement the `Diffable` protocol.\n\n```swift\npublic protocol Diffable {    \n    var diffIdentifier: String { get }    \n    func isEqualToDiffable(_ other: Diffable?) -\u003e Bool\n}\n```\n\n`diffIdentifier` is a `String` identifier which describes whether the identity of two models is different. Think of it as a primary key in a database. Different `diffIdentifiers` will lead to an animated insert, delete or move change. Additionally, `isEqualToDiffable` can be used to describe whether the data or content of a models has changed even if the `diffIdentifier` is the same. For example, if the name of a person was changed in a database, the primarykey of that person would usually remain the same. In such cases you usually don't want an insert, delete or move but instead and (potentially animated) update of the corresponding row in your table.\n\nDiffing is demonstrated by two examples:\n\n`RandomPersonsViewController` creates a random set of persons in two sections and animates the changes between the datasets.\n\n```swift\nprivate func randomData() -\u003e [SectionType] {\n    let count = Int.random(5, 15)\n\n    let persons = (0 ..\u003c count).map { _ in Person.random()  }.sorted()\n\n    let letters = Set([\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\"])\n\n    let firstGroup = persons.filter {\n        $0.lastNameStartsWith(letters: letters)\n    }\n\n    let secondGroup = persons.filter {\n        !$0.lastNameStartsWith(letters: letters)\n    }\n\n    return [\n        Section(\"A - L\", items: firstGroup),\n        Section(\"M - Z\", items: secondGroup),\n    ]\n}\n\n@IBAction func refresh(_ sender: Any) {\n    dataSource.sections = randomData()\n    dataSource.reloadData(tableView, animated: true)\n}\n```\n\n`DiffViewController` creates rows of numbers where the `diffIdentifier` is the number itself and the content is the name of that number in English or German. This shows how the animated row changes can be accomplished.\n\n```swift\nstruct DiffItem {    \n    let value: Int\n    let text: String\n    let diffIdentifier: String\n\n    init(_ value: Int, text: String) {\n        self.value = value\n        self.text = text\n        self.diffIdentifier = String(value)\n    }\n}\n\nextension DiffItem: Diffable {    \n    public func isEqualToDiffable(_ other: Diffable?) -\u003e Bool {\n        guard let other = other as? DiffItem else { return false }\n        return self.text == other.text\n    }\n}\n```\n\nPlease refer to the examples for the full code.\n\n### Hiding Rows and Sections\n\nBoth, `CellDescriptor` and `SectionDescriptor` provide an `isHidden` closure, which allow to simply hide and show rows based on any custom criteria.\n\nThe `FormViewController` example uses this to only show the last name field whenever the first name is not empty, and also shows an \"additional fields\" section whenever a switch is enabled:\n\n```swift\nlazy var dataSource: DataSource = {\n    DataSource(\n        cellDescriptors: [\n            TextFieldCell.descriptor\n                .isHidden { (field, indexPath) in\n                    if field.id == self.lastNameField.id {\n                        return self.firstNameField.text?.isEmpty ?? true\n                    } else {\n                        return false\n                    }\n                },\n            SwitchCell.descriptor,\n            TitleCell.descriptor,\n        ],\n        sectionDescriptors: [\n            SectionDescriptor\u003cVoid\u003e(\"section-name\")\n                .headerHeight { .zero },\n\n            SectionDescriptor\u003cVoid\u003e(\"section-additional\")\n                .header {\n                    .title(\"Additional Fields\")\n                }\n                .isHidden {\n                    !self.switchField.isOn\n                }\n        ])\n    }()\n```\n\nThe `isHidden` closure is evaluated whenever `dataSource.reloadData(...)` is called.\n\n### Delegates and Fallbacks\n\n`DataSource` provides a convenient way to handle all `UITableViewDelegate` methods in a type-safe and simple way using closures. In most cases you define those closures on the `CellDescriptor` or `SectionDescriptor`. However, sometimes this leads to duplicated code if, for example, you have different cells but the code executed for a selection is the same. In these cases you can set the delegate closures on the `DataSource` itself:\n\n```swift\ndataSource.didSelect = { (row, indexPath) in\n    print(\"selected\")\n    return .deselect\n}\n```\n\nThese closures will be used as a fallback if no closure for the specific delegate method is defined on `CellDescriptor` (or `SectionDescriptor`).\n\nAdditionally, you can set a fallback `UITableViewDelegate` and `UITableViewDataSource`, which are again used if the matching closure on `CellDescriptor` or `SectionDescriptor` is not set.\n\n```swift\ndataSource.fallbackDelegate = self\ndataSource.fallbackDataSource = self\n```\n\nUsing these fallback mechanisms you can choose which parts of `DataSource` you want to use in your specific use case. For example, you could use it to setup and configure all your cells, animate changes between datasets but keep your existing `UITableViewDelegate` code.\n\nThe `fallbackDelegate` can be used to implement methods that don't belong to `DataSource`, like e.g. `UIScrollViewDelegate` methods. You should take extra care that the fallback delegate  needs to be set *before* setting the table view delegate, otherwise certain delegate methods will never be called by `UIKit`.\n\n```swift\n// Always set the fallback before setting the table view delegate\ndataSource.fallbackDelegate = self\ntableView.dataSource = dataSource\ntableView.delegate = dataSource\n```\n\n### Custom bundles\n\nCells can be registered from custom bundles. You can specify in the cell descriptor from which bundle the cell should be loaded. The bundle defaults to the main bundle.\n\n```swift\nlet descriptor = CellDescriptor(bundle: customBundle)\n```\n\n### Cell Registration\nIf you define your cell types in a separate xib(outside your tableView definition in a storyboard) or entirely in code your cell needs to be registered with the tableView you want to use it with.\nYou can either register the cell with the tableView manually(see UITableView docs) or let DataSource do that for you by conforming to the `AutoRegisterCell` protocol.\n\n## Version Compatibility\n\nCurrent Swift compatibility breakdown:\n\n| Swift Version | Framework Version |\n| ------------- | ----------------- |\n| 5.1           | 8.x               |\n| 5.0           | 7.x               |\n| 4.2           | 6.x               |\n| 4.1           | 5.x               |\n| 3.x           | 3.x, 4.x          |\n\n[all releases]: https://github.com/mbuchetics/DataSource/releases\n\n## Installation\n\n### Swift Package Manager (Recommended)\n\nAdd the following dependency to your `Package.swift` file:\n\n```\n.package(url: \"https://github.com/allaboutapps/DataSource.git\", from: \"8.0.0\")\n```\n\n### Carthage\n\nAdd the following line to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile).\n\n```\ngithub \"allaboutapps/DataSource\", ~\u003e 8.0\n```\n\nThen run `carthage update`.\n\n### CocoaPods\n\nFor DataSource, use the following entry in your Podfile:\n\n```rb\npod 'MBDataSource'\n```\n\nThen run `pod install`.\n\nIn any file you'd like to use DataSource in, don't forget to import the framework with `import DataSource`.\n\n### Manually\n\nJust drag and drop the `.swift` files in the `DataSource` folder into your project.\n\n## Contributing\n\n* Create something awesome, make the code better, add some functionality,\n  whatever (this is the hardest part).\n* [Fork it](http://help.github.com/forking/)\n* Create new branch to make your changes\n* Commit all your changes to your branch\n* Submit a [pull request](http://help.github.com/pull-requests/)\n\n## Contact\n\nContact me at [matthias.buchetics.com](http://matthias.buchetics.com) or follow me on [Twitter](https://twitter.com/mbuchetics).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallaboutapps%2Fdatasource","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fallaboutapps%2Fdatasource","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallaboutapps%2Fdatasource/lists"}