{"id":13905794,"url":"https://github.com/malcommac/Flow","last_synced_at":"2025-07-18T03:31:39.877Z","repository":{"id":51109160,"uuid":"99253559","full_name":"malcommac/Flow","owner":"malcommac","description":"Declarative approach to populate and manage UITableViews (see https://github.com/malcommac/FlowKit)","archived":true,"fork":false,"pushed_at":"2019-04-30T16:04:00.000Z","size":33842,"stargazers_count":421,"open_issues_count":5,"forks_count":21,"subscribers_count":19,"default_branch":"master","last_synced_at":"2024-10-29T03:05:07.227Z","etag":null,"topics":["declarative","swift","tableview","uitableview","uitableviewdatasource","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/malcommac.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-03T16:30:07.000Z","updated_at":"2024-10-03T01:51:59.000Z","dependencies_parsed_at":"2022-08-21T04:20:43.719Z","dependency_job_id":null,"html_url":"https://github.com/malcommac/Flow","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malcommac%2FFlow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malcommac%2FFlow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malcommac%2FFlow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/malcommac%2FFlow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/malcommac","download_url":"https://codeload.github.com/malcommac/Flow/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226336694,"owners_count":17608877,"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":["declarative","swift","tableview","uitableview","uitableviewdatasource","uitableviewdelegate"],"created_at":"2024-08-06T23:01:23.847Z","updated_at":"2024-11-25T13:31:53.992Z","avatar_url":"https://github.com/malcommac.png","language":"Swift","readme":"## Flow\n\n\u003cp align=\"center\" \u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/malcommac/Flow/master/Assets/logo.png\" width=300px height=230px alt=\"Flow\" title=\"Flow\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\" \u003e\n\u003ch1\u003eTHE ENTIRE PROJECT WAS MOVED TO THE NEW HOME AND IT'S NOW CALLED OWL.\u003c/h1\u003e\n\u003ch2\u003e\u003ca href=\"https://github.com/malcommac/Owl\"\u003ehttps://github.com/malcommac/Owl\u003c/a\u003e\u003c/br\u003e\nThis repository will be removed in few months.\n\u003c/p\u003e\n\n[![Version](https://img.shields.io/cocoapods/v/FlowTables.svg?style=flat)](http://cocoadocs.org/docsets/FlowTables) [![License](https://img.shields.io/cocoapods/l/FlowTables.svg?style=flat)](http://cocoadocs.org/docsets/FlowTables) [![Platform](https://img.shields.io/cocoapods/p/FlowTables.svg?style=flat)](http://cocoadocs.org/docsets/FlowTables)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FlowTables.svg)](https://img.shields.io/cocoapods/v/FlowTables.svg)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Twitter](https://img.shields.io/badge/twitter-@danielemargutti-blue.svg?style=flat)](http://twitter.com/danielemargutti)\n\n\n\u003cp align=\"center\" \u003e★★ \u003cb\u003eStar Flow to help the project! \u003c/b\u003e ★★\u003c/p\u003e\n\n\u003cp align=\"center\" \u003e\u003ca href=\"http://paypal.me/danielemargutti\"\u003eSupport the project. \u003cb\u003eDonate now.\u003c/b\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp align=\"center\" \u003eCreated by \u003ca href=\"http://www.danielemargutti.com\"\u003eDaniele Margutti\u003c/a\u003e (\u003ca href=\"http://www.twitter.com/danielemargutti\"\u003e@danielemargutti\u003c/a\u003e)\u003c/p\u003e\n\nFlow is a Swift lightweight library which help you to better manage content in UITableViews.\nIt's easy and fast, perfectly fits the type-safe nature of Swift.\n\n**Say goodbye to `UITableViewDataSource` and `UITableViewDelegate`** : just declare and set your data, let Flow take care of the rest!\n\n## WHAT YOU CAN DO\n\nThe following code is the only required to create a complete TableView which shows a list of some country flags.\nEach flag is represented by a class (the model) called `CountryModel`; the instance is represented into the tableview by the `CountryCell` UITableViewCell subclass.\n\n\n```swift\nlet countries: [CountryModel] = ... // your array of countries\nlet rows = Row\u003cCountryCell\u003e.create(countries, { row in // create rows\nrow.onTap = { _, path in // reponds to tap on cells\n  print(\"Tap on '\\(row.item.name)'\")\n  return nil\n}\ntableManager.add(rows: rows) // just add them to the table\ntableManager.reloadData()\n```\n\nA complete table in few lines of code; **feel amazing uh? Yeah it is**, and there's more:\nYou can handle tap events, customize editing, easy create custom footer and headers and manage the entire content simply as like it was an array!.\n\nA complete article about this topic can be found here:\n[**\"Forget DataSource and Delegates: a new approach for UITableView\"**](http://danielemargutti.com/2017/10/08/forget-datasource-delegates-a-new-approach-to-uitableview/)\n\n## MAIN FEATURES\nMain features of Flow includes:\n* **Declare the content**: Decide cell's class, the model and use array-like methods to add/remove or manage rows into the table. No more data source, no more delegate, just plain understandable methods to manage what kind of data you want to display (auto animations included!).\n* **Separation of concerns**: Let the cell do its damn job; passing represented item (model) to the cell you can add a layer of separation between your model, your view controller and the cell which represent the model itself. Stop doing cell population inside the `tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)` datasource function. Be [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)).\n* **Type-safe**: Describe your cell classes, the model to represent and let the library take care of you. Allocation and configuration is automatic: no more reuse identifier strings, no more dequeue operations, no more casts.\n* **FP (Functional Programming) Style**. Cell configurations is easy; you can observe events and manage the behaviour of the cells in a functional style.\n* **AutoLayout support**: Provide a simple mechanism to specify the height of a cell or leave the class decide the best one upon described constraints.\n* **Animations**: Like `performBatchUpdates` of `UICollectionView` Flow manage automatically what kind of animations perform on the table as you change the layout.\n\n## OTHER LIBRARIES YOU MAY LIKE\n\nI'm also working on several other projects you may like.\nTake a look below:\n\n\u003cp align=\"center\" \u003e\n\n| Library         | Description                                      |\n|-----------------|--------------------------------------------------|\n| [**SwiftDate**](https://github.com/malcommac/SwiftDate)       | The best way to manage date/timezones in Swift   |\n| [**Hydra**](https://github.com/malcommac/Hydra)           | Write better async code: async/await \u0026 promises  |\n| [**Flow**](https://github.com/malcommac/Flow) | A new declarative approach to table managment. Forget datasource \u0026 delegates. |\n| [**SwiftRichString**](https://github.com/malcommac/SwiftRichString) | Elegant \u0026 Painless NSAttributedString in Swift   |\n| [**SwiftLocation**](https://github.com/malcommac/SwiftLocation)   | Efficient location manager                       |\n| [**SwiftMsgPack**](https://github.com/malcommac/SwiftMsgPack)    | Fast/efficient msgPack encoder/decoder           |\n\u003c/p\u003e\n\n* * *\n\n## DOCUMENTATION\n\n* [Main Architecture](#architecture)\n* [Demo Application](#example)\n  * [Create the `TableManager`](#create_tablemanager)\n  * [Prepare a Cell (for Row)](#prepare_cell)\n  * [Prepare a Row](#prepare_row)\n  * [Prepare Rows for an array of model](#prepare_rows_array)\n  * [Add Rows into the table](#add_rows)\n  * [Create `Section` and manage header/footer](#create_section)\n  \n* [`UITableView` Animations](#table_animations)\n* [Observe `Row`/Cell Events](#row_events)\n* [Full SDK Documentation](#sdk)\n* [Changelog](#changelog)\n* [Installation](#installation)\n* [Requirements](#requirements)\n* [Support the project](#donate)\n\n\u003ca name=\"architecture\" /\u003e\n\n### Main Architecture\n\nFlow is composed by four different entities:\n* **`TableManager`**: a single table manager is responsible to manage the content of a `UITableView` instance.\n* **`Section`**: represent a section in table. It manages the list of rows and optional header and footer.\n* **`Row`**: represent a single row in a section; a row is linked to a pair of objects: the model (any class; if not applicable `Void` is valid) and the cell (a subclass of the `UITableViewCell` conforms to `DeclarativeCell` protocol).\n* **`SectionView`**: A section may show header/footer; these objects maybe simple `String` or custom views: `SectionView`. As for `Row`, `SectionView` is linked to a model and a view (subclass of `UITableViewHeaderFooterView`).\n\n![](https://raw.githubusercontent.com/malcommac/Flow/master/Assets/ARCHITECTURE.png)\n\n\u003ca name=\"example\" /\u003e\n\n### Demo Application\n\nA live working example can be found in [FlowDemoApp directory](https://github.com/malcommac/Flow/tree/develop/FlowDemoApp). It demostrates how to use Flow in a simple Login screen for a fake social network app. Check it out to see how Flow can really help you to simplify UITableView management.\n\n\u003ca name=\"create_tablemanager\" /\u003e\n\n#### Create the `TableManager`\n\nIn order to use Flow you must set the ownership of a `UITableView` instance to an instance of `TableManager`:\n\n```swift\nself.tableManager = TableManager(table: self.table!)\n```\n\nFrom now the `UITableView` instance is backed by Flow; every change (add/remove/move rows or sections) must be done by calling appropriate methods of the `TableManager` itself or any child `Section`/`Row`.\n\n\u003ca name=\"prepare_cell\" /\u003e\n\n#### Prepare a Cell (for Row)\n\nA row is resposible to manage the model and its graphical representation (the cell instance).\nTo create a new `Row` instance you need to specify the model class received by the instance cell and the cell class to instantiate into the table.\n\nWhile sometimes a model is not applicable (your cell maybe a simple static representation or its decorative), the cell class is mandatory.\nThe cell must be a subclass of `UITableViewCell` conforms to `DeclarativeCell` protocol.\nThis protocol defines at least two important properties:\n\n- the model assocated with the Cell (`public typealias T = MyClass`)\n- a method called right after the row's cell is dequeued (`public func configure(_: T, path: IndexPath)`)\n\nThis is an example of a `CountryCell` which is responsible to display data for a single country (class `CountryModel`):\n\n```swift\nimport UIKit\nimport Flow\n\npublic class CountryCell: UITableViewCell, DeclarativeCell {\n    // assign to the cell the model to be represented\n    public typealias T = CountryModel\n    // if your cell has a fixed height you can set it directly at class level as below\n    public static var defaultHeight: CGFloat? = 157.0\n\n    // this func is called when a new instance of the cell is dequeued\n    // and you need to fill the data with a model instance.\n    public func configure(_ country: CountryModel, path: IndexPath) {\n      self.countryNameLabel.text = country.name.capitalized()\n      self.continentLabel.image = country.continent\n      self.flagImageView.setURL(country.countryURL)\n      // ... and so on\n    }\n}\n```\n\nIf your cell does not need of a model you can assign `public typealias T = Void`.\n\nUser interface of the cell can be made in two ways:\n* **Prototype (only in Storyboards)**: create a new prototype cell, assign the class to your class (here `CountryCell`) and set the `reuseIdentifier` in IB to the same name of the class (again `CountryCell`). By default Flow uses as identifier of the cell the same name of the class itself (you can change it by overriding `reuseIdentifier` static property).\n* **External XIB File**: create a new xib file with the same name of your cell class (here `CountryCell.xib`) and drag an instance of `UITableViewCell` class as the only single top level object. Assign to it the name of your class and the `reuseIdentifier`.\n\nHeight of a cell can be set in differen ways:\n* If cell has a fixed height you can set it at class level by adding `public static var defaultHeight: CGFloat? = ...` in your cell subclass.\n* If cell is autosized you can evaluate the height in row configuration (see below) by providing a value into `estimatedHeight` or `evaluateEstimatedHeight()` function.\n\n\n\u003ca name=\"prepare_row\" /\u003e\n\n#### Prepare a Row\n\nYou can now create a new row to add into the table; a `Row` instance is created by passing the `DeclarativeCell` type and an instance of the model represented.\n\n```swift\nlet italy = CountryModel(\"Italy\",\"Europe\",\"http://...\")\n...\nlet rowItaly = Row\u003cCountryCell\u003e(model: italy, { row in\n\t// ... configuration\n})\n```\n\nIf model is not applicable just pass `Void()` as model param.\n\nInside the callback you can configure the various aspect of the row behaviour and appearance.\nAll standard UITableView events can be overriden; a common event is `onDequeue`, called when `Row`'s linked cell instance is dequeued and displayed. Anoter one (`onTap`) allows you to perform an action on cell's tap event.\nSo, for example:\n\n```swift\nlet rowItaly = Row\u003cCountryCell\u003e(model: italy, { row in\n\trow.onDequeue = { _ in\n\t\tself.countryNameLabel.text = country.name.capitalized()\n\t\treturn nil // when nil is returned cell will be deselected automatically\n\t}\n\trow.onTap = { _ in\n\t\tprint(\"Show detail for country \\(row.model)\")\n\t}\n})\n```\n\nThere are lots of other events you can set into the row configuration callback (`onDelete`,`onSelect`,`onDeselect`,`onShouldHighlit` and so on).\n\n\u003ca name=\"prepare_rows_array\" /\u003e\n\n#### Prepare Rows for an array of model\n\nWhen you have an array of model instances to represent, one for each Row, you can use `create` shortcut.\nThe following code create an array of `Rows\u003cCountryCell\u003e` where each row receive the relative item from `self.countries` array.\n\n```swift\nlet rowAllCountries = Row\u003cCountryCell\u003e.create(self.countries)\n```\n\u003ca name=\"add_rows\" /\u003e\n\n#### Add Rows into the table\n\nAdding rows to a table is easy as call a simple `add` function.\n\n```swift\nself.tableManager.add(rows: rowAllCountries) // add rows (by appending a new section)\nself.tableManager.reloadData() // apply changes\n```\n\n(Remember: when you add rows without specifing a section a new section is created automatically for you).\n\n**Please note**: when you apply a change to a table (by using `add`, `remove` or `move` functions, both for `Section` or `Row` instances) you must call the `reloadData()` function in order to reflect changes in UI.\n\n\nIf you want to apply changes using standard table's animations just call `update()` function; it allows you to specify a list of actions to perform. In this case `reloadData()` is called automatically for you and the engine evaluate what's changed automatically (inserted/moved/removed rows/section).\n\nThe following example add a new section at the end of the table and remove the first:\n\n```swift\nself.tableManager?.update(animation: .automatic, {\n\tself.tableManager?.insert(section: profileDetailSection, at: profileSection!.index! + 1)\n\tself.tableManager?.remove(sectionAt: 0)\n})\n```\n\n\u003ca name=\"create_section\" /\u003e\n\n#### Create `Section` and manage header/footer\n\nIf not specified sections are created automatically when you add rows into a table.\n`Section` objects encapulate the following properties:\n\n- `rows` list of rows (`RowProtocol`) inside the section\n- `headerTitle` / `headerView` a plain header string or a custom header view (`SectionView`)\n- `footerTitle` / `footerView` a plain footer string or a custom footer view (`SectionView`)\n\nCreating a new section with rows is pretty simple:\n\n```swift\nlet rowCountries: [RowProtocol] = ...\nlet sectionCountries = Section(id: SECTION_ID_COUNTRIES row: rowCountries, headerTitle: \"\\(rowCountries.count) COUNTRIES\")\"\n```\n\nAs like for `Row` even `Section` may have custom view for header or footer; in this case your custom header/footer must be an `UITableViewHeaderFooterView` subclass defined in a separate XIB file (**with the same name of the class**) which is conform to `DeclarativeView` protocol.\n`DeclarativeView` protocol defines the model accepted by the custom view (as for `Row` you can use `Void` if not applicable).\n\nFor example:\n\n```swift\nimport UIKit\nimport Flow\n\npublic class TeamSectionView: UITableViewHeaderFooterView, DeclarativeView {\n public typealias T = TeamModel // the model represented by the view, use `Void` if not applicable\n\t\n public static var defaultHeight: CGFloat? = 100\n\t\n public func configure(_ item: TeamModel, type: SectionType, section: Int) {\n  self.sectionLabel?.text = item.name.uppercased()\n }\n}\n```\n\nNow you can create custom view as header for section:\n\n\n```swift\nlet europeSection = Section(europeRows, headerView: SectionView\u003cContinentSectionView\u003e(team))\nself.tableManager.add(section: europeSection)\nself.tableManager.reloadData()\n```\n\n\n\u003ca name=\"table_animations\" /\u003e\n\n### `UITableView` animations\n\nFlow fully supports animation for `UITableView` changes. As like for `UICollectionView` you will need to call a func which encapsulate the operations you want to apply.\n\nIn Flow it's called `update(animation: UITableViewRowAnimation, block: ((Void) -\u003e (Void)))`.\n\nInside the block you can alter the sections of the table, remove or add rows and section or move things into other locations. At the end of the block Flow will take care to collect the animations needed to reflect applied changes both to the model and the UI and execute them.\n\nYou just need to remember only two things:\n\n* **Deletes are processed before inserts in batch operations**. This means the indexes for the deletions are processed relative to the indexes of the collection view’s state before the batch operation, and the indexes for the insertions are processed relative to the indexes of the state after all the deletions in the batch operation.\n* In order to make a correct refresh of the data, **insertion must be done in order of the row index**.\n\nFor example:\n\n```swift\nself.tableManager?.update(animation: .automatic, {\n\tself.tableManager?.remove(sectionAt: 1) // remove section 1\n\tself.tableManager?.add(row: newCountry, in: self.tableManager?.section(atIndex: 0)) // add a new row in section 0\n})\n```\n\u003ca name=\"row_events\" /\u003e\n\n### Observe `Row`/Cell Events\n\nFlow allows you to encapsulate the logic of your `UITableViewCell` instances directly in your `Row` objects. You can listen for `dequeue`, `tap`, manage `highlights` or `edit`... pratically everything you can do with plain tables, but more confortably.\n\nAll events are available and fully described into the `Row` class.\nIn this example you will see how to respond to the tap:\n\n```swift\n// Respond to tap on countrie's cells\nlet rows = Row\u003cCountryCell\u003e.create(countries, { row in\n  row.onTap = { _,path in\n  print(\"Tap on country at \\(String(path.row)): '\\(row.item.name.capitalized())'\")\n    return nil\n  }\n})\n```\n\nAll observable events are described in [API SDK](API_SDK.md).\n\n* * *\n\n\u003ca name=\"sdk\" /\u003e\n\n## Full SDK Documentation\n\nFull method documentation is available both in source code and in API_SDK file.\nClick here to read the [Full SDK Documentation](API_SDK.md).\n\n\u003ca name=\"changelog\" /\u003e\n\n## Changelog\n\nCurrent version of Flow is 0.8.1.\nFull changelog is available in [CHANGELOG.MD](CHANGELOG.md) file.\n\n\u003ca name=\"installation\" /\u003e\n\n## Installation\n\n\u003ca name=\"cocoapods\" /\u003e\n\n### Install via CocoaPods\n\n[CocoaPods](http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like Flow in your projects. You can install it with the following command:\n\n```bash\n$ gem install cocoapods\n```\n\n\u003e CocoaPods 1.0.1+ is required to build Flow.\n\n#### Install via Podfile\n\nTo integrate Flow into your Xcode project using CocoaPods, specify it in your `Podfile`:\n\n```ruby\nsource 'https://github.com/CocoaPods/Specs.git'\nplatform :ios, '8.0'\n\ntarget 'TargetName' do\n  use_frameworks!\n  pod 'FlowTables'\nend\n```\n\nThen, run the following command:\n\n```bash\n$ pod install\n```\n\n\u003ca name=\"carthage\" /\u003e\n\n### Carthage\n\n[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.\n\nYou can install Carthage with [Homebrew](http://brew.sh/) using the following command:\n\n```bash\n$ brew update\n$ brew install carthage\n```\n\nTo integrate Flow into your Xcode project using Carthage, specify it in your `Cartfile`:\n\n```ogdl\ngithub \"malcommac/Flow\"\n```\n\nRun `carthage` to build the framework and drag the built `Flow.framework` into your Xcode project.\n\n\u003ca name=\"requirements\" /\u003e\n\n## REQUIREMENTS \u0026 LICENSE\nFlow minimum requirements are:\n* iOS 8+\n* Swift 4+ (Swift 3 is supported in [swift-3 branch](https://github.com/malcommac/Flow/tree/swift-3))\n\nWe are supporting both [CocoaPods](#cocoapods) and [Chartage](#carthage).\n\nFlow was created and mantained by [Daniele Margutti](http://www.danielemargutti.com); you can contact me at [hello@danielemargutti.com](mailto://hello@danielemargutti.com) or on twitter at [@danielemargutti](http://www.twitter.com/danielemargutti).\n\nThis library is licensed under [MIT License](https://opensource.org/licenses/MIT).\n\nIf you are using it in your software:\n* Add a notice in your credits/copyright box: `Flow for UITableViews - © 2017 Daniele Margutti - www.danielemargutti.com`\n* *(optional but appreciated)* [Click here to report me](https://github.com/malcommac/flow/issues/new?labels[]=Share\u0026labels[]=[Type]%20Share\u0026title=I'm%20using%20your%20library%20in%20my%20software\u0026body=Hi,%20I'm%20using%20your%20library%20in%20my%20software;%20you%20can%20found%20it%20at%20the%20following%20link:) **your app using Flow**.\n\n\u003ca name=\"donate\" /\u003e\n\n## SUPPORT THE PROJECT\nCreating and mantaining libraries takes time and as developer you know this better than anyone else.\n\nIf you want to contribuite to the development of this project or give to me a thanks please consider to make a small donation using PayPal:\n\n**MAKE A SMALL DONATION** and support the project.\n\n[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](http://paypal.me/danielemargutti)\n\n","funding_links":["http://paypal.me/danielemargutti"],"categories":["Uncategorized","HarmonyOS"],"sub_categories":["Uncategorized","Windows Manager"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmalcommac%2FFlow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmalcommac%2FFlow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmalcommac%2FFlow/lists"}