Populates the collection with the specified sections, and asynchronously updates the collection view to reflect the cells and sections that have changed.
+
+
+
+
Declaration
+
+
Swift
+
@available(*, deprecated, message: "Call `scroll(to:animated:scrollPosition:﹚` in the completion handler instead.")
+publicfuncrenderAndDiff(_newSections:[TableSection],keyPath:KeyPath?,animated:Bool=true,completion:(()->Void)?=nil)
+
+
+
+
+
Parameters
+
+
+
+
+
+ newSections
+
+
+
+
+
An array of TableSection instances to populate the collection with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ keyPath
+
+
+
+
+
The key path identifying which cell to scroll into view after the render occurs.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the collection cells, or false if the UICollectionView should be updated with no animation.
+
+
+
+
+
+
+ completion
+
+
+
+
+
Callback that will be called on the main thread once the UICollectionView has finished updating and animating any changes.
Populates the collection with the specified sections, and asynchronously updates the collection view to reflect the cells and sections that have changed.
An array of TableSection instances to populate the collection with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the collection cells, or false if the UICollectionView should be updated with no animation.
+
+
+
+
+
+
+ completion
+
+
+
+
+
Callback that will be called on the main thread once the UICollectionView has finished updating and animating any changes.
A key path identifying a row in the collection view.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true if you want to animate the selection, and false if the change should be immediate.
+
+
+
+
+
+
+ scrollPosition
+
+
+
+
+
An option that specifies where the item should be positioned when scrolling finishes.
+
+
+
+
+
+
+ triggerDelegate
+
+
+
+
+
true to trigger the collection:didSelectItemAt: delegate from UICollectionView or false to skip it. Skipping it is the default UICollectionView behavior.
This is an undocumented optional UIScrollViewDelegate method that is not exposed by the public protocol
+but will still get called on delegates that implement it. Because it is not publicly exposed,
+the Swift 4 compiler will not automatically annotate it as @objc, requiring this manual annotation.
Enclosing UITableView that presents all the TableSection data.
+
+
FunctionalTableData will take care of setting its own UITableViewDelegate and
+UITableViewDataSource and manage all the internals of the UITableView on its own.
Populates the table with the specified sections, and asynchronously updates the table view to reflect the cells and sections that have changed.
+
+
+
+
Declaration
+
+
Swift
+
@available(*, deprecated, message: "Call `scroll(to:animated:scrollPosition:﹚` in the completion handler instead.")
+publicfuncrenderAndDiff(_newSections:[TableSection],keyPath:KeyPath?,animated:Bool=true,animations:TableAnimations=.default,completion:(()->Void)?=nil)
+
+
+
+
+
Parameters
+
+
+
+
+
+ newSections
+
+
+
+
+
An array of TableSection instances to populate the table with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ keyPath
+
+
+
+
+
The key path identifying which cell to scroll into view after the render occurs.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the table cells, or false if the UITableView should be updated with no animation.
An array of TableSection instances to populate the table with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the table cells, or false if the UITableView should be updated with no animation.
This is an undocumented optional UIScrollViewDelegate method that is not exposed by the public protocol
+but will still get called on delegates that implement it. Because it is not publicly exposed,
+the Swift 4 compiler will not automatically annotate it as @objc, requiring this manual annotation.
Initiates a layout pass of UICollectionView and its items. Necessary for calculating new
+cell heights and animations when the internal state of a cell changes and needs to reflect
+them immediately.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UICollectionViewCell, or if that cell is not a child of self
+
+
+
+
Declaration
+
+
Swift
+
publicfuncindexPath(forview:UIView)->IndexPath?
+
+
+
+
+
Parameters
+
+
+
+
+
+ view
+
+
+
+
+
The view to find the IndexPath.
+
+
+
+
+
+
+
+
Return Value
+
The IndexPath of the view in the UICollectionView or nil if it could not be found.
Initiates a layout pass of UITableView and its items. Necessary for calculating new
+cell heights and animations when the internal state of a cell changes and needs to reflect
+them immediately.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UITableViewCell, or if that cell is not a child of self
+
+
+
+
Declaration
+
+
Swift
+
publicfuncindexPath(forview:UIView)->IndexPath?
+
+
+
+
+
Parameters
+
+
+
+
+
+ view
+
+
+
+
+
The view to find the IndexPath.
+
+
+
+
+
+
+
+
Return Value
+
The IndexPath of the view in the UITableView or nil if it could not be found.
A type that provides the information required by FunctionalTableData to generate cells.
+
+
The key property should be a unique String for the section that the item is contained in. It should also be representative of the item and not just a random value or UUID. This is because on an update pass the key is used to determine if an item has been added, moved, or removed from the data set. Using a stable value means that this can correctly be determined.
+
+
The isEqual function is used to determine if two CellConfigType’s have matching keys and they represent the same data. This allows the system to update the views state directly when something has changed instead of forcing a reload of the entire cell all the time.
+
+
When two items have matching key values but the isEqual call between old and new returns false the update function is called. It is the responsibility of this function to update the cell, and any subviews of the cell, to reflect the state.
A type that identifies a dequeueable object. Used by FunctionalTableData to increase performance by reusing objects when it needs to, just like UITableView and UICollectionView.
FunctionalTableData deals in arrays of TableSection instances. Each section, at a minimum, has a string value unique within the table itself, and an array of CellConfigType instances that represent the rows of the section. Additionally there may be a header and footer for the section.
A type that provides the information required by FunctionalTableData to generate cells.
+
+
The key property should be a unique String for the section that the item is contained in. It should also be representative of the item and not just a random value or UUID. This is because on an update pass the key is used to determine if an item has been added, moved, or removed from the data set. Using a stable value means that this can correctly be determined.
+
+
The isEqual function is used to determine if two CellConfigType’s have matching keys and they represent the same data. This allows the system to update the views state directly when something has changed instead of forcing a reload of the entire cell all the time.
+
+
When two items have matching key values but the isEqual call between old and new returns false the update function is called. It is the responsibility of this function to update the cell, and any subviews of the cell, to reflect the state.
Handles the exception. This is only for debugging purposes, and commonly used
+by storing the state information into the filesystem for a developer review.
A type that identifies a dequeueable object. Used by FunctionalTableData to increase performance by reusing objects when it needs to, just like UITableView and UICollectionView.
FunctionalTableData deals in arrays of TableSection instances. Each section, at a minimum, has a string value unique within the table itself, and an array of CellConfigType instances that represent the rows of the section. Additionally there may be a header and footer for the section.
The actions property exposed on the CellConfigType represents possible events that will be executed based on the users interaction with that particular cell. Of note are the selectionAction and previewingViewControllerAction. The selectionAction is executed when the user taps on that particular cell. The main use case for this is present a new detail view controller or a modal (but is not constrained to these actions, these are just the common use cases). The previewingViewControllerAction is responsible for returning an instance of a UIViewController that will be shown when a user 3D-touches on a cell.
Defines the view, state and layout information of a row item inside a TableSection.
+It relies on you to build UIView subclasses and use those instead of implementing UITableViewCell or UICollectionViewCell subclasses. This has the side effect of building better more reusable view components. This greatly simplifies composition by combining several host-cells into more complex layouts. It also makes equality simpler and more Swifty by requiring that anything provided as State only requires that the State object conform to the Equatable protocol. The View portion of the generic only requires it to be a UIView subclass.
The actions property exposed on the CellConfigType represents possible events that will be executed based on the users interaction with that particular cell. Of note are the selectionAction and previewingViewControllerAction. The selectionAction is executed when the user taps on that particular cell. The main use case for this is present a new detail view controller or a modal (but is not constrained to these actions, these are just the common use cases). The previewingViewControllerAction is responsible for returning an instance of a UIViewController that will be shown when a user 3D-touches on a cell.
The action to perform when the cell will be selected.
+
+
Important
+ When the canSelectAction is called, it is passed a CanSelectCallback closure. It is the responsibility of the action to eventually call the passed in closure providing either a true or false value to it. This passed in value determines if the selection will be performed or not.
+
+
Defines the view, state and layout information of a row item inside a TableSection.
+It relies on you to build UIView subclasses and use those instead of implementing UITableViewCell or UICollectionViewCell subclasses. This has the side effect of building better more reusable view components. This greatly simplifies composition by combining several host-cells into more complex layouts. It also makes equality simpler and more Swifty by requiring that anything provided as State only requires that the State object conform to the Equatable protocol. The View portion of the generic only requires it to be a UIView subclass.
A function that updates a cell’s view to match the current state. It receives two values, the view instance and an optional state instance. The purpose of this function is to update the view to reflect that of the given state. The reason that the state is optional is because cells may move into the reuse queue. When this happens they no longer have a state and the updater function is called giving the opportunity to reset the view to its default value.
Populates the collection with the specified sections, and asynchronously updates the collection view to reflect the cells and sections that have changed.
+
+
+
+
Declaration
+
+
Swift
+
@available(*, deprecated, message: "Call `scroll(to:animated:scrollPosition:﹚` in the completion handler instead.")
+publicfuncrenderAndDiff(_newSections:[TableSection],keyPath:KeyPath?,animated:Bool=true,completion:(()->Void)?=nil)
+
+
+
+
+
Parameters
+
+
+
+
+
+ newSections
+
+
+
+
+
An array of TableSection instances to populate the collection with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ keyPath
+
+
+
+
+
The key path identifying which cell to scroll into view after the render occurs.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the collection cells, or false if the UICollectionView should be updated with no animation.
+
+
+
+
+
+
+ completion
+
+
+
+
+
Callback that will be called on the main thread once the UICollectionView has finished updating and animating any changes.
Populates the collection with the specified sections, and asynchronously updates the collection view to reflect the cells and sections that have changed.
An array of TableSection instances to populate the collection with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the collection cells, or false if the UICollectionView should be updated with no animation.
+
+
+
+
+
+
+ completion
+
+
+
+
+
Callback that will be called on the main thread once the UICollectionView has finished updating and animating any changes.
A key path identifying a row in the collection view.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true if you want to animate the selection, and false if the change should be immediate.
+
+
+
+
+
+
+ scrollPosition
+
+
+
+
+
An option that specifies where the item should be positioned when scrolling finishes.
+
+
+
+
+
+
+ triggerDelegate
+
+
+
+
+
true to trigger the collection:didSelectItemAt: delegate from UICollectionView or false to skip it. Skipping it is the default UICollectionView behavior.
This is an undocumented optional UIScrollViewDelegate method that is not exposed by the public protocol
+but will still get called on delegates that implement it. Because it is not publicly exposed,
+the Swift 4 compiler will not automatically annotate it as @objc, requiring this manual annotation.
Enclosing UITableView that presents all the TableSection data.
+
+
FunctionalTableData will take care of setting its own UITableViewDelegate and
+UITableViewDataSource and manage all the internals of the UITableView on its own.
Populates the table with the specified sections, and asynchronously updates the table view to reflect the cells and sections that have changed.
+
+
+
+
Declaration
+
+
Swift
+
@available(*, deprecated, message: "Call `scroll(to:animated:scrollPosition:﹚` in the completion handler instead.")
+publicfuncrenderAndDiff(_newSections:[TableSection],keyPath:KeyPath?,animated:Bool=true,animations:TableAnimations=.default,completion:(()->Void)?=nil)
+
+
+
+
+
Parameters
+
+
+
+
+
+ newSections
+
+
+
+
+
An array of TableSection instances to populate the table with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ keyPath
+
+
+
+
+
The key path identifying which cell to scroll into view after the render occurs.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the table cells, or false if the UITableView should be updated with no animation.
An array of TableSection instances to populate the table with. These will replace the previous sections and update any cells that have changed between the old and new sections.
+
+
+
+
+
+
+ animated
+
+
+
+
+
true to animate the changes to the table cells, or false if the UITableView should be updated with no animation.
This is an undocumented optional UIScrollViewDelegate method that is not exposed by the public protocol
+but will still get called on delegates that implement it. Because it is not publicly exposed,
+the Swift 4 compiler will not automatically annotate it as @objc, requiring this manual annotation.
Initiates a layout pass of UICollectionView and its items. Necessary for calculating new
+cell heights and animations when the internal state of a cell changes and needs to reflect
+them immediately.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UICollectionViewCell, or if that cell is not a child of self
+
+
+
+
Declaration
+
+
Swift
+
publicfuncindexPath(forview:UIView)->IndexPath?
+
+
+
+
+
Parameters
+
+
+
+
+
+ view
+
+
+
+
+
The view to find the IndexPath.
+
+
+
+
+
+
+
+
Return Value
+
The IndexPath of the view in the UICollectionView or nil if it could not be found.
Initiates a layout pass of UITableView and its items. Necessary for calculating new
+cell heights and animations when the internal state of a cell changes and needs to reflect
+them immediately.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UITableViewCell, or if that cell is not a child of self
+
+
+
+
Declaration
+
+
Swift
+
publicfuncindexPath(forview:UIView)->IndexPath?
+
+
+
+
+
Parameters
+
+
+
+
+
+ view
+
+
+
+
+
The view to find the IndexPath.
+
+
+
+
+
+
+
+
Return Value
+
The IndexPath of the view in the UITableView or nil if it could not be found.
A type that provides the information required by FunctionalTableData to generate cells.
+
+
The key property should be a unique String for the section that the item is contained in. It should also be representative of the item and not just a random value or UUID. This is because on an update pass the key is used to determine if an item has been added, moved, or removed from the data set. Using a stable value means that this can correctly be determined.
+
+
The isEqual function is used to determine if two CellConfigType’s have matching keys and they represent the same data. This allows the system to update the views state directly when something has changed instead of forcing a reload of the entire cell all the time.
+
+
When two items have matching key values but the isEqual call between old and new returns false the update function is called. It is the responsibility of this function to update the cell, and any subviews of the cell, to reflect the state.
A type that identifies a dequeueable object. Used by FunctionalTableData to increase performance by reusing objects when it needs to, just like UITableView and UICollectionView.
FunctionalTableData deals in arrays of TableSection instances. Each section, at a minimum, has a string value unique within the table itself, and an array of CellConfigType instances that represent the rows of the section. Additionally there may be a header and footer for the section.
A type that provides the information required by FunctionalTableData to generate cells.
+
+
The key property should be a unique String for the section that the item is contained in. It should also be representative of the item and not just a random value or UUID. This is because on an update pass the key is used to determine if an item has been added, moved, or removed from the data set. Using a stable value means that this can correctly be determined.
+
+
The isEqual function is used to determine if two CellConfigType’s have matching keys and they represent the same data. This allows the system to update the views state directly when something has changed instead of forcing a reload of the entire cell all the time.
+
+
When two items have matching key values but the isEqual call between old and new returns false the update function is called. It is the responsibility of this function to update the cell, and any subviews of the cell, to reflect the state.
Handles the exception. This is only for debugging purposes, and commonly used
+by storing the state information into the filesystem for a developer review.
A type that identifies a dequeueable object. Used by FunctionalTableData to increase performance by reusing objects when it needs to, just like UITableView and UICollectionView.
FunctionalTableData deals in arrays of TableSection instances. Each section, at a minimum, has a string value unique within the table itself, and an array of CellConfigType instances that represent the rows of the section. Additionally there may be a header and footer for the section.
The actions property exposed on the CellConfigType represents possible events that will be executed based on the users interaction with that particular cell. Of note are the selectionAction and previewingViewControllerAction. The selectionAction is executed when the user taps on that particular cell. The main use case for this is present a new detail view controller or a modal (but is not constrained to these actions, these are just the common use cases). The previewingViewControllerAction is responsible for returning an instance of a UIViewController that will be shown when a user 3D-touches on a cell.
Defines the view, state and layout information of a row item inside a TableSection.
+It relies on you to build UIView subclasses and use those instead of implementing UITableViewCell or UICollectionViewCell subclasses. This has the side effect of building better more reusable view components. This greatly simplifies composition by combining several host-cells into more complex layouts. It also makes equality simpler and more Swifty by requiring that anything provided as State only requires that the State object conform to the Equatable protocol. The View portion of the generic only requires it to be a UIView subclass.
The actions property exposed on the CellConfigType represents possible events that will be executed based on the users interaction with that particular cell. Of note are the selectionAction and previewingViewControllerAction. The selectionAction is executed when the user taps on that particular cell. The main use case for this is present a new detail view controller or a modal (but is not constrained to these actions, these are just the common use cases). The previewingViewControllerAction is responsible for returning an instance of a UIViewController that will be shown when a user 3D-touches on a cell.
The action to perform when the cell will be selected.
+
+
Important
+ When the canSelectAction is called, it is passed a CanSelectCallback closure. It is the responsibility of the action to eventually call the passed in closure providing either a true or false value to it. This passed in value determines if the selection will be performed or not.
+
+
Defines the view, state and layout information of a row item inside a TableSection.
+It relies on you to build UIView subclasses and use those instead of implementing UITableViewCell or UICollectionViewCell subclasses. This has the side effect of building better more reusable view components. This greatly simplifies composition by combining several host-cells into more complex layouts. It also makes equality simpler and more Swifty by requiring that anything provided as State only requires that the State object conform to the Equatable protocol. The View portion of the generic only requires it to be a UIView subclass.
A function that updates a cell’s view to match the current state. It receives two values, the view instance and an optional state instance. The purpose of this function is to update the view to reflect that of the given state. The reason that the state is optional is because cells may move into the reuse queue. When this happens they no longer have a state and the updater function is called giving the opportunity to reset the view to its default value.
Functional Table Data implements a functional renderer for UITableView. You pass it a complete description of your table state, and Functional Table Data compares it with the previous render call to insert, update, and remove the sections and cells that have changed. This massively simplifies state management of complex UI.
+
+
No longer do you have to manually track the number of sections, cells, and indices of your UI. Build one method that generates your table state structure from your data. The provided HostCell generic makes it easy to add FunctionalTableData support to UITableViewCells.
+
+
+
+
+
Noteworthy features
+
+
+
+
💯
+
Functional approach for maintaining table state
+
+
+
👷
+
Reusable views and states
+
+
+
✅
+
Unit tests
+
+
+
🔀
+
Automatic diff in your states
+
+
+
❤️
+
Used across Shopify’s iOS apps
+
+
+
🙅
+
No more IndexPath bookkeeping
+
+
+
Installation
+
Manual
+
+
Simply drag and drop the FunctionalTableData/FunctionalTableData folder into your Xcode project.
+
Carthage
+
+
Add the following to your Cartfile:
+
github"Shopify/FunctionalTableData"
+
+
Getting started
+
+
To use the Functional Table Data (FTD) two things are required, one instance of UITableView, and an instance of the FTD itself. Once both are available, typically in a view controller’s viewDidLoad, they are connected together using
+functionalTableData.tableView = yourTableViewInstance. After this, every time we want to display/update the data we simply call functionalTableData.renderAndDiff(sections).
+
Usage
+
+
Check out the demo app for a fully interactive example.
+
+
Any time you want to update the data currently being displayed you generate the new state and pass it off to your instance of the Functional Table Data. The FTD is then responsible for computing the differences between the previous state and the next state and updating itself as necessary.
+
+
The FunctionalTableData holds onto an array of sections where each section has a key. This key must be unique across all sections but should be deterministic so that its possible to adjust the rows contained within that section without replacing the entire section itself.
Each section contains a series of rows where each row value must conform to the CellConfigType protocol.
+
/// The simplest possible version of a cell that displays a label.
+/// Useful to get started, but in most cases a more robust state should be used allowing more customization.
+typealiasLabelCell=HostCell<UILabel,String,LayoutMarginsTableItemLayout>
+
+letcells:[CellConfigType]=[
+ LabelCell(key:"company",state:"Shopify"){view,statein
+ view.text=state
+ },
+ LabelCell(key:"location",state:"🇨🇦"){view,statein
+ view.text=state
+ }
+]
+
+
+
The rows themselves also have a key which must be unique inside of that section. This key is used to determine when new rows are added to a section, if any were removed, or if any moved to a different location.
+Additionally, each CellConfig type implements an isEqual function to determine if two of them represent the same data being displayed. This allows for a single cell to perform a state change operation, that is, a toggle changing from its off to on state, a text value changing, etc.
+
+
After assigning the variable rows to our previously created section, all that is needed to display the data in the table view is this method.
+
functionalTableData.renderAndDiff([section])
+
+
+
+
+
Check out the demo app for a fully interactive example.
+
Building new Cells
+
+
Knowing that a cell consists of a view and state let’s start with a simple example, a cell that displays a label. By specifying the generic requirements of HostCell, the simplest possible example is one that takes an UILabel as its view, a String as its state and LayoutMarginsTableItemLayout as the layout (See TableItemLayout for more info).
Although, the previous code is very useful to get started, in most cases a more robust state should be used allowing more customization. A better example would be something like this
+
typealiasLabelCell=HostCell<UILabel,LabelState,LayoutMarginsTableItemLayout>
+
+structLabelState:Equatable{
+ lettext:String
+ letalignment:NSTextAlignment
+ letcolor:UIColor
+
+ staticfunc==(lhs:LabelState,rhs:LabelState)->Bool{
+ returnlhs.text==rhs.text&&lhs.alignment==rhs.alignment&&lhs.color==rhs.color
+ }
+}
+
+// Usage
+LabelCell(key:"company",state:LabelState(text:"Shopify",
+ alignment:.center,
+ color:.green)){view,statein
+ guardletstate=stateelse{
+ // If the state is `nil`, prepare this view to be reused
+ view.text=""
+ view.textAlignment=.natural
+ view.textColor=.black
+ return
+ }
+ view.text=state.text
+ view.textAlignment=state.alignment
+ view.textColor=state.color
+}
+
+
+
At the end of the day HostCell is just one of the possible implementations of CellConfigType, that’s the underlying power of this framework.
A function that updates a cell’s view to match the current state. It receives two values, the view instance and an optional state instance. The purpose of this function is to update the view to reflect that of the given state. The reason that the state is optional is because cells may move into the reuse queue. When this happens they no longer have a state and the updater function is called giving the opportunity to reset the view to its default value.
The actions property exposed on the CellConfigType represents possible events that will be executed based on the users interaction with that particular cell. Of note are the selectionAction and previewingViewControllerAction. The selectionAction is executed when the user taps on that particular cell. The main use case for this is present a new detail view controller or a modal (but is not constrained to these actions, these are just the common use cases). The previewingViewControllerAction is responsible for returning an instance of a UIViewController that will be shown when a user 3D-touches on a cell.
Defines the view, state and layout information of a row item inside a TableSection."},"Protocols/FunctionalTableDataExceptionHandler.html#/s:19FunctionalTableData0abC16ExceptionHandlerP6handleyA2AC0D0V9exception_tF":{"name":"handle(exception:)","abstract":"
Handles the exception. This is only for debugging purposes, and commonly used","parent_name":"FunctionalTableDataExceptionHandler"},"Protocols/TableSectionType.html#/s:19FunctionalTableData0B11SectionTypeP3keySSvp":{"name":"key","abstract":"
A type that identifies a dequeueable object. Used by FunctionalTableData to increase performance by reusing objects when it needs to, just like UITableView and UICollectionView.
Initiates a layout pass of UITableView and its items. Necessary for calculating new","parent_name":"UITableView"},"Extensions/UITableView.html#/s:So11UITableViewC19FunctionalTableDataE23deselectLastSelectedRowySb8animated_tF":{"name":"deselectLastSelectedRow(animated:)","abstract":"
Deselects the previously selected row, with an option to animate the deselection.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UITableViewCell, or if that cell is not a child of self
Initiates a layout pass of UICollectionView and its items. Necessary for calculating new","parent_name":"UICollectionView"},"Extensions/UICollectionView.html#/s:So16UICollectionViewC19FunctionalTableDataE24deselectLastSelectedItemySb8animated_tF":{"name":"deselectLastSelectedItem(animated:)","abstract":"
Deselects the previously selected row, with an option to animate the deselection.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UICollectionViewCell, or if that cell is not a child of self
Functional Table Data implements a functional renderer for UITableView. You pass it a complete description of your table state, and Functional Table Data compares it with the previous render call to insert, update, and remove the sections and cells that have changed. This massively simplifies state management of complex UI.
+
+
No longer do you have to manually track the number of sections, cells, and indices of your UI. Build one method that generates your table state structure from your data. The provided HostCell generic makes it easy to add FunctionalTableData support to UITableViewCells.
+
+
+
+
+
Noteworthy features
+
+
+
+
💯
+
Functional approach for maintaining table state
+
+
+
👷
+
Reusable views and states
+
+
+
✅
+
Unit tests
+
+
+
🔀
+
Automatic diff in your states
+
+
+
❤️
+
Used across Shopify’s iOS apps
+
+
+
🙅
+
No more IndexPath bookkeeping
+
+
+
Installation
+
Manual
+
+
Simply drag and drop the FunctionalTableData/FunctionalTableData folder into your Xcode project.
+
Carthage
+
+
Add the following to your Cartfile:
+
github"Shopify/FunctionalTableData"
+
+
Getting started
+
+
To use the Functional Table Data (FTD) two things are required, one instance of UITableView, and an instance of the FTD itself. Once both are available, typically in a view controller’s viewDidLoad, they are connected together using
+functionalTableData.tableView = yourTableViewInstance. After this, every time we want to display/update the data we simply call functionalTableData.renderAndDiff(sections).
+
Usage
+
+
Check out the demo app for a fully interactive example.
+
+
Any time you want to update the data currently being displayed you generate the new state and pass it off to your instance of the Functional Table Data. The FTD is then responsible for computing the differences between the previous state and the next state and updating itself as necessary.
+
+
The FunctionalTableData holds onto an array of sections where each section has a key. This key must be unique across all sections but should be deterministic so that its possible to adjust the rows contained within that section without replacing the entire section itself.
Each section contains a series of rows where each row value must conform to the CellConfigType protocol.
+
/// The simplest possible version of a cell that displays a label.
+/// Useful to get started, but in most cases a more robust state should be used allowing more customization.
+typealiasLabelCell=HostCell<UILabel,String,LayoutMarginsTableItemLayout>
+
+letcells:[CellConfigType]=[
+ LabelCell(key:"company",state:"Shopify"){view,statein
+ view.text=state
+ },
+ LabelCell(key:"location",state:"🇨🇦"){view,statein
+ view.text=state
+ }
+]
+
+
+
The rows themselves also have a key which must be unique inside of that section. This key is used to determine when new rows are added to a section, if any were removed, or if any moved to a different location.
+Additionally, each CellConfig type implements an isEqual function to determine if two of them represent the same data being displayed. This allows for a single cell to perform a state change operation, that is, a toggle changing from its off to on state, a text value changing, etc.
+
+
After assigning the variable rows to our previously created section, all that is needed to display the data in the table view is this method.
+
functionalTableData.renderAndDiff([section])
+
+
+
+
+
Check out the demo app for a fully interactive example.
+
Building new Cells
+
+
Knowing that a cell consists of a view and state let’s start with a simple example, a cell that displays a label. By specifying the generic requirements of HostCell, the simplest possible example is one that takes an UILabel as its view, a String as its state and LayoutMarginsTableItemLayout as the layout (See TableItemLayout for more info).
Although, the previous code is very useful to get started, in most cases a more robust state should be used allowing more customization. A better example would be something like this
+
typealiasLabelCell=HostCell<UILabel,LabelState,LayoutMarginsTableItemLayout>
+
+structLabelState:Equatable{
+ lettext:String
+ letalignment:NSTextAlignment
+ letcolor:UIColor
+
+ staticfunc==(lhs:LabelState,rhs:LabelState)->Bool{
+ returnlhs.text==rhs.text&&lhs.alignment==rhs.alignment&&lhs.color==rhs.color
+ }
+}
+
+// Usage
+LabelCell(key:"company",state:LabelState(text:"Shopify",
+ alignment:.center,
+ color:.green)){view,statein
+ guardletstate=stateelse{
+ // If the state is `nil`, prepare this view to be reused
+ view.text=""
+ view.textAlignment=.natural
+ view.textColor=.black
+ return
+ }
+ view.text=state.text
+ view.textAlignment=state.alignment
+ view.textColor=state.color
+}
+
+
+
At the end of the day HostCell is just one of the possible implementations of CellConfigType, that’s the underlying power of this framework.
A function that updates a cell’s view to match the current state. It receives two values, the view instance and an optional state instance. The purpose of this function is to update the view to reflect that of the given state. The reason that the state is optional is because cells may move into the reuse queue. When this happens they no longer have a state and the updater function is called giving the opportunity to reset the view to its default value.
The actions property exposed on the CellConfigType represents possible events that will be executed based on the users interaction with that particular cell. Of note are the selectionAction and previewingViewControllerAction. The selectionAction is executed when the user taps on that particular cell. The main use case for this is present a new detail view controller or a modal (but is not constrained to these actions, these are just the common use cases). The previewingViewControllerAction is responsible for returning an instance of a UIViewController that will be shown when a user 3D-touches on a cell.
Defines the view, state and layout information of a row item inside a TableSection."},"Protocols/FunctionalTableDataExceptionHandler.html#/s:19FunctionalTableData0abC16ExceptionHandlerP6handleyA2AC0D0V9exception_tF":{"name":"handle(exception:)","abstract":"
Handles the exception. This is only for debugging purposes, and commonly used","parent_name":"FunctionalTableDataExceptionHandler"},"Protocols/TableSectionType.html#/s:19FunctionalTableData0B11SectionTypeP3keySSvp":{"name":"key","abstract":"
A type that identifies a dequeueable object. Used by FunctionalTableData to increase performance by reusing objects when it needs to, just like UITableView and UICollectionView.
Initiates a layout pass of UITableView and its items. Necessary for calculating new","parent_name":"UITableView"},"Extensions/UITableView.html#/s:So11UITableViewC19FunctionalTableDataE23deselectLastSelectedRowySb8animated_tF":{"name":"deselectLastSelectedRow(animated:)","abstract":"
Deselects the previously selected row, with an option to animate the deselection.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UITableViewCell, or if that cell is not a child of self
Initiates a layout pass of UICollectionView and its items. Necessary for calculating new","parent_name":"UICollectionView"},"Extensions/UICollectionView.html#/s:So16UICollectionViewC19FunctionalTableDataE24deselectLastSelectedItemySb8animated_tF":{"name":"deselectLastSelectedItem(animated:)","abstract":"
Deselects the previously selected row, with an option to animate the deselection.
Find the IndexPath for a particular view. Returns nil if the view is not an instance of, or a subview of UICollectionViewCell, or if that cell is not a child of self