-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Interactive Reordering #976
Conversation
TODO: - IGListStackedSectionController - Tests
Generated by 🚫 Danger |
Thank you for this pull request @jverdi. Would this increase the minimal iOS version requirement from 8.0 to 9.0? As I think the interactive movement feature was introduced in iOS 9? The library is targeting 8.0 at the moment but maybe this can be changed @rnystrom if necessary? As Instagram itself seems to be iOS 9 or later. |
Hi @weyert - the PR shouldn't require changing the deployment target to iOS 9. The 9+ specific code for interactive reordering only gets implemented in the client apps (and IGListKit example projects). But, to take advantage of this optional feature would require iOS 9. Common solutions to support interactive reordering on iOS <9 typically impose a specific UICollectionViewLayout like |
Great thank you, @jverdi. Sounds like a great addition to the project! (and sorry for the dumb question |
Thanks @jverdi ! 🙌 |
Source/IGListAdapterDataSource.h
Outdated
@@ -60,7 +60,27 @@ NS_SWIFT_NAME(ListAdapterDataSource) | |||
adding the background view and maintaining its visibility. | |||
*/ | |||
- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter; | |||
|
|||
|
|||
@optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just noting:
we can keep this for 3.2
to avoid breaking clients and remove for 4.0
.
after this merges, let's be sure to open a new issue to track this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jverdi could we move this to just be a new delegate on IGListAdapter
called IGListAdapterMoveDelegate
? Then we:
- Don't have to use
@optional
at all - Can let it be
nil
by default - Don't add any boilerplate to clients that don't use moving (all of them at the beginning). This will saves hundreds of empty implementations within Instagram 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks awesome, I think this is absolutely on the right track. Would love to land this and include in 3.2 announcement.
Other thing to start thinking about is testing. We will definitely need to cover this w/ some tests. Doesn't have to be incredibly exhaustive, but at least get some groundwork so if a regression happens we can add tests on top of the initial ones.
@param fromIndex The source index of the section to move. | ||
@param toIndex The destination index of the section to move. | ||
*/ | ||
- (void)moveSectionInCollectionView:(UICollectionView *)collectionView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this is a breaking change, but I'm not sure if ppl are making their own updaters. @jessesquires thoughts?
Source/IGListAdapterDataSource.h
Outdated
@param sectionIndex The index of the section proposed to be moved. | ||
@param index The index of the object proposed to be moved. | ||
*/ | ||
- (BOOL)listAdapter:(IGListAdapter *)listAdapter canMoveObjectInSection:(NSInteger)sectionIndex atIndex:(NSInteger)index; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way we could make this a property (defaulted to NO
) on the adapter? Adding another (eventually) required method like this will be a pretty big pain to upgrade.
Another option, (which I like a lot more), can we add a IGListAdapterMovingDelegate
or something onto IGListAdapter
? Then we can make all the methods required and not break any upgrades. Plus, if you don't use moving, it makes your data source implementation much cleaner.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing this entirely based on later feedback.
// for ease of implementation, first we check with the listAdapter | ||
if ([dataSource respondsToSelector:@selector(listAdapter:canMoveObjectInSection:atIndex:)]) { | ||
return [dataSource listAdapter:self canMoveObjectInSection:sectionIndex atIndex:itemIndex]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a note above, but wondering if we can just skip this entirely? Maybe just rely on the section controller deciding if a move is allowed or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure -- I was thinking about whole section reordering here. My motivation was that for section controllers to be reusable, they might be re-orderable or not depending on the context of the collection/adapter they appear in.
But, if the delegates are all to be required eventually, yes, it's worth dropping this to reduce complexity.
IGAssert(dataSource != nil, @"Found a nil dataSource when requesting canMoveItemAtIndexPath for interactive reordering"); | ||
|
||
NSInteger sectionIndex = indexPath.section; | ||
NSInteger itemIndex = indexPath.item; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: const
on both
NSInteger sourceItemIndex = sourceIndexPath.item; | ||
NSInteger destinationItemIndex = destinationIndexPath.item; | ||
NSInteger sourceSectionIndex = sourceIndexPath.section; | ||
NSInteger destinationSectionIndex = destinationIndexPath.section; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: const
all of it
// the "item" representing our section was dropped | ||
// into the beginning of a destination section rather than the end | ||
// so it really belongs one section before the section where it landed | ||
destinationSectionIndex -= 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great!
|
||
// otherwise this is a move of an _item_ from one section to another section | ||
// this is not currently supported, so we need to revert the change as it's too late to cancel | ||
[self revertInvalidInteractiveMoveFromIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're comments are 💯
Source/IGListSectionController.h
Outdated
|
||
@param index The index of the unhighlighted cell. | ||
|
||
@note Interactive reordering is supported both for items within a single section, as well as for reordering sections themselves when sections contain only one item. The default implementation returns false. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source/IGListSectionController.m
Outdated
} | ||
|
||
- (void)moveObjectFromIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex { | ||
IGFailAssert(@"Section controller %@ must override %s if interactive reordering is enabled.", self, __PRETTY_FUNCTION__); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
Source/IGListAdapterDataSource.h
Outdated
@param sourceIndex The starting index of the object. | ||
@param destinationIndex The ending index of the object. | ||
*/ | ||
- (void)listAdapter:(IGListAdapter *)listAdapter moveSectionAtIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
B/c data sources could be out of sync w/ what is being displayed (ultimately held by IGListAdapter
internally), I think we should do one of the following:
- Add params for to/from objects
- Add to/from object arrays
Ok, feedback addressed and tests added. I still have to dive into |
Super keen to see this PR merged as it's something I'm keen to use in a current project. |
… for this purpose
…ationContextClass
I'm going to hold on importing until Monday since everyone will be back from the holidays. SO excited! |
Sounds great! Thanks for all the feedback. |
@jverdi I was trying out your interactive reordering functionality and I experienced a reoccurring crash in the following scenario: UICollectionView with layout: While dragging to move a section controller it would crash on: I was able to successfully use your PR by switching my data source to use one section controller with many items. Please ignore if it was intended to not function with many section controllers per row. |
return NO; | ||
} | ||
|
||
- (BOOL)canMoveItemAtIndex:(NSInteger)sourceItemIndex toIndex:(NSInteger)destinationItemIndex { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be exposed for overriding in order to allow users to prevent moving to specific indices?
Currently even if canMoveItemAtIndex:
returns false for a specific index that item can still be moved when you move another item into its place.
Thanks for the reminder! Going to merge Sent with GitHawk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rnystrom is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index]; | ||
// during interactive reordering, its possible for an item to be moved into a section beyond the last section | ||
// in that case, just return the size of the current last item in the section | ||
const NSInteger maxIndex = [self numberOfItems]-1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h/t to @amonshiz for catching this bug. numberOfItems
can absolutely return 0
. I'm updating locally before landing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol oops 😳
nice! 😎 |
@RamblinWreck77 3.3.0 WIP right now 😇 I believe it should work just fine w/ an NxN grid but haven't done that myself. Would be an awesome addition to the demos app! |
Why didn't this get merged? :( |
I had a desire for interactive reordering in a personal project, so here's a first attempt at adding support in IGListKit.
I figured I might as well get a WIP PR up for comments before I continue further as there are a few aspects to interactive reordering that don't interplay perfectly with IGListKit.
As discussed in #291, I went after two prime use cases:
I also "disabled" moving items between sections by having those moves revert, to mimic interactive reordering cancellation as closely as possible.
You can see both in the Mixed Data example. Grid items can be moved within a section, while users can be moved to reorder whole sections. But trying to move a grid item out of a grid or a user item into a grid will auto-revert. The revert animation isn't as tight as it should be. It may be more desirable to disable the animation - though you lose the visual cue.
There is a also a new example,
ReorderableViewController
, that demonstrates 2 in its pure form (likely the most desired use case), where all sections are reorderable single rows.Happy to take feedback -- this is my first experience working on IGListKit, so I would expect there to be gaps. (Ex. I haven't used
IGListStackedSectionController
, and its tests failed as I hadn't implemented reordering delegates for it. Those are simply stubbed out for now.)Changes in this pull request
Issue fixed: #291
Checklist
CHANGELOG.md
for any breaking changes, enhancements, or bug fixes.Additional TODO
IGListStackedSectionController