Skip to content

Commit

Permalink
Adding initial drag-to-reorder support to TUITableView; still needs w…
Browse files Browse the repository at this point in the history
…ork.
  • Loading branch information
Brian William Wolter committed Aug 1, 2011
1 parent df419f7 commit 9e1a635
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 1 deletion.
29 changes: 29 additions & 0 deletions lib/UIKit/TUITableView+Cell.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2011 Twitter, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless 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.
*/

#import "TUITableView.h"

/**
* @brief Exposes internal table view methods to cells.
*/
@interface TUITableView (Cell)

-(void)__mouseDownInCell:(TUITableViewCell *)cell offset:(CGPoint)offset event:(NSEvent *)event;
-(void)__mouseUpInCell:(TUITableViewCell *)cell offset:(CGPoint)offset event:(NSEvent *)event;
-(void)__mouseDraggedCell:(TUITableViewCell *)cell offset:(CGPoint)offset event:(NSEvent *)event;

@end

142 changes: 142 additions & 0 deletions lib/UIKit/TUITableView+Cell.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
Copyright 2011 Twitter, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless 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.
*/

#import "TUITableView+Cell.h"

@implementation TUITableView (Cell)

/**
* @brief Mouse down in a cell
*/
-(void)__mouseDownInCell:(TUITableViewCell *)cell offset:(CGPoint)offset event:(NSEvent *)event {
[_referenceDragToReorderIndexPath release];
_referenceDragToReorderIndexPath = [cell.indexPath retain];
[_currentDragToReorderIndexPath release];
_currentDragToReorderIndexPath = [cell.indexPath retain];
[_previousDragToReorderIndexPath release];
_previousDragToReorderIndexPath = [cell.indexPath retain];
}

/**
* @brief Mouse up in a cell
*/
-(void)__mouseUpInCell:(TUITableViewCell *)cell offset:(CGPoint)offset event:(NSEvent *)event {
[_referenceDragToReorderIndexPath release];
_referenceDragToReorderIndexPath = nil;
[_currentDragToReorderIndexPath release];
_currentDragToReorderIndexPath = nil;
[_previousDragToReorderIndexPath release];
_previousDragToReorderIndexPath = nil;
}

/**
* @brief A cell was dragged
*
* If reordering is permitted by the table, this will begin a move operation.
*/
-(void)__mouseDraggedCell:(TUITableViewCell *)cell offset:(CGPoint)offset event:(NSEvent *)event {

// determine if reordering this cell is permitted or not via our delegate
if(self.delegate == nil || ![self.delegate respondsToSelector:@selector(tableView:allowsReorderingOfRowAtIndexPath:)] || ![self.delegate tableView:self allowsReorderingOfRowAtIndexPath:cell.indexPath]){
return; // reordering cells is not permitted
}

CGPoint location = [[cell superview] localPointForEvent:event];
CGRect visible = [self visibleRect];

// dragged cell destination frame
CGRect dest = CGRectMake(0, roundf(MAX(0, MIN(visible.origin.y + visible.size.height - cell.frame.size.height, location.y + visible.origin.y - offset.y))), self.bounds.size.width, cell.frame.size.height);

// determine the current index path the cell is occupying
TUIFastIndexPath *currentPath;
if((currentPath = [self indexPathForRowAtPoint:CGPointMake(location.x, location.y + visible.origin.y)]) != nil){
// allow the delegate to revise the proposed index path if it wants to
if(self.delegate != nil && [self.delegate respondsToSelector:@selector(tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:)]){
currentPath = [self.delegate tableView:self targetIndexPathForMoveFromRowAtIndexPath:cell.indexPath toProposedIndexPath:currentPath];
}
}

// note the previous path
[_previousDragToReorderIndexPath release];
_previousDragToReorderIndexPath = [_currentDragToReorderIndexPath retain];

// determine the current drag direction
NSComparisonResult currentDragDirection = (_previousDragToReorderIndexPath != nil) ? [currentPath compare:_previousDragToReorderIndexPath] : NSOrderedSame;

// we now have the final destination index path. if it's not nil, update surrounding
// cells to make room for the dragged cell
if(currentPath != nil && (_currentDragToReorderIndexPath == nil || ![currentPath isEqual:_currentDragToReorderIndexPath])){
TUIFastIndexPath *previousPath = (_currentDragToReorderIndexPath == nil) ? cell.indexPath : _currentDragToReorderIndexPath;

// determine whether we're above or below the original index path and handle the
// reordering accodringly
if(currentDragDirection == NSOrderedAscending){
NSLog(@"Above: %@", currentPath);
CGFloat adjust = ([currentPath compare:cell.indexPath] == NSOrderedDescending) ? 1 : 0;

int irow = currentPath.row;
for(int i = currentPath.section; i < [self numberOfSections]; i++){
for(int j = irow; j < [self numberOfRowsInSection:i]; j++){
TUIFastIndexPath *path = [TUIFastIndexPath indexPathForRow:j inSection:i];
TUITableViewCell *displacedCell;
if([path isEqual:_previousDragToReorderIndexPath]){
goto done; // stop when we hit the original row
}else if((displacedCell = [self cellForRowAtIndexPath:path]) != nil){
CGRect frame = [self rectForRowAtIndexPath:path];
displacedCell.frame = CGRectMake(frame.origin.x, frame.origin.y - cell.frame.size.height, frame.size.width, frame.size.height);
}
}
irow = 0;
}

}else if(currentDragDirection == NSOrderedDescending){
NSLog(@"Below: %@ (%@)", currentPath, _previousDragToReorderIndexPath);
CGFloat adjust = ([currentPath compare:cell.indexPath] == NSOrderedDescending) ? 0 : 1;

int irow = _previousDragToReorderIndexPath.row;
for(int i = _previousDragToReorderIndexPath.section; i < [self numberOfSections]; i++){
for(int j = irow; j < [self numberOfRowsInSection:i]; j++){
TUIFastIndexPath *path = [TUIFastIndexPath indexPathForRow:j inSection:i];
TUITableViewCell *displacedCell;
if((displacedCell = [self cellForRowAtIndexPath:path]) != nil){
CGRect frame = [self rectForRowAtIndexPath:path];
displacedCell.frame = CGRectMake(frame.origin.x, frame.origin.y + (cell.frame.size.height * adjust), frame.size.width, frame.size.height);
}
if([path isEqual:currentPath]){
goto done; // stop when we hit the current row
}
}
irow = 0;
}

}

}

done:
// note the current path
[_currentDragToReorderIndexPath release];
_currentDragToReorderIndexPath = [currentPath retain];

// bring to front
[[cell superview] bringSubviewToFront:cell];
// move the cell
cell.frame = dest;

}

@end

13 changes: 13 additions & 0 deletions lib/UIKit/TUITableView.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ typedef enum {
- (void)tableView:(TUITableView *)tableView didSelectRowAtIndexPath:(TUIFastIndexPath *)indexPath; // happens on mouse down
- (void)tableView:(TUITableView *)tableView didDeselectRowAtIndexPath:(TUIFastIndexPath *)indexPath;
- (void)tableView:(TUITableView *)tableView didClickRowAtIndexPath:(TUIFastIndexPath *)indexPath withEvent:(NSEvent *)event; // happens on mouse up (can look at clickCount)

// the following are good places to update or restore state (such as selection) when the table data reloads
- (void)tableViewWillReloadData:(TUITableView *)tableView;
- (void)tableViewDidReloadData:(TUITableView *)tableView;

// the following are required to support dragging to reorder cells
- (BOOL)tableView:(TUITableView *)tableView allowsReorderingOfRowAtIndexPath:(TUIFastIndexPath *)indexPath;
- (TUIFastIndexPath *)tableView:(TUITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(TUIFastIndexPath *)fromPath toProposedIndexPath:(TUIFastIndexPath *)proposedPath;

@end

@interface TUITableView : TUIScrollView
Expand All @@ -70,6 +76,11 @@ typedef enum {
TUIFastIndexPath * _keepVisibleIndexPathForReload;
CGFloat _relativeOffsetForReload;

TUIFastIndexPath * _currentDragToReorderIndexPath;
TUIFastIndexPath * _previousDragToReorderIndexPath;
TUIFastIndexPath * _referenceDragToReorderIndexPath;
NSComparisonResult _currentDragToReorderDirection;

struct {
unsigned int animateSelectionChanges:1;
unsigned int forceSaveScrollPosition:1;
Expand All @@ -79,6 +90,7 @@ typedef enum {
unsigned int dataSourceNumberOfSectionsInTableView:1;
unsigned int delegateTableViewWillDisplayCellForRowAtIndexPath:1;
} _tableFlags;

}

- (id)initWithFrame:(CGRect)frame style:(TUITableViewStyle)style; // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
Expand Down Expand Up @@ -106,6 +118,7 @@ typedef enum {
- (NSIndexSet *)indexesOfSectionHeadersInRect:(CGRect)rect;
- (TUIFastIndexPath *)indexPathForCell:(TUITableViewCell *)cell; // returns nil if cell is not visible
- (NSArray *)indexPathsForRowsInRect:(CGRect)rect; // returns nil if rect not valid
- (TUIFastIndexPath *)indexPathForRowAtPoint:(CGPoint)point;

- (TUITableViewCell *)cellForRowAtIndexPath:(TUIFastIndexPath *)indexPath; // returns nil if cell is not visible or index path is out of range
- (NSArray *)visibleCells; // no particular order
Expand Down
29 changes: 29 additions & 0 deletions lib/UIKit/TUITableView.m
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ - (void)dealloc
[_indexPathShouldBeFirstResponder release];
[_keepVisibleIndexPathForReload release];
[_pullDownView release];
[_currentDragToReorderIndexPath release];
[_previousDragToReorderIndexPath release];
[_referenceDragToReorderIndexPath release];
[super dealloc];
}

Expand Down Expand Up @@ -416,6 +419,32 @@ - (NSArray *)indexPathsForRowsInRect:(CGRect)rect
return indexPaths;
}

/**
* @brief Obtain the index path of the row at the specified point
*
* If the point is not valid or no row exists at that point, nil is
* returned.
*
* @param point location in the table view
* @return index path of the row at @p point
*/
- (TUIFastIndexPath *)indexPathForRowAtPoint:(CGPoint)point {

NSInteger sectionIndex = 0;
for(TUITableViewSection *section in _sectionInfo){
for(NSInteger row = 0; row < [section numberOfRows]; row++){
TUIFastIndexPath *indexPath = [TUIFastIndexPath indexPathForRow:row inSection:sectionIndex];
CGRect cellRect = [self rectForRowAtIndexPath:indexPath];
if(CGRectContainsPoint(cellRect, point)){
return indexPath;
}
}
++sectionIndex;
}

return nil;
}

- (TUIFastIndexPath *)_topVisibleIndexPath
{
TUIFastIndexPath *topVisibleIndex = nil;
Expand Down
5 changes: 4 additions & 1 deletion lib/UIKit/TUITableViewCell.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ typedef enum {

@interface TUITableViewCell : TUIView
{
NSString *_reuseIdentifier;

NSString * _reuseIdentifier;
CGPoint _mouseOffset;

struct {
unsigned int highlighted:1;
unsigned int selected:1;
} _tableViewCellFlags;

}

- (id)initWithStyle:(TUITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier;
Expand Down
20 changes: 20 additions & 0 deletions lib/UIKit/TUITableViewCell.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#import "TUITableViewCell.h"
#import "TUITableView.h"
#import "TUITableView+Cell.h"

@implementation TUITableViewCell

Expand Down Expand Up @@ -74,16 +75,35 @@ - (BOOL)acceptsFirstMouse:(NSEvent *)event

- (void)mouseDown:(NSEvent *)event
{

// note the initial mouse location for dragging
_mouseOffset = [self localPointForLocationInWindow:[event locationInWindow]];
// notify our table view of the event
[self.tableView __mouseDownInCell:self offset:_mouseOffset event:event];

TUITableView *tableView = self.tableView;
[tableView selectRowAtIndexPath:self.indexPath animated:tableView.animateSelectionChanges scrollPosition:TUITableViewScrollPositionNone];
[super mouseDown:event]; // may make the text renderer first responder, so we want to do the selection before this

_tableViewCellFlags.highlighted = 1;
[self setNeedsDisplay];

}

/**
* @brief The table cell was dragged
*/
-(void)mouseDragged:(NSEvent *)event {
// notify our table view of the event
[self.tableView __mouseDraggedCell:self offset:_mouseOffset event:event];
}

- (void)mouseUp:(NSEvent *)event
{
[super mouseUp:event];
// notify our table view of the event
[self.tableView __mouseUpInCell:self offset:_mouseOffset event:event];

_tableViewCellFlags.highlighted = 0;
[self setNeedsDisplay];

Expand Down

0 comments on commit 9e1a635

Please sign in to comment.