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

WIP: Auto diffing section controller #449

Closed
wants to merge 14 commits into from
Closed

WIP: Auto diffing section controller #449

wants to merge 14 commits into from

Conversation

rnystrom
Copy link
Contributor

@rnystrom rnystrom commented Jan 27, 2017

⚠️⚠️⚠️ Do Not Merge ⚠️⚠️⚠️

Started work on the plane to get this moving since #418 is up and ready to land. We'll likely need to spend some time fleshing out the API of this, and I think I'll split it up into a couple different PRs once ready for review. Putting this up now to get early feedback.

What is this?

This adds an auto-diffing section controller as outlined in #38. There are several key parts:

  • Subclass a new section controller IGListAutoSectionController (naming wip)
  • Connect a data source
  • Implement the data source methods that do 3 things:
    • Given a top-level object, transform it into an array of diffable view models
    • Given a view model, return a cell
    • Given a view model, return a size for a cell
  • A new protocol for the cell IGListBindable so that we can control when the cell is updated w/ the view model.
    • The most important part of this is that it unlocks moving and reloading a cell, which you can't do w/ UICollectionView

TODO

  • Unit test reloadObjects:
  • Add didSelectItemAtIndex: support
  • Verify all headers use FB copyright
  • Create PR for IGListAdapterUpdater changes
  • Create PR for new section controller w/ unit tests
  • Create PR for example
  • Add header docs

IGListAdapter

I had to make some changes to IGListAdapterUpdater in order for this to work. The biggest challenge is when the section controller receives a new object, it has to split it up into view models and then apply changes.

However, didUpdateToObject: will be called inside performBatchUpdates:, so we need to diff and fire off the changes on the collection view immediately. A few issues:

Diffing top-level models

One super important piece to getting this right is having top-level (the ones given to IGListAdapter and handed to the SC in didUpdateToObject: always equal itself, even if its values change. I solved this in the unit tests w/ an object that implements diffing like:

@interface IGTestObject: NSObject <IGListDiffable>
@property NSString *key;
@end

@implementation IGTestObject
- (id)diffIdentifier {
  return self.key;
}

- (BOOL)isEqualToDiffableObject:(id)object {
  return [self.key isEqual:[object key]];
}
@end

If you use a traditional isEqualToDiffableObject: (where you check all props/values) you'll end up reloading the section, avoiding all the nice cell animations.

I started making a stock object that does this, something like IGListAutoObject, but its so simple that it's kind of annoying. It's essentially a model box that will probably end up more confusing than having to learn how to diff w/ the auto SC.

Would love thoughts here.

Selection

didSelectItemAtIndex: is totally broken. Options are:

  • Add a new method on the data source protocol
    • Gross b/c that's another empty method for a lot of folks
  • Add another delegate protocol you can optionally wire up

We've discussed splitting off didSelectItemAtIndex: into a selectionDelegate (like the display/scroll/etc delegate) in #184 and elsewhere. That might help, though the view models array is private, so we'd have to expose that or create another delegate API.

How can I help?

Since this is super early, please hold on nit picks, formatting, etc. I'll work on that as we go along. But there are some big things I'd love your thoughts on:

  • The name IGListAutoSectionController. Anything better out there? I thought IGListDiffableSectionController but idk
  • I'm using a data source to split the object into view models, return cells, and handle sizes. I started off passing the SC blocks on init that did the transform (sort of like IGListSingleSectionController). I thought the API was kind of gross though, and you have to subclass the SC anyways.
  • IGListBindable is great, but you can't just use the view model to config the cell. For instance, how do you make the SC the cell delegate? That's why I'm leaving the method to still return a cell, but we will call bind before returning it. Is this ok?
  • Sample project now or later?
  • Let me know if you see some glaring Swift interop issues. I haven't tried it yet.
  • Throw all the crazy test-case scenarios you got at me!

@rnystrom rnystrom changed the base branch from master to move-items January 27, 2017 05:08
@zhubofei
Copy link

zhubofei commented Jan 27, 2017

@rnystrom How about IGListReactSectionController 🤔? The one-way data flow behavior is similar to react.js

@Sherlouk
Copy link
Contributor

Sounds amazing!

I'd say we should definitely try and create some examples with this, an easy way to show usage and will be easier to spot most/some Swift interop issues!

@rnystrom
Copy link
Contributor Author

@zhubofei

How about IGListReactSectionController 🤔 The one-way data flow behavior is similar to react.js

That's so true! I worry about stepping on their toes though w/ naming. I should probably leave all the "react" stuff in their court. But I like where you're going w/ this.

@Sherlouk agreed! I'll probably include all of the changes in this PR so we can get a feel for things, but then split it up into 3 PRs (updater changes, new SC, example).

Btw forgot to mention that the updater changes should also fix #288.

@zhubofei
Copy link

zhubofei commented Jan 28, 2017

@rnystrom I prefer IGListDiffableSectionController to IGListAutoSectionController. IMHO, Auto may be a little bit vague compared to what we already have i.e. Stack, Single. Or, can we use sth like IGListTemplateSectionController? b\c we have viewModel built into the SC.

Copy link
Contributor

@Adlai-Holler Adlai-Holler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is extremely exciting @rnystrom!

@interface IGListAutoSectionController()

@property (nonatomic, strong) id object;
@property (nonatomic, strong) NSArray<id<IGListDiffable>> *viewModels;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make this publicly readable? Or add a viewModelAtIndex: method to access this internal store? As a client it would be good to know what the state-of-the-world for this section controller is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Absolutely we should. I can imagine plenty of cases where someone would need to grab the view model.


- (NSArray<id<IGListDiffable>> *)viewModelsForObject:(id)object;
- (UICollectionViewCell<IGListBindable> *)cellForViewModel:(id)viewModel atIndex:(NSInteger)index;
- (CGSize)sizeForViewModel:(id)viewModel;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these pass sender i.e. - (CGSize)autoSectionController:(IGListAutoSectionController *)sectionController sizeForViewModel:(id)viewModel? It's more formally correct but uglier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, good point. I guess to be proper they probably should.

result = IGListDiff(oldViewModels, self.viewModels, IGListDiffEquality);

[collectionContext deleteInSectionController:self atIndexes:result.deletes];
[collectionContext insertInSectionController:self atIndexes:result.inserts];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we support formally reloading the item for an updated view-model, rather than only binding a new view model to the existing cell?

One idea is, we add a data source method like:
- (BOOL)shouldReloadCellForUpdatedViewModel:(id)viewModel atIndex:(NSInteger)index

And call it for each updated view model to decide whether to reload or just -bindToViewModel:.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the purpose be for actual reloading? Bear in mind that we don't actually call reloadItemsAtIndexPaths: b/c of UICollectionView issues, we convert it to a delete+insert under the hood (that's the whole thing about moving alongside reload/bind here).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the model changes in such a way that we need a new cell class to represent it, a reload (or delete/insert) is the only way to achieve the update. It's not a huge requirement, but I think it's worth considering at this early stage since it would affect the API bigly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Adlai-Holler interesting point. I could see that.

I'm leaning towards not supporting this yet and filling it in later if someone really-really needs it (who knows, we might run into it). I'd love to see some concrete use-cases before we design the API too.

Plus it shouldn't be breaking when we add it, only an enhancement.

bigly

🙊 😅

@jessesquires jessesquires added this to the 3.0.0 milestone Feb 3, 2017
@rnystrom
Copy link
Contributor Author

rnystrom commented Feb 6, 2017

Some other naming ideas:

  • IGListBindingSectionController (bindable, binder)
  • IGListViewModelSectionController

@facebook-github-bot
Copy link
Contributor

@rnystrom updated the pull request - view changes

jessesquires and others added 12 commits February 7, 2017 16:16
Summary: Closes #472

Differential Revision: D4524473

Pulled By: jessesquires

fbshipit-source-id: b72f3dde2cf3eb1c8f9b53ee73cf87cc04e34d08
Summary:
Before the diff, you can lookup the object for a given section, and then lookup the section controller for that object, but this seems like a pretty valuable/common operation.
Closes #477

Differential Revision: D4537479

Pulled By: rnystrom

fbshipit-source-id: ad47a243f0bb0fc72a362863dff2f00b0b640fab
Summary:
Working on porting our collection view layout to IGListKit. I'm doing this because its a solid layout, and we just finished preparing it to work with inline sections. It is designed to work in tandem with IGListKit, so we're adding it.

This is still a WIP as I add more tests, but I'd love as much feedback as possible.

Aside from the glob of header documentation, this has the following features:

- Infinite sections that each have infinite items. Sections and items can fall inline. When they break the width of their container they will fall on the next row.
- Sections can have their own insets, line spacing, and interitem spacing.
- Sticky header support! When you use headers, it will always newline the section.
- Maximum width with a border decoration view
  - Use this to pinch in your content on larger devices

Followup to #423

- [ ] ~~Move decoration view support to delegate~~ removed
- [x] Unit test changing [top y sticky inset](https://coveralls.io/builds/9977284/source?filen
Closes #450

Reviewed By: jessesquires

Differential Revision: D4521797

Pulled By: rnystrom

fbshipit-source-id: 20b36ae573d38ca3125a6f3d5faec181c290ab94
Summary:
- [x] All tests pass. Demo project builds and runs.
- [ ] I added tests, an experiment, or detailed why my change isn't tested.
- [ ] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
- [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)

I guess there's some mistake in unavailable hints
Closes #479

Differential Revision: D4543489

Pulled By: rnystrom

fbshipit-source-id: fb925fad04d2931fbfd0e26a87d36f85eee0b2e9
…orting inline sections

Summary: This reverts commit 20b36ae573d38ca3125a6f3d5faec181c290ab94

Differential Revision: D4521797

fbshipit-source-id: 447de6cf2b30de9c2109dffb266326aceceec7fc
Summary:
Internal crash reports (see T15774792) indicate we're having integer overflow/underflow issues.
I notcied this inconsistency, which was introduced by GH issues #431, #440.

Differential Revision: D4546852

fbshipit-source-id: 67e56487cce02f082943f3008bcfcb5cf6205e0e
Summary:
Adding an API to do item-level (cell) moves on the collection view. This complicates things a little bit because of all the issues that moving sections have while in batch updates (e.g. simultaneous animation UICV bugs). Thankfully we use pretty strict types so the compiler does most of the work for us.

Closes #145

- [x] Tests build and pass
- [x] Add `IGListBatchUpdateData` tests to check moves during
  - [x] ~~Moving within a reloaded section (no op)~~ can't reload sections
  - [x] Moving within a deleted section (no op)
  - [x] Moving within a moved section (convert section ops to delete+insert)
  - [x] Moving an index path that is also reloaded (convert to delete+insert path)
- [x] Add move unit tests to `IGListAdapterUpdater`
- [x] Add move unit tests to `IGListReloadDataUpdater` (mostly for code coverage...)
- [x] Add move unit tests to `IGListStackedSectionController`
- [x] Add `CHANGELOG.md` entry for 3.0.0
- [x] Test moving without batch
Closes #418

Reviewed By: jessesquires

Differential Revision: D4521732

Pulled By: rnystrom

fbshipit-source-id: 99a46d1cbb0cc1f857a62ff6ca257aff6e8b7f25
Summary:
Working on porting our collection view layout to IGListKit. I'm doing this because its a solid layout, and we just finished preparing it to work with inline sections. It is designed to work in tandem with IGListKit, so we're adding it.

This is still a WIP as I add more tests, but I'd love as much feedback as possible.

Aside from the glob of header documentation, this has the following features:

- Infinite sections that each have infinite items. Sections and items can fall inline. When they break the width of their container they will fall on the next row.
- Sections can have their own insets, line spacing, and interitem spacing.
- Sticky header support! When you use headers, it will always newline the section.
- Maximum width with a border decoration view
  - Use this to pinch in your content on larger devices

Followup to #423

- [ ] ~~Move decoration view support to delegate~~ removed
- [x] Unit test changing [top y sticky inset](https://coveralls.io/builds/9977284/source?filen
Closes #484

Differential Revision: D4547760

Pulled By: rnystrom

fbshipit-source-id: 879e2da16eb78bb6a90967e77d9ad0bbf7c69594
Summary: This diff isn't correct, and the Instagram cut happens today. Pulling back so I have time to test the correct change more throughly.

Reviewed By: rnystrom

Differential Revision: D4550149

fbshipit-source-id: 2e882caeb9ef999b7fd57562740b352ea8edfa5f
Summary:
This was a little bit of an invasive change with the display handler, but I think that this is the right call. When sending display events for objects, we should account for the supplementary view as part of the section controller. This is especially useful for headers and footers.

Note that this wont effect the working range API at all.

Fixes #300
Closes #470

Differential Revision: D4551338

Pulled By: rnystrom

fbshipit-source-id: dda6fbf18bcfc2c941d80ee2314a543d1ab83843
Summary:
Follow from #450, this layout has been replaced. You served us well!

- [x] All tests pass. Demo project builds and runs.
- [x] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
Closes #482

Differential Revision: D4553897

Pulled By: rnystrom

fbshipit-source-id: caccb386ba8914fbbf5e81d997524efb78c6e4ea
Summary:
Removing the assert and handling `nil` section controllers. This wont effect Swift code which demands a non-optional section controller, but Objective-C is more nuanced. There is evidence that some callers do not have a default state and are returning `nil`.

Unfortunately this will [OOB here](https://github.com/Instagram/IGListKit/blob/master/Source/Internal/IGListSectionMap.m#L63) if using the original `objects` array provided by the data source. Instead we prune the array, only allowing in objects that have a matching section controller.

Fixes t15773862 internally.

- [x] All tests pass. Demo project builds and runs.
- [x] I added tests, an experiment, or detailed why my change isn't tested.
- [x] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
Closes #488

Reviewed By: dshahidehpour

Differential Revision: D4553886

Pulled By: rnystrom

fbshipit-source-id: a473a99b5eb513e4b610019dba0394a014193fc4
@amonshiz
Copy link
Contributor

IGListDiffingSectionController

  • This new object is taking some actions on behalf of the developer and so the name should be active.
  • The action that the object is taking is that of diffing up some view models.
  • It is a section controller.

jessesquires and others added 2 commits February 14, 2017 15:33
Summary: Reduce and avoid unnecessary copies in IGListAdapter and IGListSectionMap.

Reviewed By: rnystrom

Differential Revision: D4555347

fbshipit-source-id: 3ade3311955fe5d12fc7617ad72feba9dafb60fb
@facebook-github-bot
Copy link
Contributor

@rnystrom updated the pull request - view changes

@rnystrom
Copy link
Contributor Author

History got messed up, recreated and moving to #494

@rnystrom rnystrom closed this Feb 15, 2017
@jessesquires jessesquires deleted the auto-diff branch February 15, 2017 16:58
@jessesquires jessesquires removed this from the 3.0.0 milestone Feb 15, 2017
@rnystrom rnystrom mentioned this pull request Mar 1, 2017
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants