{"id":13872099,"url":"https://github.com/square/Listable","last_synced_at":"2025-07-16T01:33:10.729Z","repository":{"id":35963065,"uuid":"198273470","full_name":"square/Listable","owner":"square","description":"Declarative list views for iOS apps.","archived":false,"fork":false,"pushed_at":"2024-10-19T11:11:25.000Z","size":68509,"stargazers_count":200,"open_issues_count":19,"forks_count":28,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-29T08:43:21.278Z","etag":null,"topics":["collectionview","collectionviewlayout","declarative","declarative-ui","ios"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/square.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-22T17:41:21.000Z","updated_at":"2024-10-02T16:46:22.000Z","dependencies_parsed_at":"2024-01-11T23:15:52.956Z","dependency_job_id":"67b0bfd3-d334-44f6-8625-41b09e9709ed","html_url":"https://github.com/square/Listable","commit_stats":null,"previous_names":["kyleve/listable"],"tags_count":91,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2FListable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2FListable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2FListable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2FListable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/square","download_url":"https://codeload.github.com/square/Listable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226090030,"owners_count":17572114,"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":["collectionview","collectionviewlayout","declarative","declarative-ui","ios"],"created_at":"2024-08-05T23:00:33.954Z","updated_at":"2024-11-23T19:31:40.244Z","avatar_url":"https://github.com/square.png","language":"Swift","readme":"**Note** – Listable is still experimental :see_no_evil:. While it is shipping in [Square Point of Sale](https://squareup.com/us/en/point-of-sale) in several places, we're still actively iterating on the API, and backfilling comprehensive tests. As such, expect things to break and change in the coming months.\n\n# Listable\n\nListable is a declarative list framework for iOS, which allows you to concisely create rich, live updating list based layouts which are highly customizable across many axes: padding, spacing, number of columns, alignment, etc. It's designed to be performant: Handling lists of 10k+ items without issue on most devices.\n\n```swift\nself.listView.setContent { list in\n    list += Section(\"section-1\") { section in\n        \n        section.header = DemoHeader(title: \"This Is A Header\")\n        \n        section += DemoItem(text: \"And here is a row\")\n        section += DemoItem(text: \"And here is another row.\")\n        \n        let rows = [\n            \"You can also map rows\",\n            \"Like this\"\n        ]\n        \n        section += rows.map {\n            DemoItem(text: $0)\n        }\n    }\n\n    list += Section(\"section-2\") { section in\n        \n        section.header = DemoHeader(title: \"Another Header\")\n        \n        section += DemoItem(text: \"The last row.\")\n    }    \n}\n```\n\n## Features\n\n### Declarative Interface \u0026 Intelligent Updates\n\nThe core power and benefit of Listable comes from its declarative-style API, which allows you to implement SwiftUI or React style one-way data flow within your app's list views, eliminating many common state management bugs you encounter with standard UITableView or UICollectionView delegate-based solutions. You only need to tell the list what should be in it right now – it does the hard parts of diffing the changes to perform rich animated updates when new content is provided.\n\nLet's say you start with an empty table, like so:\n\n```swift\nself.listView.setContent { list in\n    // No content right now.\n}\n```\n\nAnd then push in new content, so there  is one row with one section:\n\n```swift\nself.listView.setContent { list in\n    list += Section(\"section-1\") { section in\n        section.header = DemoHeader(title: \"This Is A Header\")\n        \n        section += DemoItem(text: \"And here is a row\")\n    } \n}\n```\n\nThis new section will be animated into place. If you then insert another row:\n\n```swift\nself.listView.setContent { list in\n    list += Section(\"section-1\") { section in\n        section.header = DemoHeader(title: \"This Is A Header\")\n        \n        section += DemoItem(text: \"And here is a row\")\n        section += DemoItem(text: \"Another row!\")\n    } \n}\n```\n\nIt will also be animated into place by the list.  The same goes for any change you make to the table – a diff will be performed, and the changes will be animated into place. Content that did not change between updates will be unaffected.\n\n\n### Performant\n\nA core design principle of Listable is performance! Lists are _usually_ small, but not always! For example, within Square Point of Sale, a seller may have an item catalog of 1,000, 10,000, or even more items. When designing Listable, it was important to ensure that it could support lists of these scales with minimum performance cost, to make it easy to build them without paying for performance, or without having to drop back down to standard `UITableView` or `UICollectionView` APIs, which are easy to misuse.\n\nThis performance is achieved through an internal batching system, which only queries and diffs the items needed to display the current scroll point, plus some scrollover. Views are only created for what is currently on screen. This allows culling of most content pushed into the list for long lists.\n\nFurther, height and sizing measurements are cached more efficiently than in a regular collection view implementation, which for large lists, can boost scrolling performance and prevent dropped frames.\n\n\n### Highly Customizable\n\nListable makes very few assumptions of the appearance of your content. The currency you deal with is plain `UIViews` (not `UICollectionViewCells`), so you can draw content however you wish.\n\nFurther, the layout and appearance controls vended by `ListView` allow for customization of the layout to draw lists in nearly any way desired.\n\nThis is primarily controlled through the `Appearance` object:\n\n```swift\npublic struct Appearance : Equatable\n{\n    public var backgroundColor : UIColor\n    \n    public var direction : LayoutDirection\n    \n    public var stickySectionHeaders : Bool\n    \n    public var list : TableAppearance\n}\n``` \n\nYou use the `TableAppearance.Sizing` struct to control the default measurements within the list: How tall are standard rows, headers, footers, etc.\n\n```swift\npublic struct TableAppearance.Sizing : Equatable\n{\n    public var itemHeight : CGFloat\n    \n    public var sectionHeaderHeight : CGFloat\n    public var sectionFooterHeight : CGFloat\n    \n    public var listHeaderHeight : CGFloat\n    public var listFooterHeight : CGFloat\n    \n    public var itemPositionGroupingHeight : CGFloat\n}\n```\n\nYou can use `TableAppearance.Layout` to customize the padding of the entire list, how wide the list should be (eg, up to 700px, more than 400px, etc) plus control spacing between items, headers, and footers. \n\n```swift\npublic struct TableAppearance.Layout : Equatable\n{\n    public var padding : UIEdgeInsets\n    public var width : WidthConstraint\n\n    public var interSectionSpacingWithNoFooter : CGFloat\n    public var interSectionSpacingWithFooter : CGFloat\n    \n    public var sectionHeaderBottomSpacing : CGFloat\n    public var itemSpacing : CGFloat\n    public var itemToSectionFooterSpacing : CGFloat\n    \n    public var stickySectionHeaders : Bool\n}\n```\n\nFinally, the `Behavior` and  `Behavior.Underflow` allows customizing what happens when a list's content is shorter than its container view: Should the scroll view bounce, should the content be centered, etc.\n\n```swift\npublic struct Behavior : Equatable\n{\n    public var keyboardDismissMode : UIScrollView.KeyboardDismissMode\n    \n    public var underflow : Underflow\n```\n\n```swift\n\nstruct Underflow : Equatable\n{\n    public var alwaysBounce : Bool\n    public var alignment : Alignment\n    \n    public enum Alignment : Equatable\n    {\n        case top\n        case center\n        case bottom\n    }\n}\n```\n\n### Self-Sizing Cells\n\nAnother common pain-point for standard `UITableViews` or `UICollectionViews` is handling dynamic and self sizing cells. Listable handles this transparently for you, and provides many ways to size content. Each `Item` has a `sizing` property, which can be set to any of the following values. `.default` pulls the default sizing of the item from the `List.Measurement` mentioned above, where as the `thatFits` and `autolayout` values size the item based on `sizeThatFits` and `systemLayoutSizeFitting`, respectively.\n\n```swift\npublic enum Sizing : Equatable\n{\n    case `default`\n    \n    case fixed(CGFloat)\n    \n    case thatFits(Constraint = .noConstraint)\n    \n    case autolayout(Constraint = .noConstraint)\n}\n```\n\n### Integrates With Blueprint\n\nListable integrates closely with [Blueprint](https://github.com/square/blueprint/), Square's framework for declarative UI construction and management (if you've used SwiftUI, Blueprint is similar). Listable provides wrapper types and default types to make using Listable lists within Blueprint elements simple, and to make it easy to build Listable items out of Blueprint elements.\n\nAll you need to do is take a dependency on the `BlueprintUILists` pod, and then `import BlueprintUILists` to begin using Blueprint integration.\n\nIn this example, we see how to declare a `List` within a Blueprint element hierarchy.\n\n```swift\nvar elementRepresentation : Element {\n    List { list in\n        list += Section(\"podcasts\") { section in\n            \n            section += self.podcasts.map {\n                PodcastRow(podcast: $0)\n            }\n        }\n    }\n}\n```\n\nAnd in this example, we see how to create a simple `BlueprintItemContent` that uses Blueprint to render its content.\n\n```swift\nstruct DemoItem : BlueprintItemContent, Equatable\n{\n    var text : String\n    \n    // ItemContent\n    \n    var identifierValue: String {\n        return self.text\n    }\n    \n    // BlueprintItemContent\n    \n    func element(with info : ApplyItemContentInfo) -\u003e Element\n    {\n        var box = Box(\n            backgroundColor: .white,\n            cornerStyle: .rounded(radius: 6.0),\n            wrapping: Inset(\n                uniformInset: 10.0,\n                wrapping: Label(text: self.text)\n            )\n        )\n        \n        box.borderStyle = .solid(color: .white(0.9), width: 2.0)\n        \n        return box\n    }\n}\n```\n\n## Instruments.app Integration\n\nListable provides integration with the `os_signpost` API for measuring the duration of events in your application. If you are experiencing issues with list performance in your app, you can profile it in Instruments, and add the `os_signpost` instrument to inspect the timing for various layout and update passes.\n\n## Primary API \u0026 Surface Area\n\nMost of your interaction will be three primary families of types: `ListView`, `Item`,  `HeaderFooter`, and `Section`. \n\n### ListView\nThe list that you put content into! Beyond allocating a list and putting it on screen, the bulk of your interaction with `ListView` will be through the `setContent` API shown above.\n\n```swift\nself.listView.setContent { list in\n    // Set list appearance, specify content, etc...\n}\n```\n\nWhat is that `list` parameter, you ask...?\n\n### ListProperties\n`ListProperties` is a struct which contains all the information required to render a list update.\n\n```swift\npublic struct ListProperties\n{\n    public var animatesChanges : Bool\n\n    public var layoutType : ListLayoutType\n    public var appearance : Appearance\n    \n    public var behavior : Behavior\n    public var autoScrollAction : AutoScrollAction\n    public var scrollInsets : ScrollInsets\n    \n    public var accessibilityIdentifier: String?\n    \n    public var content : Content\n}\n```\n\nThis allows you to configure the list view however needed within the `configure` update.\n\n\n### Item\nYou can think of `Item` as the wrapper for the content _you_ provide to the list – similar to how a `UITableViewCell` or `UICollectionViewCell` wraps a content view and provides other configuration options. \n\nAn `Item` is what you add to a section to represent a row in a list. It contains your provided content (`ItemContent`), alongside things like sizing, layout customization, selection behavior, reordering behavior, and callbacks which are performed when an item is selected, displayed, etc.\n\n```swift\npublic struct Item\u003cContent:ItemContent\u003e : AnyItem\n{\n    public var identifier : Content.Identifier\n    \n    public var content : Content\n    \n    public var sizing : Sizing\n    public var layout : ItemLayout\n    \n    public var selection : ItemSelection\n    \n    public var swipeActions : SwipeActions?\n    \n    public var reordering : ItemReordering?\n        \n    public typealias OnSelect = (Content) -\u003e ()\n    public var onSelect : OnSelect?\n    \n    public typealias OnDeselect = (Content) -\u003e ()\n    public var onDeselect : OnDeselect?\n    \n    public typealias OnDisplay = (Content) -\u003e ()\n    public var onDisplay : OnDisplay?\n    \n    public typealias OnEndDisplay = (Content) -\u003e ()\n    public var onEndDisplay : OnEndDisplay?\n}\n```\nYou can add an item to a section via either the `add` function, or via the `+=` override. \n\n```swift\nsection += Item(\n    YourContent(title: \"Hello, World!\"),\n    \n    sizing: .default,\n    selection: .notSelectable\n)\n```\n\nHowever, if you want to use all default values from the `Item` initializer, you can skip a step and simply add your `ItemContent` to the section directly.\n\n```swift\nsection += YourContent(title: \"Hello, World!\")\n```\n\n\n### ItemContent\nThe core value type which represents an item's content.\n\nThis view model describes the content of a given row / item, via the `identifier`, plus the `wasMoved` and `isEquivalent` methods.\n\nTo convert an `ItemContent` into views for display, the  `createReusableContentView(:)` method is called to create a reusable view to use when displaying the content (the same happens for background views as well).\n\nTo prepare the views for display, the `apply(to:for:with:)` method is called, which is where you push the content from your `ItemContent` onto the provided views.\n\n```swift\npublic protocol ItemContent\n{\n    associatedtype IdentifierValue : Hashable\n\n    var identifierValue : IdentifierValue { get }\n\n    func apply(\n        to views : ItemContentViews\u003cSelf\u003e,\n        for reason: ApplyReason,\n        with info : ApplyItemContentInfo\n    )\n\n    func wasMoved(comparedTo other : Self) -\u003e Bool\n    func isEquivalent(to other : Self) -\u003e Bool\n\n    associatedtype ContentView:UIView\n    static func createReusableContentView(frame : CGRect) -\u003e ContentView\n\n    associatedtype BackgroundView:UIView = UIView\n    static func createReusableBackgroundView(frame : CGRect) -\u003e BackgroundView\n\n    associatedtype SelectedBackgroundView:UIView = BackgroundView\n    static func createReusableSelectedBackgroundView(frame : CGRect) -\u003e SelectedBackgroundView\n}\n```\n\nNote however, you usually do not need to implement all these methods! For example, if your `ItemContent` is `Equatable`, you get `isEquivalent` for free – and by default, `wasMoved` is the same was `isEquivalent(other:) == false`.\n\n```swift\npublic extension ItemContent\n{\n    func wasMoved(comparedTo other : Self) -\u003e Bool\n    {\n        return self.isEquivalent(to: other) == false\n    }\n}\n\n\npublic extension ItemContent where Self:Equatable\n{\n    func isEquivalent(to other : Self) -\u003e Bool\n    {\n        return self == other\n    }\n}\n```\n\nThe `BackgroundView` and `SelectedBackgroundView` views also default to a plain `UIView` which do not display any content of their own. You only need to provide these background views if you wish to support customization of the appearance of the item during highlighting and selection.\n\n```swift\npublic extension ItemContent where BackgroundView == UIView\n{\n    static func createReusableBackgroundView(frame : CGRect) -\u003e BackgroundView\n    {\n        BackgroundView(frame: frame)\n    }\n}\n```\n\nThe `SelectedBackgroundView` also defaults to the type of `BackgroundView` unless you explicitly want two different view types.\n\n```swift\npublic extension ItemContent where BackgroundView == SelectedBackgroundView\n{\n    static func createReusableSelectedBackgroundView(frame : CGRect) -\u003e BackgroundView\n    {\n        self.createReusableBackgroundView(frame: frame)\n    }\n}\n```\n\n\nThis is all a bit abstract, so consider the following example: An `ItemContent` which provides a title and detail label.\n\n```swift\nstruct SubtitleItem : ItemContent, Equatable\n{\n    var title : String\n    var detail : String\n    \n    // ItemContent\n\n    func apply(to views : ItemContentViews\u003cSelf\u003e, for reason: ApplyReason, with info : ApplyItemContentInfo)\n    {\n        views.content.titleLabel.text = self.title\n        views.content.detailLabel.text = self.detail        \n    }\n    \n    typealias ContentView = View\n    \n    static func createReusableContentView(frame : CGRect) -\u003e ContentView\n    {\n        View(frame: frame)\n    }\n    \n    private final class View : UIView\n    {\n        let titleLabel : UILabel\n        let detailLabel : UILabel\n        \n        ...\n    }\n}\n```\n\n### HeaderFooter\nHow to describe a header or footer within a list. Very similar API to `Item`, but with less stuff, as headers and footers are display-only.\n\n```swift\npublic struct HeaderFooter\u003cContent:HeaderFooterContent\u003e : AnyHeaderFooter\n{\n    public var content : Content\n    \n    public var sizing : Sizing\n    public var layout : HeaderFooterLayout\n}\n```\n\nYou set headers and footers on sections via the `header` and `footer` parameter.\n\n```swift\nself.listView.configure { list in\n    list += Section(\"section-1\") { section in\n        section.header = DemoHeader(title: \"This Is A Header\")\n        section.footer = DemoFooter(text: \"And this is a footer. Please check the EULA for details.\")\n    } \n}\n```\n\n#### HeaderFooterContent\nAgain, a similar API to  `ItemContent`, but with a reduced surface area, given the reduced concerns of header and footers.\n\n```swift\npublic protocol HeaderFooterContent\n{\n    func apply(to view : Appearance.ContentView, reason : ApplyReason)\n\n    func isEquivalent(to other : Self) -\u003e Bool\n    \n    associatedtype ContentView:UIView\n    static func createReusableContentView(frame : CGRect) -\u003e ContentView\n    \n    associatedtype BackgroundView:UIView\n    static func createReusableBackgroundView(frame : CGRect) -\u003e BackgroundView\n    \n    associatedtype PressedBackgroundView:UIView\n    static func createReusablePressedBackgroundView(frame : CGRect) -\u003e PressedBackgroundView\n}\n```\n\nAs is with `Item`, if your `HeaderFooterContent` is `Equatable`, you get `isEquivalent` for free.\n\n```swift\npublic extension HeaderFooterContent where Self:Equatable\n{    \n    func isEquivalent(to other : Self) -\u003e Bool\n    {\n        return self == other\n    }\n}\n```\n\nA standard implementation may look like this:\n\n```swift\nstruct Header : HeaderFooterContent, Equatable\n{\n    var title : String\n\n    func apply(to view : Appearance.ContentView, for reason: ApplyReason)\n    {\n        view.titleLabel.text = self.title       \n    }\n    \n    typealias ContentView = View\n    \n    static func createReusableContentView(frame : CGRect) -\u003e ContentView\n    {\n        View(frame: frame)\n    }\n    \n    private final class View : UIView\n    {\n        let titleLabel : UILabel\n        \n        ...\n    }\n}\n```\n\n### Section\n`Section` – surprise – represents a given section in a list. Most of your interaction with `Section` will be through the init \u0026 builder API, as shown above.\n\n```swift\nSection(\"section\") { section in\n    section += self.podcasts.map {\n        PodcastRow(podcast: $0)\n    }\n}\n```\n\nHowever, section has many properties to allow for configuration. You can customize the layout, the number of and layout of columns, set the header and footer, and obviously provide items, via the `items` property, and via the many provided overrides of the `+=` operator.\n\n```swift\npublic struct Section\n{    \n    public var layout : Layout\n    public var columns : Columns\n    \n    public var header : AnyHeaderFooter?\n    public var footer : AnyHeaderFooter?\n    \n    public var items : [AnyItem]\n}\n```\n\n## Integration With Blueprint\n\nIf you're using Blueprint integration via the  `BlueprintUILists` module, you will also interact with the following types.\n\n### List\nWhen using `ListView` directly, you'd use `list.configure { list in ... }` to set the content of a list.\n\nHowever, Blueprint element trees are just descriptions of UI – as such, `List` is just a Blueprint `Element` which describes a list. The parameter passed to `List { list in ... }` is the same type (`ListProperties`) that is passed to  `list.configure { list in ... }`.\n\n```swift\nvar elementRepresentation : Element {\n    List { list in\n        list += Section(\"section\") { section in\n            \n            section += self.podcasts.map {\n                PodcastRow(podcast: $0)\n            }\n        }\n    }\n}\n````\n\n### BlueprintItemContent\n`BlueprintItemContent` simplifies the `ItemContent` creation process, asking you for a Blueprint `Element` description, instead of view types and view instances. \n\nUnless you are supporting highlighting and selection of your `ItemContent`, you do not need to provide implementations of `backgroundElement(:)` and `selectedBackgroundElement(:)` – they default to returning nil. Similar to `ItemContent`, `wasMoved(:)` and `isEquivalent(:)` are also provided based on `Equatable` conformance.\n\n```swift\npublic protocol BlueprintItemContent : ItemContent\n{\n    associatedtype IdentifierValue : Hashable\n\n    var identifierValue : IdentifierValue { get }\n\n    func wasMoved(comparedTo other : Self) -\u003e Bool\n    func isEquivalent(to other : Self) -\u003e Bool\n\n    func element(with info : ApplyItemContentInfo) -\u003e Element\n    \n    func backgroundElement(with info : ApplyItemContentInfo) -\u003e Element?\n\n    func selectedBackgroundElement(with info : ApplyItemContentInfo) -\u003e Element?\n}\n```\n\nA standard `BlueprintItemContent` may look something like this:\n\n```swift\n\nstruct MyPerson : BlueprintItemContent, Equatable\n{\n    var name : String\n    var phoneNumber : String\n\n    var identifierValue : String {\n        self.name\n    }\n    \n    func element(with info : ApplyItemContentInfo) -\u003e Element {\n        Row {\n            $0.add(child: Label(text: name))\n            $0.add(child: Spacer())\n            $0.add(child: Label(text: name))\n        }\n        .inset(by: 15.0)\n    }\n}\n```\n\n### BlueprintHeaderFooterContent\nSimilarly, `BlueprintHeaderFooterContent` makes creating a header or footer easy – just implement `elementRepresentation`, which provides the content element for your header or footer. As usual, `isEquivalent(to:)` is provided if your type is `Equatable`.\n\n```swift\npublic protocol BlueprintHeaderFooterContent : HeaderFooterElement\n{\n    func isEquivalent(to other : Self) -\u003e Bool\n\n    var elementRepresentation : Element { get }\n}\n```\n\nA standard `BlueprintHeaderFooterContent` may look something like this:\n\n```swift\n\nstruct MyHeader : BlueprintHeaderFooterContent, Equatable\n{\n    var name : String\n    var itemCount : String\n    \n    var elementRepresentation : Element {\n        Row {\n            $0.add(child: Label(text: name))\n            $0.add(child: Spacer())\n            $0.add(child: Label(text: itemCount))\n        }\n        .inset(by: 15.0)\n    }\n}\n```\n\n## Getting Started\n\nListable is published on CocoaPods. You can add a dependency on Listable or it's Blueprint wrapper with the following in your Podspec: \n\n```\ns.dependency 'ListableUI'\ns.dependency 'BlueprintUILists'\n```\n\nIf you want to depend on bleeding-edge changes, you can add the pods to your Podfile via the git repo like so:\n\n```\n  pod 'BlueprintUILists', git: 'ssh://git@github.com:kyleve/Listable.git'\n  pod 'ListableUI', git: 'ssh://git@github.com:kyleve/Listable.git'\n```\n\n## Demo Project\nIf you'd like to see examples of Listable in use, clone the repo, and then run `bundle exec pod install` in the root of the repo. This will create the `Demo/Demo.xcworkspace` workspace, which you can open and run. It contains examples of various types of screens and use cases.\n\n\n# Other Neat Stuff\n\n### You can nest Lists in other lists.\nYou can nest horizontal scrolling Lists within vertical scrolling lists to create advanced, custom layouts. Listable provides a `ListItemElement` to make this easy.\n\n### You can override many layout parameters on a per-item and per-header/footer basis.\nBy setting the `layout` parameter on `Item` or `HeaderFooter`, you can specify the alignment of each item within a layout, how much padding it should have, how much spacing it can have, etc.\n\n\n# Appendix\n\n## Implementation Details\n\n### Rendering \u0026 Display\nListable is built on top of `UICollectionView`, with a custom `UICollectionViewLayout` though this is not exposed to consumers.\n\n### Performance\nInternally, performance is achieved through transparent batching of content that is loaded into the collection view itself. This allows pushing large amounts\nof content into the list, but `ListView` is intelligent enough to only load, measure, diff, etc, enough of that content to display the current scroll position,\nplus some scroll overflow. In practice, this means that even if you put 50,000 items into a list, if the user is scrolled at the top of the table, only a few hundred\nitems will be measured, diffed, and take up computation time during initial rendering and updates. This allows performance to remain nearly constant, regardless\nof what content is pushed into the list. The farther down a user scrolls, the more computation must be completed.\n\n### View State Management\nInternally, every item drawn on screen and visible in the list is represented by a long-lived `PresentationState` instance, which tracks visible cells, sizing measurements, etc.\nThis long lived object allows an extra layer which means it's easy to cache height calculations across multiple content updates in the list view, allowing for further performance\nimprovements and optimizations. This is transparent to the developer.\n\n\n## Why?\n\nBuilding rich and interactive list views and lists on iOS remains a challenge. Maintaining state and performing animations on changes is tricky and error prone. More often than not, there are lurking state bugs that result in inconsistent data or crashes that are hard to diagnose and debug.\n\nHistorically, we have managed list view state one of a few ways...\n\n1) Via Core Data and NSFetchedResultsController, which handles diffing and updates. However, this binds your UI tightly to the underlying core data model, which makes changes difficult and error prone. You end up needing to model UI concerns deep in your Core Data model to sort and section your data as you want. No good.\n\n2) Use other options such as common block-based table view or collection view builders – which abstracts some of the complexity away from developers, but it still deals in the currency of cells and views – and makes it difficult to properly handle animations and updates.\n\n3) Sometimes, you end up just giving up and calling reloadData() any time anything in your table’s data source changes – this sucks because users don’t see animations which indicate to them what changed.\n\n4) Or, even worse, you end up managing insertions, deletions, and updates yourself, which usually goes something like this…\n\n\n\u003e Call beginUpdates\n\u003e \n\u003e Call insertRow:atIndexPath:\n\u003e Call insertRow:atIndexPath:\n\u003e Call moveRowAtIndexPath:toIndexPath:\n\u003e Etc..\n\u003e \n\u003e Call endUpdates\n\u003e \n\u003e Assertion failure in UITableView/UICollectionView.m:20000000: The number of rows before the update is not equal to \u003e the number of rows after the update, plus or minus the added and removed rows. You suck, nerd!\n\u003e \n\u003e [Crash]\n\nNeedless to say, none of these options are great, and all of these are state-of-the-art circa about 2011 – which was a long time ago.\n\n\n## Legal Stuff\n\nCopyright 2019 Square, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License\n","funding_links":[],"categories":["Swift"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsquare%2FListable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsquare%2FListable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsquare%2FListable/lists"}