From a52510596f441ab8294da0e9ef217ef74d0ebe53 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Mon, 29 Dec 2014 18:11:42 +0100 Subject: [PATCH 01/12] changed calculation of x position in grid view to avoid position jitter --- JNWCollectionView/JNWCollectionViewGridLayout.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) mode change 100644 => 100755 JNWCollectionView/JNWCollectionViewGridLayout.m diff --git a/JNWCollectionView/JNWCollectionViewGridLayout.m b/JNWCollectionView/JNWCollectionViewGridLayout.m old mode 100644 new mode 100755 index e540be8..b81efbc --- a/JNWCollectionView/JNWCollectionViewGridLayout.m +++ b/JNWCollectionView/JNWCollectionViewGridLayout.m @@ -115,18 +115,19 @@ - (void)prepareLayout { NSUInteger numberOfSections = [self.collectionView numberOfSections]; CGFloat verticalSpacing = self.verticalSpacing; - self.itemPadding = 0; + CGFloat itemPadding = 0; if (numberOfColumns > 0) { if (self.itemHorizontalMargin == 0 && self.itemPaddingEnabled) { CGFloat totalPadding = totalWidth - (numberOfColumns * itemSize.width); - self.itemPadding = floorf(totalPadding / (numberOfColumns + 1)); + itemPadding = floorf(totalPadding / numberOfColumns); } else { - self.itemPadding = self.itemHorizontalMargin; + itemPadding = self.itemHorizontalMargin; } } else { numberOfColumns = 1; } + self.itemPadding = itemPadding; self.numberOfColumns = numberOfColumns; CGFloat totalHeight = 0; @@ -146,7 +147,7 @@ - (void)prepareLayout { for (NSInteger item = 0; item < numberOfItems; item++) { CGPoint origin = CGPointZero; NSInteger column = ((item - (item % numberOfColumns)) / numberOfColumns); - origin.x = sectionInsets.left + self.itemPadding + (item % numberOfColumns) * (itemSize.width + self.itemPadding); + origin.x = sectionInsets.left + itemPadding/2 + totalWidth/numberOfColumns * (item % numberOfColumns); origin.y = column * itemSize.height + column * verticalSpacing; sectionInfo.itemInfo[item].origin = origin; } From 0baf57e749119eea3cf9527c3b6d33dbae5ec259 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Wed, 31 Dec 2014 10:02:52 +0100 Subject: [PATCH 02/12] aligned center of cell instead of top edge when scrolling to JNWCollectionViewScrollPositionMiddle --- JNWCollectionView/JNWCollectionViewFramework.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index 4692d21..f0587d3 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -506,8 +506,8 @@ - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(JNWCo break; case JNWCollectionViewScrollPositionMiddle: // TODO + rect.origin.y -= ((CGRectGetHeight(visibleRect)-CGRectGetHeight(rect)) / 2.f); rect.size.height = self.bounds.size.height; - rect.origin.y += (CGRectGetHeight(visibleRect) / 2.f) - CGRectGetHeight(rect); break; case JNWCollectionViewScrollPositionBottom: // make the bottom of our rect flush with the bottom of the visible bounds From 1e6174aec6ac0f91340da5d598e9d9627387f57b Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Wed, 31 Dec 2014 15:17:21 +0100 Subject: [PATCH 03/12] improved calculation of adjacent cells in grid view --- JNWCollectionView/JNWCollectionViewGridLayout.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/JNWCollectionView/JNWCollectionViewGridLayout.m b/JNWCollectionView/JNWCollectionViewGridLayout.m index b81efbc..2a7f50c 100755 --- a/JNWCollectionView/JNWCollectionViewGridLayout.m +++ b/JNWCollectionView/JNWCollectionViewGridLayout.m @@ -225,13 +225,12 @@ - (NSIndexPath *)indexPathForNextItemInDirection:(JNWCollectionViewDirection)dir } else if (direction == JNWCollectionViewDirectionUp) { CGPoint origin = [self.collectionView rectForItemAtIndexPath:currentIndexPath].origin; // Bump the origin up to the cell directly above this one. - origin.y -= 1; // TODO: Use padding here when implemented. + origin.y -= (self.itemSize.height + self.verticalSpacing); newIndexPath = [self.collectionView indexPathForItemAtPoint:origin]; } else if (direction == JNWCollectionViewDirectionDown) { - CGRect frame = [self.collectionView rectForItemAtIndexPath:currentIndexPath]; - CGPoint origin = frame.origin; + CGPoint origin = [self.collectionView rectForItemAtIndexPath:currentIndexPath].origin; // Bump the origin down to the cell directly below this one. - origin.y += frame.size.height + 1; // TODO: Use padding here when implemented. + origin.y += (self.itemSize.height + self.verticalSpacing); newIndexPath = [self.collectionView indexPathForItemAtPoint:origin]; } From 22ae4908cfe67162695b5d364655e5dcd9ec3ab5 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Wed, 31 Dec 2014 15:21:04 +0100 Subject: [PATCH 04/12] improved multiple selection - Added option for disabling multiple selection - Added sorting of selected indexes array - Added multiple selection mode not deselect cells accidentally when using keys - Fixed and improved keyboard support with multiple selection --- .../JNWCollectionViewFramework.h | 5 + .../JNWCollectionViewFramework.m | 124 ++++++++++++------ 2 files changed, 88 insertions(+), 41 deletions(-) diff --git a/JNWCollectionView/JNWCollectionViewFramework.h b/JNWCollectionView/JNWCollectionViewFramework.h index 98886ae..aa01e1c 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.h +++ b/JNWCollectionView/JNWCollectionViewFramework.h @@ -267,6 +267,11 @@ typedef NS_ENUM(NSInteger, JNWCollectionViewScrollPosition) { /// Defaults to YES. @property (nonatomic, assign) BOOL allowsEmptySelection; +/// If set to NO, the collection view will not extend the selection when using command and shift modifier keys +/// +/// Defaults to YES. +@property (nonatomic, assign) BOOL allowsMultipleSelection; + /// Scrolls the collection view to the item at the specified path, optionally animated. The scroll position determines /// where the item is positioned on the screen. - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(JNWCollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index f0587d3..cde7c94 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -31,7 +31,8 @@ typedef NS_ENUM(NSInteger, JNWCollectionViewSelectionType) { JNWCollectionViewSelectionTypeSingle, JNWCollectionViewSelectionTypeExtending, - JNWCollectionViewSelectionTypeMultiple + JNWCollectionViewSelectionTypeMultiple, + JNWCollectionViewSelectionTypeMultipleDontAllowDeselection, }; @interface JNWCollectionView() { @@ -62,6 +63,7 @@ @interface JNWCollectionView() { // Selection @property (nonatomic, strong) NSMutableArray *selectedIndexes; +@property (nonatomic, strong) NSIndexPath* multipleSelectionSelectedIndexPath; // Cells @property (nonatomic, strong) NSMutableDictionary *reusableCells; // { identifier : (cells) } @@ -108,8 +110,8 @@ static void JNWCollectionViewCommonInit(JNWCollectionView *collectionView) { collectionView->_collectionViewFlags.wantsLayout = NO; collectionView.allowsSelection = YES; - collectionView.allowsEmptySelection = YES; + collectionView.allowsMultipleSelection = YES; collectionView.backgroundColor = NSColor.whiteColor; collectionView.drawsBackground = YES; @@ -901,8 +903,10 @@ - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { JNWCollectionViewCell *cell = [self cellForItemAtIndexPath:indexPath]; [cell setSelected:YES animated:self.animatesSelection]; - if (![self.selectedIndexes containsObject:indexPath]) + if (![self.selectedIndexes containsObject:indexPath]) { [self.selectedIndexes addObject:indexPath]; + [self.selectedIndexes sortUsingSelector:@selector(compare:)]; + } if (_collectionViewFlags.delegateDidSelect) { [self.delegate collectionView:self didSelectItemAtIndexPath:indexPath]; @@ -960,13 +964,20 @@ - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath } else { [indexesToSelect addObject:indexPath]; } + } else if (selectionType == JNWCollectionViewSelectionTypeMultipleDontAllowDeselection) { + [indexesToSelect addObjectsFromArray:self.selectedIndexes]; + if (![indexesToSelect containsObject:indexPath]) { + [indexesToSelect addObject:indexPath]; + } + } else if (selectionType == JNWCollectionViewSelectionTypeExtending) { // From what I have determined, this behavior should be as follows. // Take the index selected first, and select all items between there and the // last selected item. - NSIndexPath *firstIndex = (self.selectedIndexes.count > 0 ? self.selectedIndexes[0] : nil); + NSIndexPath *firstIndex = [self.selectedIndexes firstObject]; if (firstIndex != nil) { - [indexesToSelect addObject:firstIndex]; + [indexesToSelect addObjectsFromArray:self.selectedIndexes]; + //[indexesToSelect addObject:firstIndex]; if (![firstIndex isEqual:indexPath]) { NSComparisonResult order = [firstIndex compare:indexPath]; @@ -1009,13 +1020,14 @@ - (void)mouseDownInCollectionViewCell:(JNWCollectionViewCell *)cell withEvent:(N // Detect if modifier flags are held down. // We prioritize the command key over the shift key. - if (event.modifierFlags & NSCommandKeyMask) { + if (event.modifierFlags & NSCommandKeyMask && self.allowsMultipleSelection) { [self selectItemAtIndexPath:indexPath atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES selectionType:JNWCollectionViewSelectionTypeMultiple]; - } else if (event.modifierFlags & NSShiftKeyMask) { + } else if (event.modifierFlags & NSShiftKeyMask && self.allowsMultipleSelection) { [self selectItemAtIndexPath:indexPath atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES selectionType:JNWCollectionViewSelectionTypeExtending]; } else { [self selectItemAtIndexPath:indexPath atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES]; } + self.multipleSelectionSelectedIndexPath = nil; } - (void)mouseUpInCollectionViewCell:(JNWCollectionViewCell *)cell withEvent:(NSEvent *)event { @@ -1039,58 +1051,88 @@ - (void)rightClickInCollectionViewCell:(JNWCollectionViewCell *)cell withEvent:( } } -- (void)moveUp:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionUp currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES];} - -- (void)moveUpAndModifySelection:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionUp currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES selectionType:JNWCollectionViewSelectionTypeExtending]; +- (void)selectAll:(id)sender { + [self selectItemsAtIndexPaths:[self allIndexPaths] animated:YES]; } -- (void)moveDown:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionDown currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES]; +- (void)deselectAllItems { + [self deselectItemsAtIndexPaths:[self allIndexPaths] animated:YES]; } -- (void)moveDownAndModifySelection:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionDown currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES selectionType:JNWCollectionViewSelectionTypeExtending]; +- (void)selectAllItems { + [self selectAll:nil]; } -- (void)moveRight:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionRight currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES]; -} +#pragma mark - Keyboard Support -- (void)moveRightAndModifySelection:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionRight currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES selectionType:JNWCollectionViewSelectionTypeExtending]; +- (void) _selectIndexPathAndAfterKeyPressAccountForMultiSelection:(NSIndexPath*)path animated:(BOOL)animated +{ + NSEvent* event = [NSApp currentEvent]; + JNWCollectionViewSelectionType selectionType = JNWCollectionViewSelectionTypeSingle; + if (event.modifierFlags & NSCommandKeyMask && self.allowsMultipleSelection) { + selectionType = JNWCollectionViewSelectionTypeMultipleDontAllowDeselection; + } + else if (event.modifierFlags & NSShiftKeyMask && self.allowsMultipleSelection) { + selectionType = JNWCollectionViewSelectionTypeExtending; + } + [self selectItemAtIndexPath:path atScrollPosition:JNWCollectionViewScrollPositionNearest animated:animated selectionType:selectionType]; + self.multipleSelectionSelectedIndexPath = path; } -- (void)moveLeft:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionLeft currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES]; +- (void)moveUp:(id)sender +{ + NSIndexPath* currentIndexPath = (self.multipleSelectionSelectedIndexPath) ? self.multipleSelectionSelectedIndexPath : [[self indexPathsForSelectedItems] firstObject]; + + NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionUp currentIndexPath:currentIndexPath]; + + if ([self.selectedIndexes containsObject:toSelect]) { + toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionUp currentIndexPath:[[self indexPathsForSelectedItems] firstObject]]; + } + + [self _selectIndexPathAndAfterKeyPressAccountForMultiSelection:toSelect animated:YES]; } -- (void)moveLeftAndModifySelection:(id)sender { - NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionLeft currentIndexPath:[self indexPathForSelectedItem]]; - [self selectItemAtIndexPath:toSelect atScrollPosition:JNWCollectionViewScrollPositionNearest animated:YES selectionType:JNWCollectionViewSelectionTypeExtending]; +- (void)moveDown:(id)sender +{ + NSIndexPath* currentIndexPath = (self.multipleSelectionSelectedIndexPath) ? self.multipleSelectionSelectedIndexPath : [[self indexPathsForSelectedItems] lastObject]; + + NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionDown currentIndexPath:currentIndexPath]; + + if ([self.selectedIndexes containsObject:toSelect]) { + toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionDown currentIndexPath:[[self indexPathsForSelectedItems] lastObject]]; + } + + [self _selectIndexPathAndAfterKeyPressAccountForMultiSelection:toSelect animated:YES]; } -- (void)selectAll:(id)sender { - [self selectItemsAtIndexPaths:[self allIndexPaths] animated:YES]; +- (void)moveRight:(id)sender +{ + NSIndexPath* currentIndexPath = (self.multipleSelectionSelectedIndexPath) ? self.multipleSelectionSelectedIndexPath : [[self indexPathsForSelectedItems] lastObject]; + + NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionRight currentIndexPath:currentIndexPath]; + + if ([self.selectedIndexes containsObject:toSelect]) { + toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionRight currentIndexPath:[[self indexPathsForSelectedItems] lastObject]]; + } + + [self _selectIndexPathAndAfterKeyPressAccountForMultiSelection:toSelect animated:YES]; } -- (void)deselectAllItems { - [self deselectItemsAtIndexPaths:[self allIndexPaths] animated:YES]; +- (void)moveLeft:(id)sender +{ + NSIndexPath* currentIndexPath = (self.multipleSelectionSelectedIndexPath) ? self.multipleSelectionSelectedIndexPath : [[self indexPathsForSelectedItems] firstObject]; + + NSIndexPath *toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionLeft currentIndexPath:currentIndexPath]; + + if ([self.selectedIndexes containsObject:toSelect]) { + toSelect = [self.collectionViewLayout indexPathForNextItemInDirection:JNWCollectionViewDirectionLeft currentIndexPath:[[self indexPathsForSelectedItems] firstObject]]; + } + + [self _selectIndexPathAndAfterKeyPressAccountForMultiSelection:toSelect animated:YES]; } -- (void)selectAllItems { - [self selectAll:nil]; -} -#pragma mark NSObject +#pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; frame = %@; layer = <%@: %p>; content offset: %@> collection view layout: %@", From d218031e88abc667fd31dc45ec0b431f375d58fa Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Thu, 1 Jan 2015 17:39:17 +0100 Subject: [PATCH 05/12] remove option for fixed item padding, fixed left margin --- JNWCollectionView/JNWCollectionViewGridLayout.h | 14 +++++++++----- JNWCollectionView/JNWCollectionViewGridLayout.m | 12 +++--------- .../JNWCollectionViewDemo/GridDemoViewController.m | 1 + 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/JNWCollectionView/JNWCollectionViewGridLayout.h b/JNWCollectionView/JNWCollectionViewGridLayout.h index 96e4b5a..0233c12 100644 --- a/JNWCollectionView/JNWCollectionViewGridLayout.h +++ b/JNWCollectionView/JNWCollectionViewGridLayout.h @@ -62,11 +62,6 @@ extern NSString * const JNWCollectionViewGridLayoutFooterKind; /// override any value set here. @property (nonatomic, assign) CGSize itemSize; -/// Choose whether even padding between horizontal items is enabled. -/// -/// Default is YES. -@property (nonatomic, assign) BOOL itemPaddingEnabled; - /// The vertical spacing between rows in the grid. /// /// Defaults to 0. @@ -78,3 +73,12 @@ extern NSString * const JNWCollectionViewGridLayoutFooterKind; @property (nonatomic, assign) CGFloat itemHorizontalMargin; @end + + +@interface JNWCollectionViewGridLayout (Deprecated) +/// Choose whether even padding between horizontal items is enabled. +/// Not used anymore. Please use Flow Layout. +/// +/// Default is YES. +@property (nonatomic, assign) BOOL itemPaddingEnabled; +@end \ No newline at end of file diff --git a/JNWCollectionView/JNWCollectionViewGridLayout.m b/JNWCollectionView/JNWCollectionViewGridLayout.m index 2a7f50c..48785a5 100755 --- a/JNWCollectionView/JNWCollectionViewGridLayout.m +++ b/JNWCollectionView/JNWCollectionViewGridLayout.m @@ -72,8 +72,6 @@ - (instancetype)init { self = [super init]; if (self == nil) return nil; self.itemSize = JNWCollectionViewGridLayoutDefaultSize; - self.itemPaddingEnabled = YES; -; return self; } @@ -117,12 +115,8 @@ - (void)prepareLayout { CGFloat itemPadding = 0; if (numberOfColumns > 0) { - if (self.itemHorizontalMargin == 0 && self.itemPaddingEnabled) { - CGFloat totalPadding = totalWidth - (numberOfColumns * itemSize.width); - itemPadding = floorf(totalPadding / numberOfColumns); - } else { - itemPadding = self.itemHorizontalMargin; - } + CGFloat totalPadding = totalWidth - (numberOfColumns * itemSize.width); + itemPadding = floorf(totalPadding / numberOfColumns); } else { numberOfColumns = 1; @@ -147,7 +141,7 @@ - (void)prepareLayout { for (NSInteger item = 0; item < numberOfItems; item++) { CGPoint origin = CGPointZero; NSInteger column = ((item - (item % numberOfColumns)) / numberOfColumns); - origin.x = sectionInsets.left + itemPadding/2 + totalWidth/numberOfColumns * (item % numberOfColumns); + origin.x = sectionInsets.left + self.itemHorizontalMargin/2 + itemPadding/2 + totalWidth/numberOfColumns * (item % numberOfColumns); origin.y = column * itemSize.height + column * verticalSpacing; sectionInfo.itemInfo[item].origin = origin; } diff --git a/demo/JNWCollectionViewDemo/GridDemoViewController.m b/demo/JNWCollectionViewDemo/GridDemoViewController.m index 4b089b0..8aa6855 100644 --- a/demo/JNWCollectionViewDemo/GridDemoViewController.m +++ b/demo/JNWCollectionViewDemo/GridDemoViewController.m @@ -28,6 +28,7 @@ - (void)awakeFromNib { JNWCollectionViewGridLayout *gridLayout = [[JNWCollectionViewGridLayout alloc] init]; gridLayout.delegate = self; gridLayout.verticalSpacing = 10.f; + gridLayout.itemHorizontalMargin = 10.f; self.collectionView.collectionViewLayout = gridLayout; self.collectionView.dataSource = self; From 21ba88c49c9759e808eb7adfd6cf049029ff751f Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Fri, 9 Jan 2015 10:35:44 +0100 Subject: [PATCH 06/12] fixed mouse based multi-selection - fixed not sending mouse events up the responder chain when happend in cell - added deselect of cells when mouse clicked in background --- JNWCollectionView/JNWCollectionViewCell.m | 10 ++++------ JNWCollectionView/JNWCollectionViewFramework.m | 8 ++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/JNWCollectionView/JNWCollectionViewCell.m b/JNWCollectionView/JNWCollectionViewCell.m index 0722948..6512282 100644 --- a/JNWCollectionView/JNWCollectionViewCell.m +++ b/JNWCollectionView/JNWCollectionViewCell.m @@ -157,15 +157,13 @@ - (NSImage *)backgroundImage { return self.backgroundView.image; } -- (void)mouseDown:(NSEvent *)theEvent { - [super mouseDown:theEvent]; - +- (void)mouseDown:(NSEvent *)theEvent +{ [self.collectionView mouseDownInCollectionViewCell:self withEvent:theEvent]; } -- (void)mouseUp:(NSEvent *)theEvent { - [super mouseUp:theEvent]; - +- (void)mouseUp:(NSEvent *)theEvent +{ [self.collectionView mouseUpInCollectionViewCell:self withEvent:theEvent]; if (theEvent.clickCount == 2) { diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index cde7c94..12ef07b 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -1063,6 +1063,14 @@ - (void)selectAllItems { [self selectAll:nil]; } +- (void)mouseDown:(NSEvent *)theEvent +{ + NSEvent* event = [NSApp currentEvent]; + if ((event.modifierFlags >> 16) == 0) { + [self deselectAllItems]; + } +} + #pragma mark - Keyboard Support - (void) _selectIndexPathAndAfterKeyPressAccountForMultiSelection:(NSIndexPath*)path animated:(BOOL)animated From f5f04224e4f2ffc0216ee2d6d87cccf0a266d769 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Fri, 9 Jan 2015 11:15:50 +0100 Subject: [PATCH 07/12] added home and end function key support Unfortunately at least on OS X 10.10 [NSWindow _processKeyboardUIKey:] only implements moveLeft:, moveRight:, moveUp: and moveDown:. Everything else that is not supported by NSScrollView needs to be added manually --- .../JNWCollectionViewFramework.m | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index 12ef07b..45d4fb0 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -1071,6 +1071,8 @@ - (void)mouseDown:(NSEvent *)theEvent } } + + #pragma mark - Keyboard Support - (void) _selectIndexPathAndAfterKeyPressAccountForMultiSelection:(NSIndexPath*)path animated:(BOOL)animated @@ -1140,6 +1142,26 @@ - (void)moveLeft:(id)sender } +- (BOOL)performKeyEquivalent:(NSEvent *)theEvent +{ + NSString* characters = [theEvent charactersIgnoringModifiers]; + if ([characters length] == 0) { + return NO; + } + + unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0]; + if (key == NSHomeFunctionKey && [self respondsToSelector:@selector(scrollToBeginningOfDocument:)]) { + [self scrollToBeginningOfDocument:theEvent]; + return YES; + } + else if (key == NSEndFunctionKey && [self respondsToSelector:@selector(scrollToEndOfDocument:)]) { + [self scrollToEndOfDocument:theEvent]; + return YES; + } + return NO; +} + + #pragma mark - NSObject - (NSString *)description { @@ -1148,4 +1170,18 @@ - (NSString *)description { NSStringFromPoint(self.documentVisibleRect.origin), self.collectionViewLayout]; } +#pragma mark - NSResponder + +// TODO: make these ask the layout for "where's the beginning/end?" in case of non-ltr layouts +- (void)scrollToBeginningOfDocument:(id)sender { + [self.clipView scrollRectToVisible:NSMakeRect(0, 0, 0, 0) animated:self.animatesSelection]; +} + +- (void)scrollToEndOfDocument:(id)sender { + NSSize documentSize = ((JNWCollectionViewDocumentView *)self.clipView.documentView).frame.size; + NSRect scrollToRect = NSMakeRect(documentSize.width, documentSize.height, 0, 0); + [self.clipView scrollRectToVisible:scrollToRect animated:self.animatesSelection]; +} + + @end From 3e907b092edb55531863500e50e84ff8708306d5 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Fri, 9 Jan 2015 16:33:01 +0100 Subject: [PATCH 08/12] disabled performance hungry reloading of cells and added delete method to customize relayouting cells --- JNWCollectionView/JNWCollectionViewFramework.h | 4 ++++ JNWCollectionView/JNWCollectionViewFramework.m | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/JNWCollectionView/JNWCollectionViewFramework.h b/JNWCollectionView/JNWCollectionViewFramework.h index aa01e1c..68df145 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.h +++ b/JNWCollectionView/JNWCollectionViewFramework.h @@ -118,6 +118,10 @@ typedef NS_ENUM(NSInteger, JNWCollectionViewScrollPosition) { /// back into the reuse queue. - (void)collectionView:(JNWCollectionView *)collectionView didEndDisplayingCell:(JNWCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; +/// Tells the delegate that the collection view is about to reload the cells, +/// view size has already been calculated, +/// gives the delegate the chance the update the scroll position +- (void)collectionViewWillRelayoutCells:(JNWCollectionView *)collectionView; @end #pragma mark Reloading and customizing diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index 45d4fb0..7b22f9a 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -51,6 +51,7 @@ @interface JNWCollectionView() { unsigned int delegateDidDoubleClick:1; unsigned int delegateDidRightClick:1; unsigned int delegateDidEndDisplayingCell:1; + unsigned int delegateWillRelayoutCells:1; unsigned int wantsLayout; } _collectionViewFlags; @@ -146,6 +147,7 @@ - (void)setDelegate:(id)delegate { _collectionViewFlags.delegateDidEndDisplayingCell = [delegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]; _collectionViewFlags.delegateShouldScroll = [delegate respondsToSelector:@selector(collectionView:shouldScrollToItemAtIndexPath:)]; _collectionViewFlags.delegateDidScroll = [delegate respondsToSelector:@selector(collectionView:didScrollToItemAtIndexPath:)]; + _collectionViewFlags.delegateWillRelayoutCells = [delegate respondsToSelector:@selector(collectionViewWillRelayoutCells:)]; } - (void)setDataSource:(id)dataSource { @@ -593,7 +595,8 @@ - (void)layout { BOOL shouldInvalidate = [self.collectionViewLayout shouldInvalidateLayoutForBoundsChange:visibleBounds]; [self.data recalculateAndPrepareLayout:shouldInvalidate]; - [self performFullRelayoutForcingSubviewsReset:shouldInvalidate]; +#warning changed for performance reasons, what's the catch? + [self performFullRelayoutForcingSubviewsReset:YES]; } } @@ -601,7 +604,13 @@ - (void)collectionViewLayoutWasInvalidated:(JNWCollectionViewLayout *)layout { // First we prepare the layout. In the future it would possibly be a good idea to coalesce // this call to reduce unnecessary layout preparation calls. [self.data recalculateAndPrepareLayout:YES]; - [self performFullRelayoutForcingSubviewsReset:YES]; + + if (_collectionViewFlags.delegateWillRelayoutCells) { + [self.delegate collectionViewWillRelayoutCells:self]; + } + +#warning changed for performance reasons, what's the catch? + [self performFullRelayoutForcingSubviewsReset:NO]; } - (void)performFullRelayoutForcingSubviewsReset:(BOOL)forceReset { From 82bd6ee3b0d0e1e6eb7bfc6281b08ad81f49dfed Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Sat, 10 Jan 2015 13:17:35 +0100 Subject: [PATCH 09/12] added willDisplayCell delegate method --- JNWCollectionView/JNWCollectionViewFramework.h | 2 ++ JNWCollectionView/JNWCollectionViewFramework.m | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/JNWCollectionView/JNWCollectionViewFramework.h b/JNWCollectionView/JNWCollectionViewFramework.h index 68df145..7085486 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.h +++ b/JNWCollectionView/JNWCollectionViewFramework.h @@ -114,6 +114,8 @@ typedef NS_ENUM(NSInteger, JNWCollectionViewScrollPosition) { /// Tells the delegate that the specified index path has been scrolled to. - (void)collectionView:(JNWCollectionView *)collectionView didScrollToItemAtIndexPath:(NSIndexPath *)indexPath; +- (void)collectionView:(JNWCollectionView *)collectionView willDisplayCell:(JNWCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; + /// Tells the delegate that the cell for the specified index path has been put /// back into the reuse queue. - (void)collectionView:(JNWCollectionView *)collectionView didEndDisplayingCell:(JNWCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath; diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index 7b22f9a..b146e28 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -52,6 +52,7 @@ @interface JNWCollectionView() { unsigned int delegateDidRightClick:1; unsigned int delegateDidEndDisplayingCell:1; unsigned int delegateWillRelayoutCells:1; + unsigned int delegateWillDisplayCell:1; unsigned int wantsLayout; } _collectionViewFlags; @@ -148,6 +149,7 @@ - (void)setDelegate:(id)delegate { _collectionViewFlags.delegateShouldScroll = [delegate respondsToSelector:@selector(collectionView:shouldScrollToItemAtIndexPath:)]; _collectionViewFlags.delegateDidScroll = [delegate respondsToSelector:@selector(collectionView:didScrollToItemAtIndexPath:)]; _collectionViewFlags.delegateWillRelayoutCells = [delegate respondsToSelector:@selector(collectionViewWillRelayoutCells:)]; + _collectionViewFlags.delegateWillDisplayCell = [delegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]; } - (void)setDataSource:(id)dataSource { @@ -719,6 +721,10 @@ - (void)layoutCellsWithRedraw:(BOOL)needsVisibleRedraw { JNWCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath]; [self applyLayoutAttributes:attributes toCell:cell]; + if (_collectionViewFlags.delegateWillDisplayCell) { + [self.delegate collectionView:self willDisplayCell:cell forItemAtIndexPath:indexPath]; + } + if (cell.superview == nil) { [self.documentView addSubview:cell]; } else { From 105ebe87e44aba2ef473a7d8a864b22583eeca19 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Mon, 12 Jan 2015 08:21:40 +0100 Subject: [PATCH 10/12] moved relayout delegate call to where document view already has the new frameSize --- JNWCollectionView/JNWCollectionViewFramework.m | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index b146e28..c39c3af 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -507,18 +507,19 @@ - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(JNWCo break; case JNWCollectionViewScrollPositionTop: // make the top of our rect flush with the top of the visible bounds - rect.size.height = CGRectGetHeight(visibleRect); + rect.size.height = NSHeight(visibleRect); //rect.origin.y = self.documentVisibleRect.origin.y + rect.size.height; break; case JNWCollectionViewScrollPositionMiddle: // TODO - rect.origin.y -= ((CGRectGetHeight(visibleRect)-CGRectGetHeight(rect)) / 2.f); + + rect.origin.y -= ((NSHeight(visibleRect)-NSHeight(rect)) / 2.f); rect.size.height = self.bounds.size.height; break; case JNWCollectionViewScrollPositionBottom: // make the bottom of our rect flush with the bottom of the visible bounds - rect.size.height = CGRectGetHeight(visibleRect); - rect.origin.y -= CGRectGetHeight(visibleRect); + rect.size.height = NSHeight(visibleRect); + rect.origin.y -= NSHeight(visibleRect); break; case JNWCollectionViewScrollPositionNone: // no scroll needed @@ -607,10 +608,6 @@ - (void)collectionViewLayoutWasInvalidated:(JNWCollectionViewLayout *)layout { // this call to reduce unnecessary layout preparation calls. [self.data recalculateAndPrepareLayout:YES]; - if (_collectionViewFlags.delegateWillRelayoutCells) { - [self.delegate collectionViewWillRelayoutCells:self]; - } - #warning changed for performance reasons, what's the catch? [self performFullRelayoutForcingSubviewsReset:NO]; } @@ -621,6 +618,11 @@ - (void)performFullRelayoutForcingSubviewsReset:(BOOL)forceReset { } [self layoutDocumentView]; + + if (_collectionViewFlags.delegateWillRelayoutCells) { + [self.delegate collectionViewWillRelayoutCells:self]; + } + [self layoutCellsWithRedraw:YES]; [self layoutSupplementaryViewsWithRedraw:YES]; From 4a8d6c60b03eb4b95dd08b7486401caacae29233 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Mon, 12 Jan 2015 11:04:57 +0100 Subject: [PATCH 11/12] added selection API with option to extent selection --- JNWCollectionView/JNWCollectionViewFramework.h | 6 ++++++ JNWCollectionView/JNWCollectionViewFramework.m | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/JNWCollectionView/JNWCollectionViewFramework.h b/JNWCollectionView/JNWCollectionViewFramework.h index 7085486..792796b 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.h +++ b/JNWCollectionView/JNWCollectionViewFramework.h @@ -287,6 +287,12 @@ typedef NS_ENUM(NSInteger, JNWCollectionViewScrollPosition) { /// desired, pass in JNWCollectionViewScrollPositionNone to prevent the scroll.. - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(JNWCollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; +/// Selects the item at the specified index path, deselecting any other selected items in the process, optionally animated. +/// The collection view will then scroll to that item in the position as determined by scrollPosition. If no scroll is +/// desired, pass in JNWCollectionViewScrollPositionNone to prevent the scroll.. +/// extendSelection: A BOOL value that specifies whether to extend the current selection. Pass YES to extends the selection; NO replaces the current selection. +- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(JNWCollectionViewScrollPosition)scrollPosition byExtendingSelection:(BOOL)extendSelection animated:(BOOL)animated; + /// Selects all items in the collection view. - (void)selectAllItems; diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index c39c3af..69918f7 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -936,6 +936,15 @@ - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath [self selectItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated selectionType:JNWCollectionViewSelectionTypeSingle]; } +- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(JNWCollectionViewScrollPosition)scrollPosition byExtendingSelection:(BOOL)extendSelection animated:(BOOL)animated +{ + if (!extendSelection) { + [self selectItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; + } else { + [self selectItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated selectionType:JNWCollectionViewSelectionTypeMultiple]; + } +} + - (NSIndexPath *)indexPathForNextSelectableItemAfterIndexPath:(NSIndexPath *)indexPath { if (indexPath == nil && [self validateIndexPath:[NSIndexPath jnw_indexPathForItem:0 inSection:0]]) { // Passing `nil` will select the very first index path From 726bca31b4c6dbc5eaba4316d3eec643d3d6e2b5 Mon Sep 17 00:00:00 2001 From: Martin Hering Date: Thu, 15 Oct 2015 11:49:31 +0200 Subject: [PATCH 12/12] fixed xcode 7 warnings on OS X 10.11 --- JNWCollectionView/JNWCollectionViewFramework.h | 12 ------------ JNWCollectionView/JNWCollectionViewFramework.m | 7 +++---- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/JNWCollectionView/JNWCollectionViewFramework.h b/JNWCollectionView/JNWCollectionViewFramework.h index 792796b..0cb40f2 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.h +++ b/JNWCollectionView/JNWCollectionViewFramework.h @@ -190,18 +190,6 @@ typedef NS_ENUM(NSInteger, JNWCollectionViewScrollPosition) { /// Defaults to nil. @property (nonatomic, strong) JNWCollectionViewLayout *collectionViewLayout; -/// The background color determines what is drawn underneath any cells that might be visible -/// at the time. If this is a repeating pattern image, it will scroll along with the content. -/// -/// Defaults to a white color. -@property (nonatomic, strong) NSColor *backgroundColor; - -/// Whether or not the collection view draws the background color. If the collection view -/// background color needs to be transparent, this should be disabled. -/// -/// Defaults to YES. -@property (nonatomic, assign) BOOL drawsBackground; - #pragma mark - Information /// Returns the total number of sections. diff --git a/JNWCollectionView/JNWCollectionViewFramework.m b/JNWCollectionView/JNWCollectionViewFramework.m index 69918f7..bfcab17 100644 --- a/JNWCollectionView/JNWCollectionViewFramework.m +++ b/JNWCollectionView/JNWCollectionViewFramework.m @@ -79,13 +79,11 @@ @interface JNWCollectionView() { @property (nonatomic, strong) NSMutableDictionary *supplementaryViewClassMap; // { "kind/identifier" : class } @property (nonatomic, strong) NSMutableDictionary *supplementaryViewNibMap; // { "kind/identifier" : nib } -@property (nonatomic, strong) NSView *documentView; +@property (nonatomic, strong) NSView *myDocumentView; @end @implementation JNWCollectionView -@dynamic drawsBackground; -@dynamic backgroundColor; // We're using a static function for the common initialization so that subclassers // don't accidentally override this method in their own common init method. @@ -106,7 +104,8 @@ static void JNWCollectionViewCommonInit(JNWCollectionView *collectionView) { collectionView.wantsLayer = YES; // Set the document view to a custom class that returns YES to -isFlipped. - collectionView.documentView = [[JNWCollectionViewDocumentView alloc] initWithFrame:CGRectZero]; + collectionView.myDocumentView = [[JNWCollectionViewDocumentView alloc] initWithFrame:CGRectZero]; + collectionView.documentView = collectionView.myDocumentView; // We don't want to perform an initial layout pass until the user has called -reloadData. collectionView->_collectionViewFlags.wantsLayout = NO;