Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UITableView+Rx rework. #23

Closed
danielt1263 opened this issue May 28, 2015 · 4 comments
Closed

UITableView+Rx rework. #23

danielt1263 opened this issue May 28, 2015 · 4 comments

Comments

@danielt1263
Copy link
Collaborator

I would like to see the UITableView extension reworked a bit. I envision having the class subscribe to an observable array and have the ability to add and remove individual cells when the array changes.

The code for finding the add/remove/moves is available here (http://www.swift-studies.com/blog/2015/5/15/swift-coding-challenge-incremental-updates-to-views).

And yes, I tried his challenge. I managed to make a function that was almost as fast, but is was essentially the same structure as his solution so I gave it up.

I'll try making this enhancement myself, but I'm not strong on this RX stuff yet so if someone else wants to take up the gauntlet...

@carlosypunto
Copy link
Contributor

I'm working over UITableView not for performance, in the beginning only adding support for remove and move cells. In my fork there is a branch https://github.com/carlosypunto/RxSwift/tree/remove-and-move-cells with some begun work but I want suggestions.

@kzaher
Copy link
Member

kzaher commented May 28, 2015

I'll work with Carlos in his repository on this so we don't spam everybody who is watching this repository with our collaboration work until it's ready to be merge into develop.

Initially table view bindings were made intentionally simple, but it now feels that it won't be sufficient, and that people want default more sophisticated functionality. I'll revisit my original idea, and provide implementation that is more suitable.

This is my rough plan:

The will be interface that wraps reloadData (entire view refresh)

subscribeRowsTo<RowInfo>(source: Observable<[RowInfo]>) <-- all rows in one section dummy section that will be created automatically (probably 90% of cases)
subscribeSectionsTo<SectionInfo, RowInfo>(source: Observable<([(section: SectionInfo, rows: [RowInfo])])>) <-- enables binding sections that have rows

This will be interface that wraps beginUpdates/endUpdates (animated updates)

  • subscribeSectionsToIncrementalUpdates<S, R>(event: IncrementalUpdateEvent<S, R>)
    (still searching for a better name, so suggestions are welcome)
enum IncrementalUpdateEvent<S, T> {
    Snapshot([(section: S, items: [T])]) <-- sends initial state
    ItemMoved(from: NSIndexPath, to: NSIndexPath)
    ItemInserted(item: T, to: NSIndexPath)
    ItemDeleted(from: NSIndexPath)
    ItemUpdated(newState: T, oldState: T, path: NSIndexPath)
    SectionMoved(from: Int, to: Int)
    SectionInserted(section: S to: Int)
    SectionDeleted(from: Int)
    UpdatesStarted,
    UpdatesEnded
}

There will be also corresponding wrapper for NSManagedObjectContext something like

rx_entities<T>(request: NSFetchRequest) -> Observable<[T]>
rx_entityChanges<T>(request: NSFetchRequest) -> Observable<IncrementalUpdateEvent<AnyObject<NSFetchedResultsSectionInfo>, T>

I don't think that RxCocoa will provide observable array class for now. I can maybe make a separate project and play with it, but don't see it being included in RxCocoa or RxSwift project for now.

The reason why I feel this way is because if you have incremental updates, then identity of the objects is important. If identity of objects is important, then using code data would probably be smart (at least with in memory context). Also, if I would decide to implement observable array in this project, it's just a matter of time when request for batched updates would popup (CoreData already has parent/child contexts). If you have batched updates, then you have different heuristics ....

So for now, I don't see a clear concept of observable array or any good practical use case, and until that is the case, don't think it makes sense to include that interface.

But that could change if convinced otherwise.

The best I could do about observable arrays is play with the idea in separate project where we can hack with it, and see if it leads somewhere.

Would appreciate feedback on this plan.

@danielt1263
Copy link
Collaborator Author

It's a hard nut to crack. Rx assumes push, but the two primary methods on UITableViewDataSource are pull methods...

I see no problem with making an RxTableView class derived from UITableView if that helps. We can easily add an RxTableView into a storyboard or nib file so the only drawback is that we couldn't use UITableViewController... Frankly I don't use that class anyway.

@kzaher
Copy link
Member

kzaher commented Jun 28, 2015

Hi guys,

I've made bunch of changes to RxCocoa. It's still not completely done since I need to apply those changes to UICollectionView also, other views with delegates, and add further optimizations, but it's looking pretty good to me considering where it was.

Changes are in a branch called feature/rxcocoa-revamp.

I'm planning to release these changes and some other next week, so if you don't agree with direction, think changes are bad, or have some suggestions, now is the time to give feedback.

This is the summary of changes for RxCocoa project

  • It will be possible to register observables for all views + use old delegate mechanisms. The only difference will be that you'll need to call
tableView.rx_setDelegate(self, retainDelegate: false)
            >- disposeBag.addDisposable

instead of just calling tableView.delegate = self.

So you can seamlessly do:

      ....
     func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 40
    }
     ....
// customization using delegate
        // RxTableViewDelegateBridge will forward correct messages
        tableView.rx_setDelegate(self, retainDelegate: false)
            >- disposeBag.addDisposable

        tableView.rx_selectedItem()
            >- subscribeNext { [unowned self] e in
                self.showDetailsForUserAtIndexPath(e.indexPath)
            }
            >- disposeBag.addDisposable

        tableView.rx_deleteItem()
            >- subscribeNext { [unowned self] e in
                self.removeUser(e.indexPath)
            }
            >- disposeBag.addDisposable

        tableView.rx_moveItem()
            >- subscribeNext { [unowned self] e in
                self.moveUserFrom(e.sourceIndexPath, to: e.destinationIndexPath)
            }
            >- disposeBag.addDisposable

        // Rx content offset
        tableView.rx_contentOffset
            >- subscribeNext { co in
                println("Content offset from Rx observer \(co)")
            }

You can check out example usage in Example project (Master Detail & reactive DataSource).

So this is all complexity that RxCocoa project will take.

But there is something else that I've added to make it little sweeter (and it took me quite a while to do it).

The are new examples of reactive data sources that know how to do animated changes for arbitrary data structures.
There is new directory in root called RxDataSourceStarterKit. It has all of the logic needed to to that (8 files in total).
This will for now be a directory that's not included into any pod file (although maybe in future it will), but RxExample will demonstrate how it works.
It's purpose is to provide great starting point for people who are using RxCocoa. You can either use it as a starting point to create their own reactive data sources optimized for special uses.
It would really be great if people would publish their own repositories with specialized/optimized data sources that are compatible with RxSwift/Cocoa.

The reason why I don't think there will ever be an ultimate generic solution is because there are so many orthogonal dimensions that it's pretty hard not to compromise in some dimension:

  • how do you determine identity of objects
  • how to determine the structure of data source
  • how to determine is object updated
  • are objects structures or references
  • are differences between transitions already precalculated in some form (I'm looking at you NSFetchedResultsController)
  • ....

I think the way you can use them now to do fully animated changes in UITableView is pretty slick

   let sections: Variable([
        NumberSection(model: "section 1", items: [1, 2, 3]),
        NumberSection(model: "section 2", items: [4, 5, 6]),
        NumberSection(model: "section 3", items: [7, 8, 9]),
          .....

     zip(self.sections, newSections) { (old, new) in
                return differentiate(old, new)
            }
            >- startWith(initialState)
            >- partialUpdatesTableViewOutlet.rx_subscribeWithReactiveDataSource(animatedDataSource)
            >- disposeBag.addDisposable

    // this will cause animated table view updates
    sections.next([
    HashableSectionModel(model: "section 4", items: [5]),
    HashableSectionModel(model: "section 6", items: [20, 14]),
    HashableSectionModel(model: "section 9", items: [])
    ])
...

Full example where you can play with animated data sources is in Example project (TableView & reactive partial updates).

Any feedback is welcome :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants