├── .gitignore ├── JUCollectionView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── JUCollectionView ├── AppDelegate.h ├── AppDelegate.m ├── JUCollectionView-Info.plist ├── JUCollectionView-Prefix.pch ├── en.lproj │ └── MainMenu.xib └── main.m ├── README.markdown └── Source ├── JUCollectionView+Selection.h ├── JUCollectionView+Selection.m ├── JUCollectionView.h ├── JUCollectionView.m ├── JUCollectionViewCell.h ├── JUCollectionViewCell.m └── JUCollectionViewDelegate.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /JUCollectionView.xcodeproj/project.xcworkspace/xcuserdata/Sidney.xcuserdatad/UserInterfaceState.xcuserstate 3 | /JUCollectionView.xcodeproj/project.xcworkspace/xcuserdata/Sidney.xcuserdatad/WorkspaceSettings.xcsettings 4 | /JUCollectionView.xcodeproj/xcuserdata/Sidney.xcuserdatad/xcschemes/JUCollectionView.xcscheme 5 | /JUCollectionView.xcodeproj/xcuserdata/Sidney.xcuserdatad/xcschemes/xcschememanagement.plist 6 | /JUCollectionView.xcodeproj/xcuserdata/Sidney.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist -------------------------------------------------------------------------------- /JUCollectionView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E9C89B9613F0B18100E257B6 /* JUCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E9C89B9513F0B18100E257B6 /* JUCollectionViewCell.m */; }; 11 | E9C89B9F13F0E7EE00E257B6 /* JUCollectionView+Selection.m in Sources */ = {isa = PBXBuildFile; fileRef = E9C89B9E13F0E7EE00E257B6 /* JUCollectionView+Selection.m */; }; 12 | E9E3A32913F0A30000125FAA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9E3A32813F0A2FF00125FAA /* Cocoa.framework */; }; 13 | E9E3A33513F0A30000125FAA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E9E3A33413F0A30000125FAA /* main.m */; }; 14 | E9E3A33C13F0A30000125FAA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E9E3A33B13F0A30000125FAA /* AppDelegate.m */; }; 15 | E9E3A33F13F0A30000125FAA /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9E3A33D13F0A30000125FAA /* MainMenu.xib */; }; 16 | E9E3A34F13F0A39900125FAA /* JUCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = E9E3A34E13F0A39900125FAA /* JUCollectionView.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | E9C89B9413F0B18100E257B6 /* JUCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JUCollectionViewCell.h; sourceTree = ""; }; 21 | E9C89B9513F0B18100E257B6 /* JUCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JUCollectionViewCell.m; sourceTree = ""; }; 22 | E9C89B9C13F0E77900E257B6 /* JUCollectionViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JUCollectionViewDelegate.h; sourceTree = ""; }; 23 | E9C89B9D13F0E7EE00E257B6 /* JUCollectionView+Selection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JUCollectionView+Selection.h"; sourceTree = ""; }; 24 | E9C89B9E13F0E7EE00E257B6 /* JUCollectionView+Selection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JUCollectionView+Selection.m"; sourceTree = ""; }; 25 | E9E3A32413F0A2FF00125FAA /* JUCollectionView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JUCollectionView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | E9E3A32813F0A2FF00125FAA /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 27 | E9E3A32B13F0A30000125FAA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 28 | E9E3A32C13F0A30000125FAA /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 29 | E9E3A32D13F0A30000125FAA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 30 | E9E3A33013F0A30000125FAA /* JUCollectionView-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JUCollectionView-Info.plist"; sourceTree = ""; }; 31 | E9E3A33413F0A30000125FAA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 32 | E9E3A33613F0A30000125FAA /* JUCollectionView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JUCollectionView-Prefix.pch"; sourceTree = ""; }; 33 | E9E3A33A13F0A30000125FAA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 34 | E9E3A33B13F0A30000125FAA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 35 | E9E3A33E13F0A30000125FAA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 36 | E9E3A34D13F0A39900125FAA /* JUCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JUCollectionView.h; sourceTree = ""; }; 37 | E9E3A34E13F0A39900125FAA /* JUCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JUCollectionView.m; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | E9E3A32113F0A2FF00125FAA /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | E9E3A32913F0A30000125FAA /* Cocoa.framework in Frameworks */, 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | E9E3A31913F0A2FF00125FAA = { 53 | isa = PBXGroup; 54 | children = ( 55 | E9E3A34C13F0A39900125FAA /* Source */, 56 | E9E3A32E13F0A30000125FAA /* JUCollectionView */, 57 | E9E3A32713F0A2FF00125FAA /* Frameworks */, 58 | E9E3A32513F0A2FF00125FAA /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | E9E3A32513F0A2FF00125FAA /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | E9E3A32413F0A2FF00125FAA /* JUCollectionView.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | E9E3A32713F0A2FF00125FAA /* Frameworks */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | E9E3A32813F0A2FF00125FAA /* Cocoa.framework */, 74 | E9E3A32A13F0A30000125FAA /* Other Frameworks */, 75 | ); 76 | name = Frameworks; 77 | sourceTree = ""; 78 | }; 79 | E9E3A32A13F0A30000125FAA /* Other Frameworks */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | E9E3A32B13F0A30000125FAA /* AppKit.framework */, 83 | E9E3A32C13F0A30000125FAA /* CoreData.framework */, 84 | E9E3A32D13F0A30000125FAA /* Foundation.framework */, 85 | ); 86 | name = "Other Frameworks"; 87 | sourceTree = ""; 88 | }; 89 | E9E3A32E13F0A30000125FAA /* JUCollectionView */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | E9E3A33A13F0A30000125FAA /* AppDelegate.h */, 93 | E9E3A33B13F0A30000125FAA /* AppDelegate.m */, 94 | E9E3A33D13F0A30000125FAA /* MainMenu.xib */, 95 | E9E3A32F13F0A30000125FAA /* Supporting Files */, 96 | ); 97 | path = JUCollectionView; 98 | sourceTree = ""; 99 | }; 100 | E9E3A32F13F0A30000125FAA /* Supporting Files */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | E9E3A33013F0A30000125FAA /* JUCollectionView-Info.plist */, 104 | E9E3A33613F0A30000125FAA /* JUCollectionView-Prefix.pch */, 105 | E9E3A33413F0A30000125FAA /* main.m */, 106 | ); 107 | name = "Supporting Files"; 108 | sourceTree = ""; 109 | }; 110 | E9E3A34C13F0A39900125FAA /* Source */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | E9E3A34D13F0A39900125FAA /* JUCollectionView.h */, 114 | E9E3A34E13F0A39900125FAA /* JUCollectionView.m */, 115 | E9C89B9D13F0E7EE00E257B6 /* JUCollectionView+Selection.h */, 116 | E9C89B9E13F0E7EE00E257B6 /* JUCollectionView+Selection.m */, 117 | E9C89B9413F0B18100E257B6 /* JUCollectionViewCell.h */, 118 | E9C89B9513F0B18100E257B6 /* JUCollectionViewCell.m */, 119 | E9C89B9C13F0E77900E257B6 /* JUCollectionViewDelegate.h */, 120 | ); 121 | path = Source; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | E9E3A32313F0A2FF00125FAA /* JUCollectionView */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = E9E3A34213F0A30000125FAA /* Build configuration list for PBXNativeTarget "JUCollectionView" */; 130 | buildPhases = ( 131 | E9E3A32013F0A2FF00125FAA /* Sources */, 132 | E9E3A32113F0A2FF00125FAA /* Frameworks */, 133 | E9E3A32213F0A2FF00125FAA /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = JUCollectionView; 140 | productName = JUCollectionView; 141 | productReference = E9E3A32413F0A2FF00125FAA /* JUCollectionView.app */; 142 | productType = "com.apple.product-type.application"; 143 | }; 144 | /* End PBXNativeTarget section */ 145 | 146 | /* Begin PBXProject section */ 147 | E9E3A31B13F0A2FF00125FAA /* Project object */ = { 148 | isa = PBXProject; 149 | buildConfigurationList = E9E3A31E13F0A2FF00125FAA /* Build configuration list for PBXProject "JUCollectionView" */; 150 | compatibilityVersion = "Xcode 3.2"; 151 | developmentRegion = English; 152 | hasScannedForEncodings = 0; 153 | knownRegions = ( 154 | en, 155 | ); 156 | mainGroup = E9E3A31913F0A2FF00125FAA; 157 | productRefGroup = E9E3A32513F0A2FF00125FAA /* Products */; 158 | projectDirPath = ""; 159 | projectRoot = ""; 160 | targets = ( 161 | E9E3A32313F0A2FF00125FAA /* JUCollectionView */, 162 | ); 163 | }; 164 | /* End PBXProject section */ 165 | 166 | /* Begin PBXResourcesBuildPhase section */ 167 | E9E3A32213F0A2FF00125FAA /* Resources */ = { 168 | isa = PBXResourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | E9E3A33F13F0A30000125FAA /* MainMenu.xib in Resources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXResourcesBuildPhase section */ 176 | 177 | /* Begin PBXSourcesBuildPhase section */ 178 | E9E3A32013F0A2FF00125FAA /* Sources */ = { 179 | isa = PBXSourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | E9E3A33513F0A30000125FAA /* main.m in Sources */, 183 | E9E3A33C13F0A30000125FAA /* AppDelegate.m in Sources */, 184 | E9E3A34F13F0A39900125FAA /* JUCollectionView.m in Sources */, 185 | E9C89B9613F0B18100E257B6 /* JUCollectionViewCell.m in Sources */, 186 | E9C89B9F13F0E7EE00E257B6 /* JUCollectionView+Selection.m in Sources */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | /* End PBXSourcesBuildPhase section */ 191 | 192 | /* Begin PBXVariantGroup section */ 193 | E9E3A33D13F0A30000125FAA /* MainMenu.xib */ = { 194 | isa = PBXVariantGroup; 195 | children = ( 196 | E9E3A33E13F0A30000125FAA /* en */, 197 | ); 198 | name = MainMenu.xib; 199 | sourceTree = ""; 200 | }; 201 | /* End PBXVariantGroup section */ 202 | 203 | /* Begin XCBuildConfiguration section */ 204 | E9E3A34013F0A30000125FAA /* Debug */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 209 | COPY_PHASE_STRIP = NO; 210 | GCC_C_LANGUAGE_STANDARD = gnu99; 211 | GCC_DYNAMIC_NO_PIC = NO; 212 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 213 | GCC_OPTIMIZATION_LEVEL = 0; 214 | GCC_PREPROCESSOR_DEFINITIONS = ( 215 | "DEBUG=1", 216 | "$(inherited)", 217 | ); 218 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 219 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 220 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 221 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 222 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 223 | GCC_WARN_UNUSED_VARIABLE = YES; 224 | MACOSX_DEPLOYMENT_TARGET = 10.7; 225 | ONLY_ACTIVE_ARCH = YES; 226 | SDKROOT = macosx; 227 | }; 228 | name = Debug; 229 | }; 230 | E9E3A34113F0A30000125FAA /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 235 | COPY_PHASE_STRIP = YES; 236 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 237 | GCC_C_LANGUAGE_STANDARD = gnu99; 238 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 239 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 240 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 241 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 242 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | MACOSX_DEPLOYMENT_TARGET = 10.7; 245 | SDKROOT = macosx; 246 | }; 247 | name = Release; 248 | }; 249 | E9E3A34313F0A30000125FAA /* Debug */ = { 250 | isa = XCBuildConfiguration; 251 | buildSettings = { 252 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 253 | GCC_PREFIX_HEADER = "JUCollectionView/JUCollectionView-Prefix.pch"; 254 | INFOPLIST_FILE = "JUCollectionView/JUCollectionView-Info.plist"; 255 | PRODUCT_NAME = "$(TARGET_NAME)"; 256 | WRAPPER_EXTENSION = app; 257 | }; 258 | name = Debug; 259 | }; 260 | E9E3A34413F0A30000125FAA /* Release */ = { 261 | isa = XCBuildConfiguration; 262 | buildSettings = { 263 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 264 | GCC_PREFIX_HEADER = "JUCollectionView/JUCollectionView-Prefix.pch"; 265 | INFOPLIST_FILE = "JUCollectionView/JUCollectionView-Info.plist"; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | WRAPPER_EXTENSION = app; 268 | }; 269 | name = Release; 270 | }; 271 | /* End XCBuildConfiguration section */ 272 | 273 | /* Begin XCConfigurationList section */ 274 | E9E3A31E13F0A2FF00125FAA /* Build configuration list for PBXProject "JUCollectionView" */ = { 275 | isa = XCConfigurationList; 276 | buildConfigurations = ( 277 | E9E3A34013F0A30000125FAA /* Debug */, 278 | E9E3A34113F0A30000125FAA /* Release */, 279 | ); 280 | defaultConfigurationIsVisible = 0; 281 | defaultConfigurationName = Release; 282 | }; 283 | E9E3A34213F0A30000125FAA /* Build configuration list for PBXNativeTarget "JUCollectionView" */ = { 284 | isa = XCConfigurationList; 285 | buildConfigurations = ( 286 | E9E3A34313F0A30000125FAA /* Debug */, 287 | E9E3A34413F0A30000125FAA /* Release */, 288 | ); 289 | defaultConfigurationIsVisible = 0; 290 | defaultConfigurationName = Release; 291 | }; 292 | /* End XCConfigurationList section */ 293 | }; 294 | rootObject = E9E3A31B13F0A2FF00125FAA /* Project object */; 295 | } 296 | -------------------------------------------------------------------------------- /JUCollectionView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JUCollectionView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionViewAppDelegate.h 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import 19 | #import "JUCollectionView.h" 20 | 21 | @interface AppDelegate : NSObject 22 | { 23 | IBOutlet NSWindow *window; 24 | IBOutlet JUCollectionView *collectionView; 25 | 26 | NSMutableArray *content; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /JUCollectionView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionViewAppDelegate.m 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import "AppDelegate.h" 19 | 20 | @implementation AppDelegate 21 | 22 | - (NSUInteger)numberOfCellsInCollectionView:(JUCollectionView *)view 23 | { 24 | return [content count]; 25 | } 26 | 27 | - (JUCollectionViewCell *)collectionView:(JUCollectionView *)view cellForIndex:(NSUInteger)index 28 | { 29 | JUCollectionViewCell *cell = [view dequeueReusableCellWithIdentifier:@"cell"]; 30 | 31 | if(!cell) 32 | cell = [[[JUCollectionViewCell alloc] initWithReuseIdentifier:@"cell"] autorelease]; 33 | 34 | [cell setImage:[content objectAtIndex:index]]; 35 | 36 | return cell; 37 | } 38 | 39 | - (void)collectionView:(JUCollectionView *)_collectionView didSelectCellAtIndex:(NSUInteger)index 40 | { 41 | NSLog(@"Selected cell at index: %u", (unsigned int)index); 42 | NSLog(@"Position: %@", NSStringFromPoint([_collectionView positionOfCellAtIndex:index])); 43 | } 44 | 45 | 46 | 47 | 48 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 49 | { 50 | content = [[NSMutableArray alloc] init]; 51 | 52 | for(int i=0; i<1000; i++) // This creates 59000 elements! 53 | { 54 | [content addObject:[NSImage imageNamed:NSImageNameQuickLookTemplate]]; 55 | [content addObject:[NSImage imageNamed:NSImageNameBluetoothTemplate]]; 56 | [content addObject:[NSImage imageNamed:NSImageNameIChatTheaterTemplate]]; 57 | [content addObject:[NSImage imageNamed:NSImageNameSlideshowTemplate]]; 58 | [content addObject:[NSImage imageNamed:NSImageNameActionTemplate]]; 59 | [content addObject:[NSImage imageNamed:NSImageNameSmartBadgeTemplate]]; 60 | [content addObject:[NSImage imageNamed:NSImageNameIconViewTemplate]]; 61 | [content addObject:[NSImage imageNamed:NSImageNameListViewTemplate]]; 62 | [content addObject:[NSImage imageNamed:NSImageNameColumnViewTemplate]]; 63 | [content addObject:[NSImage imageNamed:NSImageNameFlowViewTemplate]]; 64 | [content addObject:[NSImage imageNamed:NSImageNamePathTemplate]]; 65 | [content addObject:[NSImage imageNamed:NSImageNameInvalidDataFreestandingTemplate]]; 66 | [content addObject:[NSImage imageNamed:NSImageNameLockLockedTemplate]]; 67 | [content addObject:[NSImage imageNamed:NSImageNameLockUnlockedTemplate]]; 68 | [content addObject:[NSImage imageNamed:NSImageNameGoRightTemplate]]; 69 | [content addObject:[NSImage imageNamed:NSImageNameGoLeftTemplate]]; 70 | [content addObject:[NSImage imageNamed:NSImageNameRightFacingTriangleTemplate]]; 71 | [content addObject:[NSImage imageNamed:NSImageNameLeftFacingTriangleTemplate]]; 72 | [content addObject:[NSImage imageNamed:NSImageNameAddTemplate]]; 73 | [content addObject:[NSImage imageNamed:NSImageNameRemoveTemplate]]; 74 | [content addObject:[NSImage imageNamed:NSImageNameRevealFreestandingTemplate]]; 75 | [content addObject:[NSImage imageNamed:NSImageNameFollowLinkFreestandingTemplate]]; 76 | [content addObject:[NSImage imageNamed:NSImageNameEnterFullScreenTemplate]]; 77 | [content addObject:[NSImage imageNamed:NSImageNameExitFullScreenTemplate]]; 78 | [content addObject:[NSImage imageNamed:NSImageNameStopProgressTemplate]]; 79 | [content addObject:[NSImage imageNamed:NSImageNameStopProgressFreestandingTemplate]]; 80 | [content addObject:[NSImage imageNamed:NSImageNameRefreshTemplate]]; 81 | [content addObject:[NSImage imageNamed:NSImageNameRefreshFreestandingTemplate]]; 82 | [content addObject:[NSImage imageNamed:NSImageNameBonjour]]; 83 | [content addObject:[NSImage imageNamed:NSImageNameComputer]]; 84 | [content addObject:[NSImage imageNamed:NSImageNameFolderBurnable]]; 85 | [content addObject:[NSImage imageNamed:NSImageNameFolderSmart]]; 86 | [content addObject:[NSImage imageNamed:NSImageNameFolder]]; 87 | [content addObject:[NSImage imageNamed:NSImageNameNetwork]]; 88 | [content addObject:[NSImage imageNamed:NSImageNameDotMac]]; 89 | [content addObject:[NSImage imageNamed:NSImageNameMobileMe]]; 90 | [content addObject:[NSImage imageNamed:NSImageNameMultipleDocuments]]; 91 | [content addObject:[NSImage imageNamed:NSImageNameUserAccounts]]; 92 | [content addObject:[NSImage imageNamed:NSImageNamePreferencesGeneral]]; 93 | [content addObject:[NSImage imageNamed:NSImageNameAdvanced]]; 94 | [content addObject:[NSImage imageNamed:NSImageNameInfo]]; 95 | [content addObject:[NSImage imageNamed:NSImageNameFontPanel]]; 96 | [content addObject:[NSImage imageNamed:NSImageNameColorPanel]]; 97 | [content addObject:[NSImage imageNamed:NSImageNameUser]]; 98 | [content addObject:[NSImage imageNamed:NSImageNameUserGroup]]; 99 | [content addObject:[NSImage imageNamed:NSImageNameEveryone]]; 100 | [content addObject:[NSImage imageNamed:NSImageNameUserGuest]]; 101 | [content addObject:[NSImage imageNamed:NSImageNameMenuOnStateTemplate]]; 102 | [content addObject:[NSImage imageNamed:NSImageNameMenuMixedStateTemplate]]; 103 | [content addObject:[NSImage imageNamed:NSImageNameApplicationIcon]]; 104 | [content addObject:[NSImage imageNamed:NSImageNameTrashEmpty]]; 105 | [content addObject:[NSImage imageNamed:NSImageNameTrashFull]]; 106 | [content addObject:[NSImage imageNamed:NSImageNameHomeTemplate]]; 107 | [content addObject:[NSImage imageNamed:NSImageNameBookmarksTemplate]]; 108 | [content addObject:[NSImage imageNamed:NSImageNameCaution]]; 109 | [content addObject:[NSImage imageNamed:NSImageNameStatusAvailable]]; 110 | [content addObject:[NSImage imageNamed:NSImageNameStatusPartiallyAvailable]]; 111 | [content addObject:[NSImage imageNamed:NSImageNameStatusUnavailable]]; 112 | [content addObject:[NSImage imageNamed:NSImageNameStatusNone]]; 113 | } 114 | 115 | [collectionView reloadData]; 116 | [collectionView setCellSize:NSMakeSize(64.0, 64.0)]; 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /JUCollectionView/JUCollectionView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.mycompany.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2011 __MyCompanyName__. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /JUCollectionView/JUCollectionView-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'JUCollectionView' target in the 'JUCollectionView' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /JUCollectionView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import 19 | 20 | int main(int argc, char *argv[]) 21 | { 22 | return NSApplicationMain(argc, (const char **)argv); 23 | } 24 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ##Overview 2 | JUCollectionView aims to be a drop in replacement for the extremely slow NSCollectionView. Instead of loading every possible cell at once, JUCollectionView only displays the visible cells. To improve the performance even further, JUCollectionView also reuses cells where possible. This means that it only has to load a batch of cells to cover the view and then reuse them its whole lifetime. 3 | 4 | Unlike NSCollectionView, JUCollectionView doesn't use NIB instantiating but a approach similar to UITableView on iOS. It asks its data source for a cell (a subclass of JUCollectionViewCell). It also provides identifier and identifier based dequeuing of cells, allowing you to display various kinds of cells while still having the benefit of reusable cells. 5 | 6 | ##Features 7 | JUCollectionView is currently at its very beginning, it features simple mouse selection (no multiple selection at the moment) and a very primitive cell class which can display an NSImage and draw a selection (although you can extend that easily). It can display a few hundred thousand cells without lags on an old 2007 MacBook Pro (try this with an NSCollectionView) and can also be used as a tile map renderer by fixed the number of columns and rows. 8 | 9 | ##License 10 | JUCollectionView is under the MIT license so basically: Do whatever you want to do with it! This also includes forking it and adding new features ;) -------------------------------------------------------------------------------- /Source/JUCollectionView+Selection.h: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionView+Selection.h 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import "JUCollectionView.h" 19 | 20 | /** 21 | * Category to allow selection of cells with the mouse and keyboard. Currently no multiple selection supported. 22 | **/ 23 | @interface JUCollectionView (JUCollectionView_Selection) 24 | 25 | - (void)mouseDown:(NSEvent *)event; 26 | - (void)mouseDragged:(NSEvent *)event; 27 | - (void)mouseUp:(NSEvent *)event; 28 | 29 | - (void)keyDown:(NSEvent *)event; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Source/JUCollectionView+Selection.m: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionView+Selection.m 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import "JUCollectionView+Selection.h" 19 | 20 | @implementation JUCollectionView (JUCollectionView_Selection) 21 | 22 | - (void)mouseDown:(NSEvent *)event 23 | { 24 | [[self window] makeFirstResponder:self]; 25 | 26 | NSUInteger index; 27 | NSPoint mousePoint = [self convertPoint:[event locationInWindow] fromView:nil]; 28 | index = [self indexOfCellAtPoint:mousePoint]; 29 | 30 | [self selectCellAtIndex:index]; 31 | } 32 | 33 | - (void)mouseDragged:(NSEvent *)event 34 | { 35 | NSUInteger index; 36 | NSPoint mousePoint = [self convertPoint:[event locationInWindow] fromView:nil]; 37 | index = [self indexOfCellAtPoint:mousePoint]; 38 | 39 | [self selectCellAtIndex:index]; 40 | [self autoscroll:event]; 41 | } 42 | 43 | - (void)mouseMoved:(NSEvent *)event 44 | { 45 | NSUInteger index; 46 | NSPoint mousePoint = [self convertPoint:[event locationInWindow] fromView:nil]; 47 | index = [self indexOfCellAtPoint:mousePoint]; 48 | 49 | [self hoverOverCellAtIndex:index]; 50 | } 51 | 52 | - (void)mouseExited:(NSEvent *)theEvent 53 | { 54 | [self hoverOutOfLastCell]; 55 | } 56 | 57 | - (void)mouseUp:(NSEvent *)event 58 | { 59 | NSUInteger index; 60 | NSPoint mousePoint = [self convertPoint:[event locationInWindow] fromView:nil]; 61 | index = [self indexOfCellAtPoint:mousePoint]; 62 | 63 | [self selectCellAtIndex:index]; 64 | 65 | if(unselectOnMouseUp) 66 | [self deselectAllCells]; 67 | 68 | lastSelection = [NSDate timeIntervalSinceReferenceDate]; 69 | } 70 | 71 | - (void)keyDown:(NSEvent *)event 72 | { 73 | if([[self selection] count] == 0) 74 | return; 75 | 76 | NSUInteger index = [[self selection] firstIndex]; 77 | BOOL isSelectionEvent = NO; 78 | 79 | switch([event keyCode]) 80 | { 81 | case 123: // Left 82 | index --; 83 | isSelectionEvent = YES; 84 | break; 85 | 86 | case 124: // Right 87 | index ++; 88 | isSelectionEvent = YES; 89 | break; 90 | 91 | case 125: // Down 92 | { 93 | NSPoint point = [self positionOfCellAtIndex:index]; 94 | point.y += 1; 95 | 96 | index = [self indexOfCellAtPosition:point]; 97 | isSelectionEvent = YES; 98 | break; 99 | } 100 | 101 | case 126: // Up 102 | { 103 | NSPoint point = [self positionOfCellAtIndex:index]; 104 | point.y -= 1; 105 | 106 | index = [self indexOfCellAtPosition:point]; 107 | isSelectionEvent = YES; 108 | break; 109 | } 110 | 111 | default: 112 | break; 113 | } 114 | 115 | if(isSelectionEvent) 116 | { 117 | [self deselectAllCells]; 118 | [self selectCellAtIndex:index]; 119 | 120 | return; 121 | } 122 | 123 | BOOL delegateImplements = [delegate respondsToSelector:@selector(collectionView:keyEvent:forCellAtIndex:)]; 124 | if(!delegateImplements) 125 | return; 126 | 127 | [[self selection] enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop){ 128 | [delegate collectionView:self keyEvent:event forCellAtIndex:index]; 129 | }]; 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /Source/JUCollectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionView.h 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import 19 | #import "JUCollectionViewCell.h" 20 | #import "JUCollectionViewDelegate.h" 21 | 22 | /** 23 | * View for displaying items in a grid like order. 24 | * The collection view is designed to be inside of an NSScrollView instance! The JUCollectionView class behaves much like UITableView on iOS allowing it 25 | * to have hundreds of thousands cells without great performance impact (unlike NSCollectionView). 26 | **/ 27 | @interface JUCollectionView : NSView 28 | { 29 | @public 30 | BOOL allowsSelection, allowsMultipleSelection; 31 | 32 | @private 33 | id dataSource; 34 | id delegate; 35 | 36 | NSUInteger desiredNumberOfColumns, desiredNumberOfRows; 37 | NSUInteger numberOfColumns, numberOfRows; 38 | NSUInteger numberOfCells; 39 | NSSize cellSize; 40 | NSUInteger lastHoverCellIndex; 41 | 42 | NSMutableDictionary *reusableCellQueues; 43 | NSMutableDictionary *visibleCells; 44 | 45 | NSMutableIndexSet *selection; 46 | NSTimeInterval lastSelection, lastDoubleClick; 47 | 48 | BOOL unselectOnMouseUp; 49 | BOOL updatingData, calledReloadData; 50 | } 51 | 52 | /** 53 | * The size of one cell. Each cell shares the very same size. 54 | **/ 55 | @property (nonatomic, assign) NSSize cellSize; 56 | /** 57 | * The number of columns the collection view should use, or NSUIntegerMax to let the collection view decide what column number fits best. 58 | **/ 59 | @property (nonatomic, assign) NSUInteger desiredNumberOfColumns; 60 | /** 61 | * The number of rows the collection view should use, or NSUIntegerMax to let the collection view use an dynmaic row number. 62 | * @remark If you set desiredNumberOfRows to a fixed value, the collection view might not show all cells but only those who fit into the desired rows. 63 | **/ 64 | @property (nonatomic, assign) NSUInteger desiredNumberOfRows; 65 | /** 66 | * The currently selected cells. 67 | **/ 68 | @property (nonatomic, readonly) NSIndexSet *selection; 69 | 70 | /** 71 | * The data source of the collection view. 72 | **/ 73 | @property (nonatomic, assign) IBOutlet id dataSource; 74 | /** 75 | * The delegate of the collection view. 76 | **/ 77 | @property (nonatomic, assign) IBOutlet id delegate; 78 | 79 | /** 80 | * YES if the collection view should allow selection, otherwise NO. The default value is YES. 81 | **/ 82 | @property (nonatomic, assign) BOOL allowsSelection; 83 | /** 84 | * YES if the collection view should allow the selection of multiple cells at the same time, otherwise NO. The default value is NO. 85 | **/ 86 | @property (nonatomic, assign) BOOL allowsMultipleSelection; 87 | /** 88 | * YES if the collection view should deselect the currently selected cell when the mouse button is released. The default value is NO. 89 | **/ 90 | @property (nonatomic, assign) BOOL unselectOnMouseUp; 91 | 92 | /** 93 | * Returns a queued cell or nil if no cell is currently in the queue. Use this if possible instead of creating new JUCollectionViewCell instances. 94 | **/ 95 | - (JUCollectionViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; 96 | /** 97 | * Returns the cell at the given index. 98 | * @remark The cell must be visible, otherwise the method will return nil. 99 | **/ 100 | - (JUCollectionViewCell *)cellAtIndex:(NSUInteger)index; 101 | 102 | /** 103 | * Forces the collection view to throw away all cached data including cells and selections and then reloading the data from scratch. 104 | **/ 105 | - (void)reloadData; 106 | 107 | /** 108 | * Selects the cell with the given index. 109 | **/ 110 | - (void)selectCellAtIndex:(NSUInteger)index; 111 | /** 112 | * Selects all cells of the index set, or, if allowsMultipleSelection is set to NO, selectes the cell at the first index of the set. 113 | **/ 114 | - (void)selectCellsAtIndexes:(NSIndexSet *)indexSet; 115 | 116 | /** 117 | * Deselects the cell at the given index. 118 | **/ 119 | - (void)deselectCellAtIndex:(NSUInteger)index; 120 | /** 121 | * Deselcts all cells of the index set. 122 | **/ 123 | - (void)deselectCellsAtIndexes:(NSIndexSet *)indexSet; 124 | /** 125 | * Deselects all previously selected cells. 126 | **/ 127 | - (void)deselectAllCells; 128 | 129 | /** 130 | * The mouse is hovering over the given cell. 131 | **/ 132 | - (void)hoverOverCellAtIndex:(NSUInteger)index; 133 | /** 134 | * The mouse was hovering over the given cell and is now out of the cell area. 135 | **/ 136 | - (void)hoverOutOfCellAtIndex:(NSUInteger)index; 137 | /** 138 | * Hover out of the most recent cell that the mouse was hovering over. 139 | **/ 140 | - (void)hoverOutOfLastCell; 141 | 142 | /** 143 | * Returns the index of the cell at the given point. 144 | **/ 145 | - (NSUInteger)indexOfCellAtPoint:(NSPoint)point; 146 | /** 147 | * Returns the index of the cell at the given position. 148 | **/ 149 | - (NSUInteger)indexOfCellAtPosition:(NSPoint)point; 150 | /** 151 | * Returns the position of the cell at the given index. For example the top left cell has the point 0|0 while the one on the right side of it has 1|0 etc. 152 | **/ 153 | - (NSPoint)positionOfCellAtIndex:(NSUInteger)index; 154 | /** 155 | * Returns the frame of the cell at the given index. 156 | **/ 157 | - (NSRect)rectForCellAtIndex:(NSInteger)index; 158 | /** 159 | * Returns a set of indexes that are inside the rect. 160 | **/ 161 | - (NSIndexSet *)indexesOfCellsInRect:(NSRect)rect; 162 | 163 | /** 164 | * Returns the currently visible index range. 165 | **/ 166 | - (NSRange)visibleRange; 167 | 168 | /** 169 | * Begins a block of changes. The collection view will only update its data and appereance when you call commitChanges. 170 | * @remark A call to reloadData will also be delayed until a commitChanges call. 171 | **/ 172 | - (void)beginChanges; 173 | /** 174 | * Updates the collection views data and appereance according to the previously made changes. 175 | * @remark Use this and beginChanges if you want to update multiple properties of the collection view in one batch call to save performance. 176 | **/ 177 | - (void)commitChanges; 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /Source/JUCollectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionView.m 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import "JUCollectionView.h" 19 | 20 | @interface JUCollectionViewCell () 21 | @property (nonatomic, assign) NSInteger index; 22 | @end 23 | 24 | @interface JUCollectionView () 25 | - (void)updateLayout; 26 | @end 27 | 28 | @implementation JUCollectionView 29 | @synthesize cellSize, desiredNumberOfColumns, desiredNumberOfRows; 30 | @synthesize dataSource, delegate; 31 | @synthesize unselectOnMouseUp, allowsSelection, allowsMultipleSelection; 32 | 33 | #pragma mark - 34 | #pragma mark Selection 35 | 36 | - (NSIndexSet *)selection 37 | { 38 | return [[selection copy] autorelease]; 39 | } 40 | 41 | - (void)selectCellAtIndex:(NSUInteger)index 42 | { 43 | if(!allowsSelection || index == NSNotFound) 44 | return; 45 | 46 | [self selectCellsAtIndexes:[NSIndexSet indexSetWithIndex:index]]; 47 | } 48 | 49 | - (void)selectCellsAtIndexes:(NSIndexSet *)indexSet 50 | { 51 | if(!allowsSelection) 52 | return; 53 | 54 | NSIndexSet *oldSelection = [[selection copy] autorelease]; 55 | 56 | if(allowsMultipleSelection) 57 | { 58 | [self deselectAllCells]; 59 | [selection addIndexes:indexSet]; 60 | } 61 | else 62 | { 63 | [self deselectAllCells]; 64 | [selection addIndex:[indexSet firstIndex]]; 65 | } 66 | 67 | BOOL delegateSingleClick = [delegate respondsToSelector:@selector(collectionView:didSelectCellAtIndex:)]; 68 | BOOL delegateDoubleClick = [delegate respondsToSelector:@selector(collectionView:didDoubleClickedCellAtIndex:)]; 69 | 70 | [selection enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { 71 | JUCollectionViewCell *cell = [visibleCells objectForKey:[NSNumber numberWithUnsignedInteger:index]]; 72 | [cell setSelected:YES]; 73 | 74 | if(delegateSingleClick && ![oldSelection containsIndex:index]) 75 | { 76 | [delegate collectionView:self didSelectCellAtIndex:index]; 77 | } 78 | else if(delegateDoubleClick && [oldSelection containsIndex:index]) 79 | { 80 | if([NSDate timeIntervalSinceReferenceDate] - lastSelection <= [NSEvent doubleClickInterval]) 81 | { 82 | if([NSDate timeIntervalSinceReferenceDate] - lastDoubleClick <= [NSEvent doubleClickInterval]) 83 | return; 84 | 85 | [delegate collectionView:self didDoubleClickedCellAtIndex:index]; 86 | lastDoubleClick = [NSDate timeIntervalSinceReferenceDate]; 87 | } 88 | } 89 | }]; 90 | } 91 | 92 | - (void)deselectCellAtIndex:(NSUInteger)index 93 | { 94 | if(index == NSNotFound) 95 | return; 96 | 97 | [self deselectCellsAtIndexes:[NSIndexSet indexSetWithIndex:index]]; 98 | } 99 | 100 | - (void)deselectCellsAtIndexes:(NSIndexSet *)indexSet 101 | { 102 | NSIndexSet *oldSelection = [[selection copy] autorelease]; 103 | [selection removeIndexes:indexSet]; 104 | 105 | BOOL implementsSelector = [delegate respondsToSelector:@selector(collectionView:didDeselectCellAtIndex:)]; 106 | 107 | [indexSet enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { 108 | JUCollectionViewCell *cell = [visibleCells objectForKey:[NSNumber numberWithUnsignedInteger:index]]; 109 | [cell setSelected:NO]; 110 | 111 | if(implementsSelector && [oldSelection containsIndex:index]) 112 | [delegate collectionView:self didDeselectCellAtIndex:index]; 113 | }]; 114 | } 115 | 116 | - (void)deselectAllCells 117 | { 118 | NSIndexSet *selectionCopy = [[selection copy] autorelease]; 119 | [self deselectCellsAtIndexes:selectionCopy]; 120 | } 121 | 122 | - (void)hoverOverCellAtIndex:(NSUInteger)index 123 | { 124 | if (lastHoverCellIndex != index) 125 | { 126 | // un-hover the previous cell 127 | [self hoverOutOfCellAtIndex:lastHoverCellIndex]; 128 | 129 | // hover over current cell 130 | JUCollectionViewCell *cell = [visibleCells objectForKey:[NSNumber numberWithUnsignedInteger:index]]; 131 | [cell setHovering:YES]; 132 | lastHoverCellIndex = index; 133 | } 134 | } 135 | 136 | - (void)hoverOutOfCellAtIndex:(NSUInteger)index 137 | { 138 | JUCollectionViewCell *cell = [visibleCells objectForKey:[NSNumber numberWithUnsignedInteger:index]]; 139 | [cell setHovering:NO]; 140 | } 141 | 142 | - (void)hoverOutOfLastCell 143 | { 144 | JUCollectionViewCell *cell = [visibleCells objectForKey:[NSNumber numberWithUnsignedInteger:lastHoverCellIndex]]; 145 | [cell setHovering:NO]; 146 | } 147 | 148 | - (NSUInteger)indexOfCellAtPoint:(NSPoint)point 149 | { 150 | NSSize boundsSize = [self bounds].size; 151 | if(point.x < 0.0 || point.y < 0.0 || point.x >= boundsSize.width || point.y >= boundsSize.height) 152 | return NSNotFound; 153 | 154 | point = NSMakePoint(floor(point.x / cellSize.width), floor(point.y / cellSize.height)); 155 | NSUInteger index = (point.y * numberOfColumns) + point.x; 156 | 157 | return index; 158 | } 159 | 160 | - (NSUInteger)indexOfCellAtPosition:(NSPoint)point 161 | { 162 | if(point.x < 0.0 || point.y < 0.0 || point.x >= numberOfColumns || point.y >= numberOfRows) 163 | return NSNotFound; 164 | 165 | point = NSMakePoint(floor(point.x), floor(point.y)); 166 | NSUInteger index = (point.y * numberOfColumns) + point.x; 167 | 168 | if(index >= numberOfCells) 169 | return NSNotFound; 170 | 171 | return index; 172 | } 173 | 174 | - (NSPoint)positionOfCellAtIndex:(NSUInteger)index 175 | { 176 | if(index >= numberOfCells || index == NSNotFound) 177 | return NSZeroPoint; 178 | 179 | NSUInteger x = index % numberOfColumns; 180 | NSUInteger y = (index - x) / numberOfColumns; 181 | 182 | return NSMakePoint(x, y); 183 | } 184 | 185 | - (NSRect)rectForCellAtIndex:(NSInteger)index 186 | { 187 | if(index >= numberOfCells || index == NSNotFound) 188 | return NSZeroRect; 189 | 190 | NSUInteger x = index % numberOfColumns; 191 | NSUInteger y = (index - x) / numberOfColumns; 192 | 193 | return NSMakeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height); 194 | } 195 | 196 | - (NSIndexSet *)indexesOfCellsInRect:(NSRect)rect 197 | { 198 | NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; 199 | for(JUCollectionViewCell *cell in [visibleCells allValues]) 200 | { 201 | if(NSIntersectsRect([cell frame], rect)) 202 | [indexSet addIndex:[cell index]]; 203 | } 204 | 205 | return indexSet; 206 | } 207 | 208 | #pragma mark - 209 | #pragma mark Cells 210 | 211 | - (JUCollectionViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier 212 | { 213 | NSMutableArray *queue = [reusableCellQueues objectForKey:identifier]; 214 | if([queue count] > 0) 215 | { 216 | JUCollectionViewCell *cell = [[queue lastObject] retain]; 217 | [queue removeLastObject]; 218 | 219 | [cell prepareForReuse]; 220 | return [cell autorelease]; 221 | } 222 | 223 | return nil; 224 | } 225 | 226 | - (JUCollectionViewCell *)cellAtIndex:(NSUInteger)index 227 | { 228 | return [visibleCells objectForKey:[NSNumber numberWithUnsignedInteger:index]]; 229 | } 230 | 231 | - (void)queueCell:(JUCollectionViewCell *)cell 232 | { 233 | [visibleCells removeObjectForKey:[NSNumber numberWithUnsignedInteger:cell.index]]; 234 | 235 | [cell removeFromSuperview]; 236 | [cell setIndex:-1]; 237 | 238 | if([reusableCellQueues objectForKey:cell.cellIdentifier]) 239 | { 240 | [[reusableCellQueues objectForKey:cell.cellIdentifier] addObject:cell]; 241 | } 242 | else 243 | { 244 | NSMutableArray *array = [NSMutableArray arrayWithObject:cell]; 245 | [reusableCellQueues setObject:array forKey:cell.cellIdentifier]; 246 | } 247 | } 248 | 249 | 250 | 251 | - (void)reorderCellsAnimated:(BOOL)animated 252 | { 253 | if(animated) 254 | { 255 | [NSAnimationContext beginGrouping]; 256 | [[NSAnimationContext currentContext] setDuration:0.1]; 257 | } 258 | 259 | for(JUCollectionViewCell *cell in [visibleCells allValues]) 260 | { 261 | NSRect rect = [self rectForCellAtIndex:[cell index]]; 262 | 263 | if(animated) 264 | [[cell animator] setFrame:rect]; 265 | else 266 | [cell setFrame:rect]; 267 | } 268 | 269 | if(animated) 270 | [NSAnimationContext endGrouping]; 271 | } 272 | 273 | - (void)removeInvisibleCells 274 | { 275 | NSRange range = [self visibleRange]; 276 | NSArray *cells = [visibleCells allValues]; 277 | 278 | for(JUCollectionViewCell *cell in cells) 279 | { 280 | if(!NSLocationInRange([cell index], range)) 281 | { 282 | [self queueCell:cell]; 283 | } 284 | } 285 | } 286 | 287 | - (void)addMissingCells 288 | { 289 | NSRange range = [self visibleRange]; 290 | NSMutableIndexSet *missingIndicies = [NSMutableIndexSet indexSetWithIndexesInRange:range]; 291 | 292 | for(JUCollectionViewCell *cell in [visibleCells allValues]) 293 | { 294 | if(NSLocationInRange([cell index], range)) 295 | { 296 | [missingIndicies removeIndex:[cell index]]; 297 | } 298 | } 299 | 300 | [missingIndicies enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop){ 301 | JUCollectionViewCell *cell = [dataSource collectionView:self cellForIndex:index]; 302 | 303 | if(!cell) 304 | return; 305 | 306 | if([selection containsIndex:index]) 307 | [cell setSelected:YES]; 308 | 309 | [cell setIndex:index]; 310 | [cell setFrame:[self rectForCellAtIndex:index]]; 311 | 312 | [self addSubview:cell]; 313 | [visibleCells setObject:cell forKey:[NSNumber numberWithUnsignedInteger:index]]; 314 | }]; 315 | } 316 | 317 | - (void)reloadData 318 | { 319 | if(updatingData) 320 | { 321 | calledReloadData = YES; 322 | return; 323 | } 324 | 325 | for(NSView *view in [visibleCells allValues]) 326 | [view removeFromSuperview]; 327 | 328 | [visibleCells removeAllObjects]; 329 | [reusableCellQueues removeAllObjects]; 330 | [selection removeAllIndexes]; 331 | 332 | numberOfCells = [dataSource numberOfCellsInCollectionView:self]; 333 | 334 | [self updateLayout]; 335 | [self addMissingCells]; 336 | } 337 | 338 | #pragma mark - 339 | #pragma mark Row and Column handling 340 | 341 | - (void)scrollViewDidScroll:(NSNotification *)notification 342 | { 343 | [self removeInvisibleCells]; 344 | [self addMissingCells]; 345 | [self setNeedsDisplay:YES]; 346 | } 347 | 348 | - (NSRange)visibleRange 349 | { 350 | NSRect rect = [self visibleRect]; 351 | rect.origin.y -= cellSize.height; 352 | rect.size.height += (cellSize.height * 2); 353 | 354 | if(rect.origin.y < 0.0) 355 | rect.origin.y = 0.0; 356 | 357 | NSInteger rows = rect.origin.y / cellSize.height; 358 | NSInteger startIndex = rows * numberOfColumns; 359 | NSInteger endIndex = 0; 360 | 361 | rows = (rect.origin.y + rect.size.height) / cellSize.height; 362 | endIndex = rows * numberOfColumns; 363 | endIndex = MIN(numberOfCells, endIndex); 364 | 365 | return NSMakeRange(startIndex, endIndex-startIndex); 366 | } 367 | 368 | - (void)updateLayout 369 | { 370 | if(updatingData) 371 | return; 372 | 373 | NSRect frame = [self frame]; 374 | CGFloat width, height; 375 | 376 | // Calculate new boundaries for the view... 377 | if(desiredNumberOfColumns == NSUIntegerMax) 378 | { 379 | numberOfColumns = floorf((float)frame.size.width / cellSize.width); 380 | width = frame.size.width; 381 | } 382 | else 383 | { 384 | numberOfColumns = desiredNumberOfColumns; 385 | width = numberOfColumns * cellSize.width; 386 | } 387 | 388 | 389 | if(desiredNumberOfRows == NSUIntegerMax && numberOfColumns > 0) 390 | { 391 | numberOfRows = ceilf((float)numberOfCells / numberOfColumns); 392 | height = numberOfRows * cellSize.height; 393 | } 394 | else 395 | { 396 | numberOfRows = desiredNumberOfRows; 397 | height = numberOfRows * cellSize.height; 398 | } 399 | 400 | 401 | frame.size.width = width; 402 | frame.size.height = height; 403 | 404 | 405 | 406 | // Update the frame and then all cells 407 | [super setFrame:frame]; 408 | 409 | [self reorderCellsAnimated:YES]; 410 | [self removeInvisibleCells]; 411 | [self addMissingCells]; 412 | } 413 | 414 | - (void)setFrame:(NSRect)frameRect 415 | { 416 | [super setFrame:frameRect]; 417 | [self updateLayout]; 418 | } 419 | 420 | - (void)setCellSize:(NSSize)newCellSize 421 | { 422 | cellSize = newCellSize; 423 | [self updateLayout]; 424 | } 425 | 426 | - (void)setDesiredNumberOfColumns:(NSUInteger)newDesiredNumberOfColumns 427 | { 428 | desiredNumberOfColumns = newDesiredNumberOfColumns; 429 | [self updateLayout]; 430 | } 431 | 432 | - (void)setDesiredNumberOfRows:(NSUInteger)newDesiredNumberOfRows 433 | { 434 | desiredNumberOfRows = newDesiredNumberOfRows; 435 | [self updateLayout]; 436 | } 437 | 438 | - (void)beginChanges 439 | { 440 | if(updatingData) 441 | return; 442 | 443 | updatingData = YES; 444 | } 445 | 446 | - (void)commitChanges 447 | { 448 | updatingData = NO; 449 | 450 | if(calledReloadData) 451 | { 452 | [self reloadData]; 453 | } 454 | else 455 | { 456 | [self updateLayout]; 457 | } 458 | 459 | calledReloadData = NO; 460 | } 461 | 462 | #pragma mark - 463 | #pragma mark Constructor / Destructor 464 | 465 | - (void)setupCollectionView 466 | { 467 | desiredNumberOfColumns = NSUIntegerMax; 468 | desiredNumberOfRows = NSUIntegerMax; 469 | 470 | reusableCellQueues = [[NSMutableDictionary alloc] init]; 471 | visibleCells = [[NSMutableDictionary alloc] init]; 472 | 473 | selection = [[NSMutableIndexSet alloc] init]; 474 | allowsSelection = YES; 475 | lastHoverCellIndex = -1; 476 | 477 | cellSize = NSMakeSize(32.0, 32.0); 478 | 479 | NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:[self frame] 480 | options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect) 481 | owner:self 482 | userInfo:nil]; 483 | [self addTrackingArea:area]; 484 | [area release]; 485 | 486 | NSClipView *clipView = [[self enclosingScrollView] contentView]; 487 | [clipView setPostsBoundsChangedNotifications:YES]; 488 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollViewDidScroll:) name:NSViewBoundsDidChangeNotification object:clipView]; 489 | 490 | [self updateLayout]; 491 | [self reloadData]; 492 | } 493 | 494 | 495 | - (BOOL)isFlipped 496 | { 497 | return YES; 498 | } 499 | 500 | 501 | - (id)initWithFrame:(NSRect)frame 502 | { 503 | if((self = [super initWithFrame:frame])) 504 | { 505 | [self setupCollectionView]; 506 | } 507 | 508 | return self; 509 | } 510 | 511 | - (id)initWithCoder:(NSCoder *)decoder 512 | { 513 | if((self = [super initWithCoder:decoder])) 514 | { 515 | [self setupCollectionView]; 516 | } 517 | 518 | return self; 519 | } 520 | 521 | - (void)dealloc 522 | { 523 | [reusableCellQueues release]; 524 | [visibleCells release]; 525 | [selection release]; 526 | 527 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 528 | [super dealloc]; 529 | } 530 | 531 | @end 532 | -------------------------------------------------------------------------------- /Source/JUCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionViewCell.h 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import 19 | 20 | /** 21 | * This class implements a single cell used by the JUCollectionView class. By default it will draw a selection ring if selected and a image, if provided. 22 | * You can subclass this class for custom cell drawing. 23 | **/ 24 | @interface JUCollectionViewCell : NSView 25 | { 26 | @protected 27 | NSImage *image; 28 | NSColor *selectionColor; 29 | BOOL selected; 30 | BOOL hovering; 31 | 32 | @private 33 | NSInteger index; 34 | NSString *cellIdentifier; 35 | 36 | BOOL drawSelection; 37 | } 38 | 39 | /** 40 | * The identifier of the cell. Used for cell reusing. 41 | **/ 42 | @property (nonatomic, readonly) NSString *cellIdentifier; 43 | /** 44 | * The image that should be drawn at the center of the cell, or nil if you don't want a image to be drawn. 45 | **/ 46 | @property (nonatomic, retain) NSImage *image; 47 | 48 | /** 49 | * The color of the selection ring. 50 | **/ 51 | @property (nonatomic, retain) NSColor *selectionColor; 52 | /** 53 | * YES if the cell should draw an selection ring. You can set this to NO if you don't want to show a selection ring or if you provide your own. 54 | * The default value is YES. 55 | **/ 56 | @property (nonatomic, assign) BOOL drawSelection; 57 | /** 58 | * YES if the cell is selected, otherwise NO. 59 | **/ 60 | @property (nonatomic, assign, getter=isSelected) BOOL selected; 61 | 62 | /** 63 | * YES if the mouse is hovering over cell, otherwise NO. 64 | **/ 65 | @property (nonatomic, assign, getter=isHovering) BOOL hovering; 66 | 67 | /** 68 | * Invoked when the cell is dequeued from a collection view. This will reset all settings to default. 69 | **/ 70 | - (void)prepareForReuse; 71 | 72 | /** 73 | * The designated initializer of the cell. Please don't use any other intializer! 74 | **/ 75 | - (id)initWithReuseIdentifier:(NSString *)identifer; 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Source/JUCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionViewCell.m 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | #import "JUCollectionViewCell.h" 19 | 20 | @interface JUCollectionViewCell () 21 | @property (nonatomic, assign) NSInteger index; 22 | @end 23 | 24 | @implementation JUCollectionViewCell 25 | @synthesize index, cellIdentifier, image; 26 | @synthesize selectionColor, selected, drawSelection; 27 | @synthesize hovering; 28 | 29 | - (void)drawRect:(NSRect)dirtyRect 30 | { 31 | if(image) 32 | { 33 | NSRect inRect = [self bounds]; 34 | NSRect srcRect = NSZeroRect; 35 | srcRect.size = [image size]; 36 | 37 | NSRect drawnRect = srcRect; 38 | if(drawnRect.size.width > inRect.size.width) 39 | { 40 | drawnRect.size.height *= inRect.size.width/drawnRect.size.width; 41 | drawnRect.size.width = inRect.size.width; 42 | } 43 | 44 | if(drawnRect.size.height > inRect.size.height) 45 | { 46 | drawnRect.size.width *= inRect.size.height/drawnRect.size.height; 47 | drawnRect.size.height = inRect.size.height; 48 | } 49 | 50 | drawnRect.origin = inRect.origin; 51 | drawnRect.origin.x += (inRect.size.width - drawnRect.size.width)/2; 52 | drawnRect.origin.y += (inRect.size.height - drawnRect.size.height)/2; 53 | 54 | [image drawInRect:drawnRect fromRect:srcRect operation:NSCompositeSourceAtop fraction:1.0]; 55 | } 56 | 57 | if(selected && drawSelection) 58 | { 59 | [selectionColor set]; 60 | 61 | NSBezierPath *path = [NSBezierPath bezierPathWithRect:[self bounds]]; 62 | [path setLineWidth:4.0]; 63 | [path stroke]; 64 | } 65 | } 66 | 67 | 68 | - (void)setImage:(NSImage *)newImage 69 | { 70 | [image autorelease]; 71 | image = [newImage retain]; 72 | 73 | [self setNeedsDisplay:YES]; 74 | } 75 | 76 | - (void)setSelected:(BOOL)state 77 | { 78 | selected = state; 79 | [self setNeedsDisplay:YES]; 80 | } 81 | 82 | - (void)setHovering:(BOOL)state 83 | { 84 | hovering = state; 85 | [self setNeedsDisplay:YES]; 86 | } 87 | 88 | - (void)prepareForReuse 89 | { 90 | selected = NO; 91 | drawSelection = YES; 92 | hovering = NO; 93 | self.image = nil; 94 | } 95 | 96 | 97 | - (id)initWithReuseIdentifier:(NSString *)identifier 98 | { 99 | if((self = [super initWithFrame:NSZeroRect])) 100 | { 101 | cellIdentifier = [identifier retain]; 102 | selectionColor = [[NSColor blackColor] retain]; 103 | drawSelection = YES; 104 | } 105 | 106 | return self; 107 | } 108 | 109 | - (void)dealloc 110 | { 111 | [image release]; 112 | [cellIdentifier release]; 113 | [selectionColor release]; 114 | 115 | [super dealloc]; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Source/JUCollectionViewDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // JUCollectionViewDelegate.h 3 | // JUCollectionView 4 | // 5 | // Copyright (c) 2011 by Sidney Just 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 7 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation 8 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 13 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 14 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | // 17 | 18 | @class JUCollectionView; 19 | 20 | /** 21 | * The data source protocol, used to gather the needed data for the collection view. 22 | **/ 23 | @protocol JUCollectionViewDataSource 24 | @required 25 | 26 | /** 27 | * This method is invoked to ask the data source for the number of cells inside the collection view. 28 | **/ 29 | - (NSUInteger)numberOfCellsInCollectionView:(JUCollectionView *)collectionView; 30 | /** 31 | * This method is involed to ask the data source for a cell to display at the given index. You should first try to dequeue an old cell before creating a new one! 32 | **/ 33 | - (JUCollectionViewCell *)collectionView:(JUCollectionView *)collectionView cellForIndex:(NSUInteger)index; 34 | 35 | @end 36 | 37 | /** 38 | * The collections views delegate protocol. 39 | **/ 40 | @protocol JUCollectionViewDelegate 41 | @optional 42 | /** 43 | * Invoked when the cell at the given index was selected. 44 | **/ 45 | - (void)collectionView:(JUCollectionView *)collectionView didSelectCellAtIndex:(NSUInteger)index; 46 | /** 47 | * Invoked when the user double clicked on the given cell. 48 | **/ 49 | - (void)collectionView:(JUCollectionView *)collectionView didDoubleClickedCellAtIndex:(NSUInteger)index; 50 | /** 51 | * Invoked when the cell at the given index was deselected. 52 | **/ 53 | - (void)collectionView:(JUCollectionView *)collectionView didDeselectCellAtIndex:(NSUInteger)index; 54 | /** 55 | * Invoked when there was an unhandled key event. The method will be invoked for every selected cell. 56 | * @remark Currently handled are the cursor keys. 57 | **/ 58 | - (void)collectionView:(JUCollectionView *)collectionView keyEvent:(NSEvent *)event forCellAtIndex:(NSUInteger)index; 59 | 60 | @end 61 | --------------------------------------------------------------------------------