Giter Site home page Giter Site logo

plangrid / reactivelists Goto Github PK

View Code? Open in Web Editor NEW
251.0 101.0 15.0 36.18 MB

React-like API for UITableView & UICollectionView

Home Page: https://plangrid.github.io/ReactiveLists

License: MIT License

Ruby 1.70% Swift 97.75% Objective-C 0.25% Shell 0.30%
ios swift react reactive-programming collectionview tableview uikit public declarative-ui

reactivelists's Introduction

React-like API for UITableView & UICollectionView

Build Status Version Status license MIT codecov Platform

ReactiveLists provides a React-like API for UITableView and UICollectionView that makes it easy to write stateless code that generates user interfaces.

In our experience this can make UI code significantly easier to read and maintain. Instead of spreading the definition of your content over various data source methods, you can define the content concisely. The table or collection content and layout are immediately obvious by scanning over the source code.

You can read more about the origins of this library in our announcement blog post.

Features

  • React-like declarative API for UITableView and UICollectionView
  • Automatic UI updates, whenever your models change

Example

// Given a view controller with a table view

// 1. create cell models
let cell0 = ExampleTableCellModel(...)
let cell1 = ExampleTableCellModel(...)
let cell2 = ExampleTableCellModel(...)

// 2. create section models
let section0 = ExampleTableSectionViewModel(cellViewModels: [cell0, cell1, cell2])

// 3. create table model
let tableModel = TableViewModel(sectionModels: [section0])

// 4. create driver
self.driver = TableViewDriver(tableView: self.tableView, tableViewModel: tableModel)

// 5. update driver with new table model as it changes
let updatedTableModel = self.doSomethingToChangeModels()
self.driver.tableViewModel = updatedTableModel

// self.tableView will update automatically

Project Status

An early version of the UITableView support has been shipping in the PlanGrid app since late 2015 and is now used accross wide parts of the app. The support for UICollectionView is less mature as we only use UICollectionView in very few places.

Feature Status
UITableView support
UICollectionView support ⚠️ Experimental

Vision

For long-term goals and direction, please see VISION.md.

Getting Started

Read our Getting Started Guide to learn how to use ReactiveLists.

Documentation

Read our documentation here. Generated with jazzy. Hosted by GitHub Pages.

Generating docs

$ ./scripts/gen_docs.sh

Requirements

  • Xcode 10.2+
  • Swift 4.2 or 5.0
  • iOS 11+

Installation

CocoaPods (recommended)

use_frameworks!

# For latest release in CocoaPods
pod 'ReactiveLists'

# Latest on master branch
pod 'ReactiveLists', :git => 'https://github.com/plangrid/ReactiveLists.git', :branch => 'master'

Select Xcode menu File > Swift Packages > Add Package Dependency... and enter repository URL with GUI.

Repository: https://github.com/plangrid/ReactiveLists

Contribute

Please read and follow our Contributing Guide and our Code of Conduct.

License

ReactiveLists is released under an MIT License. See LICENSE for details.

Copyright © 2018-present PlanGrid, Inc.

reactivelists's People

Contributors

anayini avatar ben-g avatar benasher44 avatar daniel-juarez avatar dependabot[bot] avatar dercspringer avatar jeffrey-compton avatar jessesquires avatar marcelofabri avatar marco-evc avatar nickell-andrew avatar njw438 avatar ronaldsmartin avatar shshalom avatar wickwirew avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactivelists's Issues

Empty state for sections should consider headers/footers

It's possible to have a zero-cell section with only headers and footers. (For both tables and collections)

We should update our definitions of "emptiness" for both models.

Follow-up to #48

Empty means:

  • Zero cells (empty array of *CellViewModels)
  • nil header view
  • nil footer view

Changes needed:

  • Update isEmpty on section models
  • Handle empty sections accordingly. e.g., return 0 in data source methods when appropriate (mostly applies to single-section models)

Improve collection supplementary view support

CollectionViewModel.SectionModel is unnecessarily limited to:

let cellViewModels: [CollectionViewCellViewModel]?
let headerViewModel: CollectionViewSupplementaryViewModel?
let footerViewModel: CollectionViewSupplementaryViewModel?

CollectionViews can have arbitrary supplementary views — they are not limited to headers and footers.

(Note: FlowLayout is limited to only headers / footers)

We should update the model to reflect this.

Support collection view dynamic cell sizing

CollectionViewModel / CollectionViewDataSource does not support dynamic item sizes via FlowLayoutDelegate.

Note: this is not about dynamically sizing cells, just providing a unique size per cell via the delegate

Fix table empty state + add isEmpty to collection section model

EDIT: isEmpty should check all sections

Empty tables have 1 section model with no cell models and nil header/footer models. Section models should be empty in this case.

  ▿ 0 : SectionModel
    ▿ cellViewModels : Optional<Array<FluxTableViewCellViewModel>>
      - some : 0 elements
    ▿ headerViewModel : Optional<FluxTableViewSectionHeaderFooterViewModel>
      ▿ some : (PlainHeaderFooterViewModel in _BEA0F9974F0956F35FD050A4BA315CE5)
        - title : nil
        - height : nil
        - viewInfo : nil
    ▿ footerViewModel : Optional<FluxTableViewSectionHeaderFooterViewModel>
      ▿ some : (PlainHeaderFooterViewModel in _BEA0F9974F0956F35FD050A4BA315CE5)
        - title : nil
        ▿ height : Optional<CGFloat>
          - some : 0.0
        - viewInfo : nil
    - collapsed : false
    - diffingKey : nil

Unify Table* and Collection* components

We should be able to unify our table- and collection-specific components into single, generic components using a similar approach to https://github.com/jessesquires/JSQDataSourcesKit.

The model in JSQDataSourcesKit and here are a bit different, but we should be able to do this. The main difference is that JSQDataSourcesKit does not consume the delegate properties — only the dataSource.

Todo (roughly):

  • Unify CollectionViewCellViewModel and TableViewCellViewModel --> CellViewModel<T>
  • Unify CollectionViewModel and TableViewModel --> CellParentViewModel<T>
  • Unify the nested SectionModels
  • Unify CollectionViewDataSource and TableViewDataSource --> DataSource<T>

Then, we can implement the UITableViewDataSource and UICollectionViewDataSource methods in constrained extensions on DataSource<T> similar to this:
https://github.com/jessesquires/JSQDataSourcesKit/blob/develop/Source/DataSourceProvider.swift#L64

Fix model for table view headers/footers

We have:

public protocol ReusableSupplementaryViewProtocol {
    var viewInfo: SupplementaryViewInfo? { get }
}

This should be not optional.

This works fine for collections (CollectionViewSupplementaryViewModel) ✅

This will break tables (TableViewSectionHeaderFooterViewModel), which also have a var title: String?. Tables can provide strings to get the default table view headers, or register views. Right now we model this as two optionals:

public protocol TableViewSectionHeaderFooterViewModel: ReusableSupplementaryViewProtocol {
    var title: String? { get }

    // comes from ReusableSupplementaryViewProtocol
    // var viewInfo: SupplementaryViewInfo? { get }

This should probably be an enum instead (essentially: Either<String, SupplementaryViewInfo>).

However, that introduces problems for ReusableSupplementaryViewProtocol, which must be adopted by both tables and collections.

Unless: we provide default "label-only" collection view headers in the library, such that you can just provide a String title for collections too. I'm leaning toward this. If the header just has a UILabel, clients can configure it however they want (fonts, colors, etc.) This is the approach I used in JSQDataSourcesKit: https://github.com/jessesquires/JSQDataSourcesKit/blob/develop/Source/TitledSupplementaryView.swift

TravisCI failures

Travis appears to be having problems building master:

❌  /Users/travis/build/plangrid/ReactiveLists/Sources/TableViewDriver.swift:113:47: value of type '[IndexPath]' has no member 'compactMap'
        let indexPathsAndViewModelsToReload = visibleIndexPaths.compactMap { indexPath in
                                              ^~~~~~~~~~~~~~~~~ ~~~~~~~~~~

Fixes/Improvements to TableView row height

Behavior here is currently confusing and the API is hard to use.

TableViewCellViewModel has rowHeight + a default protocol implementation of 44.

TableViewDataSource queries this rowHeight per model, or returns a magic 44:

    open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return self._tableViewModel?[indexPath]?.rowHeight ?? 44
    }

It looks like this is open for some one-off cases in PlanGrid.


Proposed changes

  • Make TableViewCellViewModel.rowHeight optional (this is a per-cell height)
  • Add TableViewModel.defaultRowHeight(this is a default for all rows in the table)
  • In heightForRowAt indexPath:, use cell rowHeight, otherwise use TableViewModel.defaultRowHeight

Fix `SupplementaryViewKind`

Our model here is incorrect.

public enum SupplementaryViewKind is simply wrong.

kind should be a property on SupplementaryViewInfo

Like I mentioned in #55, supplementary views can be arbitrary. So can kind.

For example, you can use the same cell prototype and reuseIdentifier, but different kind specifiers. We currently can't support this.

Practical use case: a cell with a label could be used as a header and footer.

[Crash] Auto-diffing bugs and crashes

Diffing for collection views with multiple sections fails. And possibly other scenarios.

We don't use this in PlanGrid, so we aren't currently affected by this.

Seems to be a bug in Dwifft. Switching to IGListKit/Diffing is now a top priority.

See also: #125

Currently had to disable this test:
https://github.com/plangrid/ReactiveLists/blob/master/Tests/CollectionView/CollectionViewDriverDiffingTests.swift#L78

UPDATE:

Repro steps

For both table and collection views:

  1. Launch example app
  2. Delete all items from first section
  3. Tap "Flip" button to swap sections until you crash

Consider removing accessibility APIs

The accessibility APIs are more part of PlanGrid's codebase than of this particular library. They are a somewhat out of scope as they go beyond just providing a declarative API for existing UITableView/UICollectionView APIs.

While it's nice to force all cells and sections to have accessibility identifiers, we should probably remove this from the library.

CollectionView: remove cell, header, footer tracking/caching

Remove in favor of UIKit APIs (visibleCells, etc.)

var _cellsOnScreen: [IndexPath: UICollectionViewCell] = [:]
var _headersOnScreen: [IndexPath: UICollectionReusableView] = [:]
var _footersOnScreen: [IndexPath: UICollectionReusableView] = [:]

[Refactor] Consistent Naming

This code has evolved quite a bit since the first version used in the PlanGrid app. The names of many types still refer to Flux, even though the library is independent of that design pattern.

Prior to the release we should rename all types to be consistent.

Cell view models should be diffing by default

In 991c073 we made automatic diffing true by default.

However, it is still incumbent upon clients to explicitly conform to DiffableViewModel.

BecauseautomaticDiffingEnabled is now true for TableViewDriver and CollectionViewDriver, this will result in an assert for clients.

ReusableCellViewModelProtocol should inherit from DiffableViewModel, which provides a default diffingKey (the class name).

  • Clients can easily customize the diffingKey for their needs.
  • Clients can opt-out of diffing by passing false to TableViewDriver and CollectionViewDriver, in which case the diffingKey will be ignored anyway.

Remove TableSectionViewModel.collapsed

This is seems to be a vestigial aspect of the framework from early PlanGrid days.

We don't use this anywhere in PlanGrid.

To achieve this sort of "collapsing" behavior, you'd capture this state elsewhere in your model, and simply regenerate your SectionModel with zero CellViewModels.

This is how we currently do this in PlanGrid.

And, I think this makes more sense to generate full state changes, rather than "hiding" a section. (i.e., this is sort of a hack)

Further, this is complicates notions of "empty/nil state" (see #48 and IP-3275) and I want to remove that complexity.

Support all delegate methods in cell models?

For tables we provide:

    var willBeginEditing: WillBeginEditingClosure? { return nil }
    var didEndEditing: DidEndEditingClosure? { return nil }
    var editingStyle: UITableViewCellEditingStyle { return .none }
    var shouldHighlight: Bool { return true }
    var commitEditingStyle: CommitEditingStyleClosure? { return nil }
    var didSelectClosure: DidSelectClosure? { return nil }
    var accessoryButtonTappedClosure: AccessoryButtonTappedClosure? { return nil }
    var shouldIndentWhileEditing: Bool { return false }

For collections we provide:

    var shouldHighlight: Bool { return true }

    var didSelectClosure: DidSelectClosure? { return nil }

    var didDeselectClosure: DidDeselectClosure? { return nil }

Obviously, TableViewModel is further along, however, both should include willDisplay..., didEndDisplaying..., etc. hooks

[Refactor] Clean up Tests

The tests currently use a lot of parameterized tests which makes them difficult to read. We should refactor them and consider using BDD tests as throughout most of the PlanGrid codebase (though this means taking on Quick+Nimble as development and CI dependency).

Cell model closures should receive dataSource/delegate function params

Most of the closures for cells at () -> Void
https://github.com/plangrid/ReactiveLists/blob/master/Sources/Typealiases.swift#L19-L32

Instead, the should receive the params from the method in which they are called, for example:
typealias DidSelectClosure = (tableView, indexPath) -> Void

It's not uncommon to need access to these values to perform certain operations.


Also of note:

Why not just define functions on the *CellViewModel` protocols? Seems like a much simpler design. No need for the typealiases.

Add Diffing Support for UICollectionView

Currently automatic diffing is only implemented for UITableView. The UICollectionView driven by this library within the PlanGrid app, currently perform updates manually.

It would be great to implement the same diffing for UICollectionView as for UITableView.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.