{"id":13526868,"url":"https://github.com/GoodRequest/GRProvider","last_synced_at":"2025-04-01T08:30:47.299Z","repository":{"id":45409285,"uuid":"265059113","full_name":"GoodRequest/GRProvider","owner":"GoodRequest","description":"🚀UITableView and UICollectionView provider to simplify basic scenarios of showing the data.","archived":false,"fork":false,"pushed_at":"2023-07-04T13:54:36.000Z","size":10416,"stargazers_count":28,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-11-02T11:34:57.707Z","etag":null,"topics":[],"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/GoodRequest.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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}},"created_at":"2020-05-18T20:52:13.000Z","updated_at":"2024-05-25T23:27:34.000Z","dependencies_parsed_at":"2024-01-29T18:06:41.891Z","dependency_job_id":null,"html_url":"https://github.com/GoodRequest/GRProvider","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGRProvider","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGRProvider/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGRProvider/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGRProvider/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GoodRequest","download_url":"https://codeload.github.com/GoodRequest/GRProvider/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246607112,"owners_count":20804521,"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":[],"created_at":"2024-08-01T06:01:36.432Z","updated_at":"2025-04-01T08:30:42.360Z","avatar_url":"https://github.com/GoodRequest.png","language":"Swift","funding_links":[],"categories":["Libs"],"sub_categories":["UI"],"readme":"![Logo](good-provider.png)\n\n# GRProvider\n[![iOS Version](https://img.shields.io/badge/iOS_Version-\u003e=_11.0-brightgreen?logo=apple\u0026logoColor=green)]() [![Swift Version](https://img.shields.io/badge/Swift_Version-5.2-green?logo=swift)](https://docs.swift.org/swift-book/)\n[![Supported devices](https://img.shields.io/badge/Supported_Devices-iPhone/iPad-green)]()\n[![Contains Test](https://img.shields.io/badge/Tests-YES-blue)]()\n[![Dependency Manager](https://img.shields.io/badge/Dependency_Manager-SPM-red)](#swiftpackagemanager)\n\n## Table of content\n\n*  [Why to use?](#why-to-use)\n*  [How to use?](#how-to-use)\n    * [Define model](#define-model)\n    * [Create the instances](#create-the-instance-of-table-view-and-provider-you-choosel)\n    * [Setup](#setup-provider)\n    * [Show items](#show-items-in-table-view)\n    * [Basic sample](#all-together)\n*  [Features](#features)\n    *  [Table View Provider](#table-view-providers)\n    *  [Collection View Provider](#collection-view-provider)\n*  [Animations](#animating-differences)\n*  [Are you missing some property configuration?](#are-you-missing-some-property-configuration)\n*  [Installation](#installation)\n*  [License](#license)\n\n## Why to use?\nWriting the simple scanario, where you want to use the basic implementation of the UITableView or UICollectionView is tedious. \nYou have to implement a lot of delegate methods, even for the simplest use-case.\nI\nExample:  `UITableViewDiffableDataSource` accessible in **iOS 13** is a simple approach of loading/diffing data in TableView \n\nLearning curve is a little bit tedious, but it's worth for the future programing.\n\n**👍 Advantages:**\n- immutable data binding\n- one source of the truth\n- no need to do the custom diffing mechanism\n- avoids synchronization bugs, exceptions and crashes\n- avoids side effects\n- uniform approach accross all providers\n- simple usage of animations using provider with **DeepDiff** library or provider using `UITableViewDiffableDataSource`\n\n**👎 Disadvantages:**\n- learning curve\n- Not a good solution for the complicated/advanced usa case of the TableView or CollectionView\n- different approach than the standard Apple iOS API\n- usage of the third part diffing library DeepDiff\n- contains only the most used delegates methods \n- still in the **development** process\n\nLook at this simple code you just need to write, to show data in Table View:\n```swift\nlazy var tableProvider = GRSimpleTableViewProvider\u003cInt\u003e { provider, tv, indexPath, item in\n    guard let cell = tv.dequeueReusableCell(fromClass: SimpleTableViewCell.self, for: indexPath) else { return UITableViewCell() }\n    cell.titleLabel.text = \"Item with value: \\(item)\"\n    return cell\n}\n\nprivate func showItems() {\n    tableView.items(tableProvider, items: [1, 2, 3, 4, 5, 6, 7], animated: true)\n}\n```\nThat's all you need to do, when showing simple data in the **TableView**. \nIsn't that great? That's all you need to do. \n\nNo need of:\n- assigning `tableView.delegate = self` \u0026 `tableView.dataSource = self` \n- implementation of any DataSource/Delegate methods\n- No need of casting/accessing the cell item property in collection (Arrays etc.)\n\n\n## How to use?\n\nYou can use this type of GRProviders:\n1. `GRSimpleTableViewProvider` -\u003e Use in case of you have just one section in the `UITableView`\n2. `GRTableViewProvider` -\u003e Default provider for the `UITableView`\n3. `GRDiffableTableViewProvider` -\u003e Inherits all functionality of `GRTableViewProvider` but uses `UITableViewDiffableDataSource` API for diffing and animating\n4. `GRDeepDiffTableViewProvider` -\u003e Inherits all functionality of `GRTableViewProvider` but uses `DeepDiff` framework for diffing and animating. More info about framework [click here](https://github.com/onmyway133/DeepDiff).\n5. `GRCollectionViewProvider` -\u003e Default provider for the CollectionView\n6. `GRDeepDiffCollectionViewProvider` -\u003e Inherits all functionality of `GRCollectionViewProvider` but uses `DeepDiff` framework for diffing and animating. More info about framework [click here](https://github.com/onmyway133/DeepDiff).\n7. `GRDiffableCollectionViewProvider` -\u003e CollectionView provider that uses `UICollectionViewDiffableDataSource` as a data source and leaves the layout handling for `collectionViewLayout`. It is recommended to use this provider alongside `UICollectionViewCompositionalLayout` or any custom layout.\n\n#### Define model \nFirstly you need to model your data, showed in the `UITableView` or `UICollectionView` using any type you choose.\n\nFor example:\n\n```swift\n/// using Enum as Section \u0026 Class as Item\nenum Section: Sectionable {\n        \n    case sectionOne([Item])\n    case sectionTwo([Item])\n\n    struct Item {\n        let title: String       \n    }\n    \n}\n\n/// using Class as a Section \u0026 Enum as an Item\nclass Section: Sectionable {\n    let items: [Item]\n    \n    enum Item {\n        case title\n        case divider\n        case advertisement\n    }\n    \n}\n\n/// Define only items without sections\nenum Item {\n    case title\n    case divider\n    case advertisement\n}\n\n```\nYou can model it, based on your preference. It's all up to you.\n\n#### Create the instance of the `UITableView` and `provider` you choose\n\n```swift\n\n@IBOutlet weak var tableView: UITableView!\nprivate let tableProvider = GRSimpleTableViewProvider\u003cItem\u003e()\n\n```\n\n#### Setup your provider\n\n```swift\n\nprivate func setupTableProvider() {            \n    tableProvider.configureCell = { provider, tableView, index, item in              \n        switch item {\n        case .advertisement:\n        return tableView.dequeueReusableCell(....)\n        case .divider:\n        return tableView.dequeueReusableCell(....)\n        case .title:\n        return tableView.dequeueReusableCell(....)\n        }                   \n    }\n}\n\n```\n\n#### Show items in the `UITableView`\n\n```swift\n\nprivate func showItems() {            \n    let items: [Item] = [.title,\n                         .divider,\n                         .advertisement,\n                         .divider,\n                         .advertisement]\n                         \n    tableProvider.bind(to: tableView, items: items)\n}\n\n```\n🔥 That's it. All you need to do to show the simple list with 2 advertisements and title in few lines of code. 🔥\n\n#### All together\nShow list of strings in table view.\n```swift\n\nimport UIKit\nimport GRProvider\n\nclass ViewController: UIViewController {\n\n    @IBOutlet weak var tableView: UITableView!\n    \n    private let tableProvider = GRSimpleTableViewProvider\u003cString\u003e()\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        title = \"Simple Table View Provider\"\n        \n        setupTableView()\n        showItems()\n    }\n\n    private func setupTableView() {\n        tableProvider.estimatedHeightForRow = 100\n                \n        tableProvider.configureCell = { _, tv, index, title in\n            guard let cell = tv.dequeueReusableCell(fromClass: SimpleTableViewCell.self, for: index) else { return UITableViewCell() }\n            cell.titleLabel.text = title\n            return cell\n        }\n    }\n    \n    private func showItems() {\n        let items = (1...10).map { \"Item \\($0)\" }\n        tableProvider.bind(to: tableView, items: items)\n    }\n    \n}\n\n```\n\n\u003e[!WARNING]\n\u003e ⚠️⚠️⚠️  **Retain cycle**: Be careful when using strong reference inside the closure. In case you need to call  `self` inside the closure, don't forget to use `[unowned self]` or `[weak self]`\n\n## Features\n### UITableView Providers\n\nYou can choose one of these types of providers:\n\n1. `GRSimpleTableViewProvider` -\u003e Use in case of you have just one section in the `UITableView`\n2. `GRTableViewProvider` -\u003e Default provider for the `UITableView`\n3. `GRDiffableTableViewProvider` -\u003e Inherits all functionality of `GRTableViewProvider` but uses `UITableViewDiffableDataSource` API for diffing and animating\n4. `GRDeepDiffTableViewProvider` -\u003e Inherits all functionality of `GRTableViewProvider` but uses `DeepDiff` framework for diffing and animating. More info about framework [click here](https://github.com/onmyway133/DeepDiff).\n\n**Features:**\n\n1.  `estimatedHeightForRow: CGFloat`   \nSetup default estimated height of the dequeued cell.\n2. `configureEstimatedCellHeight: CellHeightProvider` \nYou can provide different height estimation for each cell separatelly.\n\n```swift\ntableProvider.configureEstimatedCellHeight = { _, _, _, item in\n    switch item {\n    case .advertisement:\n        return 100\n    case .divider:\n        return 1\n    case .title:\n        return 24\n    }\n}\n```\n3.`configureCellHeight: CellHeightProvider` \nYou can provide different height for each cell separatelly.\n```swift\ntableProvider.configureCellHeight = { _, _, _, item in\n    switch item {\n    case .advertisement:\n    return UITableView.automaticDimension\n    case .divider:\n        return 1\n    case .title:\n        return 24\n    }\n}\n```\n4. `configureCell: CellProvider`\nConfiguration for dequeueing cell based on the model provided in the provider instance.\n```swift\ntableProvider.configureCell = { provider, tableView, index, item in              \n    switch title {\n    case .advertisement:\n    return tableView.dequeueReusableCell(....)\n    case .divider:\n    return tableView.dequeueReusableCell(....)\n    case .title:\n    return tableView.dequeueReusableCell(....)\n    }                   \n}\n```\n5. `configureSectionHeader: SectionHeaderFooterProvider` \u0026 `configureSectionFooter: SectionHeaderFooterProvider`\nReturns `UIView` showed in the header or te footer.\n\n```swift\ntableProvider.configureSectionHeader = { provider, section in\n    let container = UIView()\n    let label = UILabel()\n    label.translatesAutoresizingMaskIntoConstraints = false\n\n    container.addSubview(label)\n\n    label.topAnchor.constraint(equalTo: container.topAnchor, constant: 15).isActive = true\n    label.leftAnchor.constraint(equalTo: container.leftAnchor, constant: 15).isActive = true\n    label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -15).isActive = true\n    label.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -15).isActive = true\n\n    label.text = provider.sections[section].title\n\n    return container\n}\n```\n\u003e ⚠️ Don't forget to setup height of the header. Ex: `tableProvider.heightForHeaderInSection = UITableView.automaticDimension`\n\n6. `configureSectionHeaderHeight: SectionHeaderFooterHeightProvider`  \u0026  `configureSectionFooterHeight: SectionHeaderFooterHeightProvider` \u0026 `heightForFooterInSection: CGFloat` \u0026 `heightForHeaderInSection: CGFloat`\nYou can use one of the property from the list about, to configure height for the footer or the header in the section. In case you want to have different sizes use `configure` methods. Otherwise use  `heightForFooterInSection` and `heightForHeaderInSection`.\n7. `configureOnItemSelected: ItemSelectionProvider`\nOne of the most used property in the provider. Setup the closure variable to be notified about the selection on the cell.\n```swift\ntableProvider.configureOnItemSelected = { [unowned self] _, _, _, item in\n    switch item {\n    case .advertisement:\n        print(\"Advertisement clicked\")\n    case .divider:\n        print(\"Advertisement clicked\")\n    case .title:\n        print(\"Title clicked\")\n    }\n}\n```\n8. `configureTrailingSwipeGesture: SwipeGestureProvider` \u0026 `configureLeadingSwipeGesture: SwipeGestureProvider`\n10. `configureDidScroll: ScrollProvider`\n9. `configureRefreshGesture: ScrollProvider`\nClosure is fired, when the table view contains the refresh control. When `scrollViewDidEndDragging` executes, it automatically checks the refresh control `isRefreshing` property and fires the event.\n10. `configureDidEndDragging: DidEndDraggingProvider`\n11. `configureWillEndDragging: WillEndDraggingProvider`\n\n### UICollectionView Providers\n\nYou can choose one of these types of providers:\n\n1. `GRCollectionViewProvider` -\u003e Default provider for the `UICollectionView`\n2. `GRDeepDiffCollectionViewProvider` -\u003e Inherits all functionality of the `GRCollectionViewProvider` but uses the `DeepDiff` framework for diffing and animating. More info about the framework, [click here](https://github.com/onmyway133/DeepDiff)\n3. `GRDiffableCollectionViewProvider` -\u003e CollectionView provider that uses `UICollectionViewDiffableDataSource` as a data source and leaves the layout handling for `collectionViewLayout`. It is recommended to use this provider alongside `UICollectionViewCompositionalLayout` or any custom layout. Therefore some of the features as `cellSize`, `sectionInsets` and others are not available in this provider, selected `collectionViewLayout` should handle all the layout itself.\n\n**Features:**\n\n1. `configureCell: ItemProvider`\n2. `configureCellSize: ItemSizeProvider`\n3. `configureSupplementaryElementOfKind: SupplementaryViewProvider`\n4. `configureSectionInsets: SectionInsetProvider`\n5. `configureMinLineSpacingForSection: MinLineSpacingProvider`\n6. `configureMinInteritemSpacingForSection: MinLineSpacingProvider`\n7. `configureOnItemSelected: ItemSelectionProvider`\n8. `configureDidScroll: ScrollProvider`\n9. `configureRefreshGesture: ScrollProvider`\n10. `configureWillEndDragging: WillEndDraggingProvider` \u0026 `configureDidEndDragging: DidEndDraggingProvider`\n11. `cellSize: CGSize`\n12. `sectionInsets: UIEdgeInsets`\n13. `minimumLineSpacingForSection: CGFloat`\n14. `minInteritemSpacingForSection: CGFloat`\n\n\n## Animating Differences \n\n### GRDeepDiffTableViewProvider \n**DeepDiff** is a framework, used in past few years. It's fast with great benchmark against other alghoritms. More info about the library and alghoritm find [here](https://github.com/onmyway133/DeepDiff).\n\nIt works simillarly to the `GRDiffableTableViewProvider`, with same API but....\n\n#### What is different?\n\n1. Your Section/Item model definition must comform to the `DiffAware` protocol.\n2. Constructor **doesn't** require instance of the `UITableView` unlike the `DeepDiffTableViewProvider`.\n3. You can modify the animation for the insertion, deletion and replacement, what is not currently possible using the `DeepDiffTableViewProvider`.\n4. Available for all versions of iOS starting iOS 11.\n\n![](Resources/DeepDiff.gif)\n\n### GRDiffableTableViewProvider #iOS13\nApple has released the new API for animating differences in table views and collection views called `UITableViewDiffableDataSource`.\nYou can find the documentation [here](https://developer.apple.com/documentation/uikit/uitableviewdiffabledatasource).\n\n**GRProvider** uses it's benefits and provides you a custom implementation benefitting on the `GRDiffableTableViewProvider`.\nIt uses the same API as other providers, so you don't need to worry about the learning curve. All providers shares their API.\n\n#### What is different?\n\n1. You Section/Item model definition must comform to the `Hashable` protocol.\n2. Constructor of the `GRDiffableTableViewProvider` requires instance of the `UITableView` you will use in items binding.\n\nThese 2 things are required to animate your items differences in the table view. \n\n![](Resources/Diffable.gif)\n\n### GRDeepDiffCollectionViewProvider \nSimilarly to [this section](#GRDeepDiffTableViewProvider)\n\n![](Resources/DeepDiffCollection.gif)\n\n## Are you missing some property configuration?\n\nJust make a subclass of one of the providers and create aditional functionality. It's that simple.\n\n\u003e ⚠️ Be careful of overriding the current functionality. Use `super` to prevent misunderstanding. \n\n### For example\n\n```swift\nclass CustomSimpleTableViewProvider\u003cSection: Sectionable\u003e: GRTableViewProvider\u003cSection\u003e {\n    \n    open var configureDidHighlightRow: ((CustomSimpleTableViewProvider, UITableView, Section.Item) -\u003e ())?\n    \n    func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {\n        configureDidHighlightRow?(self, tableView, sections[indexPath.section].items[indexPath.row])\n    }\n    \n    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -\u003e Int {\n        print(\"Number of rows for section: \\(section)\")\n        return super.tableView(tableView, numberOfRowsInSection: section)\n    }\n    \n}\n```\n\n# Installation\n## Swift Package Manager\n\nCreate a `Package.swift` file.\n\n```swift\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"SampleProject\",\n    dependencies: [\n        .Package(url: \"https://github.com/smajdalf/GRProvider\" from: \"0.0.1\")\n    ]\n)\n\n```\nIf you are using Xcode 11 or higher, go to File / Swift Packages / Add Package Dependency... and enter package repository URL https://github.com/smajdalf/GRProvider, then follow the instructions\n\n# License\nGRProvider is released under the MIT license. See [LICENSE](LICENSE.md) for details.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGoodRequest%2FGRProvider","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGoodRequest%2FGRProvider","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGoodRequest%2FGRProvider/lists"}