├── Demo-Info.plist ├── DemoSectionView.h ├── DemoSectionView.m ├── DemoSectionView.xib ├── DemoView.h ├── DemoView.m ├── DemoView.xib ├── English.lproj ├── InfoPlist.strings └── MainMenu.xib ├── JAEdgeInsets.h ├── JAListView-Info.plist ├── JAListView.h ├── JAListView.m ├── JAListView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── JAListViewAppDelegate.h ├── JAListViewAppDelegate.m ├── JAListViewItem.h ├── JAListViewItem.m ├── JAListView_Prefix.pch ├── JAObjectListView.h ├── JAObjectListView.m ├── JAObjectListViewItem.h ├── JAObjectListViewItem.m ├── JASectionedListView.h ├── JASectionedListView.m ├── NSIndexPath+JAListViewExtensions.h ├── NSIndexPath+JAListViewExtensions.m ├── README └── main.m /Demo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.yourcompany.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /DemoSectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoSectionView.h 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 11/26/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JAListViewItem.h" 11 | 12 | 13 | @interface DemoSectionView : JAListViewItem {} 14 | 15 | + (DemoSectionView *)demoSectionView; 16 | 17 | @property (assign) IBOutlet NSTextField *textField; 18 | @property (assign) IBOutlet NSTextField *shadowTextField; 19 | @property (nonatomic, copy) NSString *text; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /DemoSectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoSectionView.m 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 11/26/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import "DemoSectionView.h" 10 | 11 | 12 | @implementation DemoSectionView 13 | 14 | + (DemoSectionView *)demoSectionView { 15 | static NSNib *nib = nil; 16 | if(nib == nil) { 17 | nib = [[NSNib alloc] initWithNibNamed:NSStringFromClass(self) bundle:nil]; 18 | } 19 | 20 | NSArray *objects = nil; 21 | [nib instantiateNibWithOwner:nil topLevelObjects:&objects]; 22 | for(id object in objects) { 23 | if([object isKindOfClass:self]) { 24 | return object; 25 | } 26 | } 27 | 28 | NSAssert1(NO, @"No view of class %@ found.", NSStringFromClass(self)); 29 | return nil; 30 | } 31 | 32 | 33 | #pragma mark NSView 34 | 35 | - (void)drawRect:(NSRect)dirtyRect { 36 | [super drawRect:dirtyRect]; 37 | 38 | [[NSColor lightGrayColor] set]; 39 | NSRectFill(dirtyRect); 40 | } 41 | 42 | 43 | #pragma mark API 44 | 45 | @synthesize textField; 46 | @synthesize shadowTextField; 47 | 48 | - (void)setText:(NSString *)text { 49 | [self.textField setStringValue:[[text copy] autorelease]]; 50 | [self.shadowTextField setStringValue:[[text copy] autorelease]]; 51 | } 52 | 53 | - (NSString *)text { 54 | return [self.textField stringValue]; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /DemoSectionView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1060 5 | 10H574 6 | 823 7 | 1038.35 8 | 461.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 823 12 | 13 | 14 | YES 15 | 16 | 17 | 18 | YES 19 | com.apple.InterfaceBuilder.CocoaPlugin 20 | 21 | 22 | YES 23 | 24 | YES 25 | 26 | 27 | YES 28 | 29 | 30 | 31 | YES 32 | 33 | NSObject 34 | 35 | 36 | FirstResponder 37 | 38 | 39 | NSApplication 40 | 41 | 42 | 43 | 268 44 | 45 | YES 46 | 47 | 48 | 298 49 | {{-3, 16}, {486, 17}} 50 | 51 | YES 52 | 53 | 68288064 54 | 138413056 55 | Section! 56 | 57 | Helvetica-Bold 58 | 13 59 | 16 60 | 61 | 62 | 63 | 6 64 | System 65 | controlColor 66 | 67 | 3 68 | MC42NjY2NjY2NjY3AA 69 | 70 | 71 | 72 | 6 73 | System 74 | controlTextColor 75 | 76 | 3 77 | MAA 78 | 79 | 80 | 81 | 82 | 83 | 84 | 298 85 | {{-3, 15}, {486, 17}} 86 | 87 | YES 88 | 89 | 68288064 90 | 138413056 91 | Section! 92 | 93 | 94 | 95 | 96 | 4 97 | MSAwLjcAA 98 | 99 | 100 | 101 | 102 | {480, 50} 103 | 104 | DemoSectionView 105 | 106 | 107 | 108 | 109 | YES 110 | 111 | 112 | textField 113 | 114 | 115 | 116 | 6 117 | 118 | 119 | 120 | shadowTextField 121 | 122 | 123 | 124 | 7 125 | 126 | 127 | 128 | 129 | YES 130 | 131 | 0 132 | 133 | 134 | 135 | 136 | 137 | -2 138 | 139 | 140 | File's Owner 141 | 142 | 143 | -1 144 | 145 | 146 | First Responder 147 | 148 | 149 | -3 150 | 151 | 152 | Application 153 | 154 | 155 | 1 156 | 157 | 158 | YES 159 | 160 | 161 | 162 | 163 | 164 | 165 | 2 166 | 167 | 168 | YES 169 | 170 | 171 | 172 | 173 | 174 | 3 175 | 176 | 177 | 178 | 179 | 4 180 | 181 | 182 | YES 183 | 184 | 185 | 186 | 187 | 188 | 5 189 | 190 | 191 | 192 | 193 | 194 | 195 | YES 196 | 197 | YES 198 | 1.IBEditorWindowLastContentRect 199 | 1.IBPluginDependency 200 | 1.WindowOrigin 201 | 1.editorWindowContentRectSynchronizationRect 202 | 2.IBPluginDependency 203 | 2.IBViewBoundsToFrameTransform 204 | 3.IBPluginDependency 205 | 4.IBPluginDependency 206 | 4.IBViewBoundsToFrameTransform 207 | 5.IBPluginDependency 208 | 209 | 210 | YES 211 | {{322, 806}, {480, 50}} 212 | com.apple.InterfaceBuilder.CocoaPlugin 213 | {628, 654} 214 | {{217, 442}, {480, 272}} 215 | com.apple.InterfaceBuilder.CocoaPlugin 216 | 217 | P4AAAL+AAADAQAAAwfgAAA 218 | 219 | com.apple.InterfaceBuilder.CocoaPlugin 220 | com.apple.InterfaceBuilder.CocoaPlugin 221 | 222 | P4AAAL+AAABDJAAAwVAAAA 223 | 224 | com.apple.InterfaceBuilder.CocoaPlugin 225 | 226 | 227 | 228 | YES 229 | 230 | 231 | YES 232 | 233 | 234 | 235 | 236 | YES 237 | 238 | 239 | YES 240 | 241 | 242 | 243 | 7 244 | 245 | 246 | 247 | YES 248 | 249 | DemoSectionView 250 | JAListViewItemView 251 | 252 | YES 253 | 254 | YES 255 | shadowTextField 256 | textField 257 | 258 | 259 | YES 260 | NSTextField 261 | NSTextField 262 | 263 | 264 | 265 | YES 266 | 267 | YES 268 | shadowTextField 269 | textField 270 | 271 | 272 | YES 273 | 274 | shadowTextField 275 | NSTextField 276 | 277 | 278 | textField 279 | NSTextField 280 | 281 | 282 | 283 | 284 | IBProjectSource 285 | DemoSectionView.h 286 | 287 | 288 | 289 | JAListViewItemView 290 | NSView 291 | 292 | IBProjectSource 293 | JAListViewItemView.h 294 | 295 | 296 | 297 | 298 | YES 299 | 300 | NSActionCell 301 | NSCell 302 | 303 | IBFrameworkSource 304 | AppKit.framework/Headers/NSActionCell.h 305 | 306 | 307 | 308 | NSApplication 309 | NSResponder 310 | 311 | IBFrameworkSource 312 | AppKit.framework/Headers/NSApplication.h 313 | 314 | 315 | 316 | NSApplication 317 | 318 | IBFrameworkSource 319 | AppKit.framework/Headers/NSApplicationScripting.h 320 | 321 | 322 | 323 | NSApplication 324 | 325 | IBFrameworkSource 326 | AppKit.framework/Headers/NSColorPanel.h 327 | 328 | 329 | 330 | NSApplication 331 | 332 | IBFrameworkSource 333 | AppKit.framework/Headers/NSHelpManager.h 334 | 335 | 336 | 337 | NSApplication 338 | 339 | IBFrameworkSource 340 | AppKit.framework/Headers/NSPageLayout.h 341 | 342 | 343 | 344 | NSApplication 345 | 346 | IBFrameworkSource 347 | AppKit.framework/Headers/NSUserInterfaceItemSearching.h 348 | 349 | 350 | 351 | NSCell 352 | NSObject 353 | 354 | IBFrameworkSource 355 | AppKit.framework/Headers/NSCell.h 356 | 357 | 358 | 359 | NSControl 360 | NSView 361 | 362 | IBFrameworkSource 363 | AppKit.framework/Headers/NSControl.h 364 | 365 | 366 | 367 | NSFormatter 368 | NSObject 369 | 370 | IBFrameworkSource 371 | Foundation.framework/Headers/NSFormatter.h 372 | 373 | 374 | 375 | NSMenu 376 | NSObject 377 | 378 | IBFrameworkSource 379 | AppKit.framework/Headers/NSMenu.h 380 | 381 | 382 | 383 | NSObject 384 | 385 | IBFrameworkSource 386 | AppKit.framework/Headers/NSAccessibility.h 387 | 388 | 389 | 390 | NSObject 391 | 392 | 393 | 394 | NSObject 395 | 396 | 397 | 398 | NSObject 399 | 400 | 401 | 402 | NSObject 403 | 404 | 405 | 406 | NSObject 407 | 408 | IBFrameworkSource 409 | AppKit.framework/Headers/NSDictionaryController.h 410 | 411 | 412 | 413 | NSObject 414 | 415 | IBFrameworkSource 416 | AppKit.framework/Headers/NSDragging.h 417 | 418 | 419 | 420 | NSObject 421 | 422 | IBFrameworkSource 423 | AppKit.framework/Headers/NSFontManager.h 424 | 425 | 426 | 427 | NSObject 428 | 429 | IBFrameworkSource 430 | AppKit.framework/Headers/NSFontPanel.h 431 | 432 | 433 | 434 | NSObject 435 | 436 | IBFrameworkSource 437 | AppKit.framework/Headers/NSKeyValueBinding.h 438 | 439 | 440 | 441 | NSObject 442 | 443 | 444 | 445 | NSObject 446 | 447 | IBFrameworkSource 448 | AppKit.framework/Headers/NSNibLoading.h 449 | 450 | 451 | 452 | NSObject 453 | 454 | IBFrameworkSource 455 | AppKit.framework/Headers/NSOutlineView.h 456 | 457 | 458 | 459 | NSObject 460 | 461 | IBFrameworkSource 462 | AppKit.framework/Headers/NSPasteboard.h 463 | 464 | 465 | 466 | NSObject 467 | 468 | IBFrameworkSource 469 | AppKit.framework/Headers/NSSavePanel.h 470 | 471 | 472 | 473 | NSObject 474 | 475 | IBFrameworkSource 476 | AppKit.framework/Headers/NSTableView.h 477 | 478 | 479 | 480 | NSObject 481 | 482 | IBFrameworkSource 483 | AppKit.framework/Headers/NSToolbarItem.h 484 | 485 | 486 | 487 | NSObject 488 | 489 | IBFrameworkSource 490 | AppKit.framework/Headers/NSView.h 491 | 492 | 493 | 494 | NSObject 495 | 496 | IBFrameworkSource 497 | Foundation.framework/Headers/NSArchiver.h 498 | 499 | 500 | 501 | NSObject 502 | 503 | IBFrameworkSource 504 | Foundation.framework/Headers/NSClassDescription.h 505 | 506 | 507 | 508 | NSObject 509 | 510 | IBFrameworkSource 511 | Foundation.framework/Headers/NSError.h 512 | 513 | 514 | 515 | NSObject 516 | 517 | IBFrameworkSource 518 | Foundation.framework/Headers/NSFileManager.h 519 | 520 | 521 | 522 | NSObject 523 | 524 | IBFrameworkSource 525 | Foundation.framework/Headers/NSKeyValueCoding.h 526 | 527 | 528 | 529 | NSObject 530 | 531 | IBFrameworkSource 532 | Foundation.framework/Headers/NSKeyValueObserving.h 533 | 534 | 535 | 536 | NSObject 537 | 538 | IBFrameworkSource 539 | Foundation.framework/Headers/NSKeyedArchiver.h 540 | 541 | 542 | 543 | NSObject 544 | 545 | IBFrameworkSource 546 | Foundation.framework/Headers/NSObject.h 547 | 548 | 549 | 550 | NSObject 551 | 552 | IBFrameworkSource 553 | Foundation.framework/Headers/NSObjectScripting.h 554 | 555 | 556 | 557 | NSObject 558 | 559 | IBFrameworkSource 560 | Foundation.framework/Headers/NSPortCoder.h 561 | 562 | 563 | 564 | NSObject 565 | 566 | IBFrameworkSource 567 | Foundation.framework/Headers/NSRunLoop.h 568 | 569 | 570 | 571 | NSObject 572 | 573 | IBFrameworkSource 574 | Foundation.framework/Headers/NSScriptClassDescription.h 575 | 576 | 577 | 578 | NSObject 579 | 580 | IBFrameworkSource 581 | Foundation.framework/Headers/NSScriptKeyValueCoding.h 582 | 583 | 584 | 585 | NSObject 586 | 587 | IBFrameworkSource 588 | Foundation.framework/Headers/NSScriptObjectSpecifiers.h 589 | 590 | 591 | 592 | NSObject 593 | 594 | IBFrameworkSource 595 | Foundation.framework/Headers/NSScriptWhoseTests.h 596 | 597 | 598 | 599 | NSObject 600 | 601 | IBFrameworkSource 602 | Foundation.framework/Headers/NSThread.h 603 | 604 | 605 | 606 | NSObject 607 | 608 | IBFrameworkSource 609 | Foundation.framework/Headers/NSURL.h 610 | 611 | 612 | 613 | NSObject 614 | 615 | IBFrameworkSource 616 | Foundation.framework/Headers/NSURLConnection.h 617 | 618 | 619 | 620 | NSObject 621 | 622 | IBFrameworkSource 623 | Foundation.framework/Headers/NSURLDownload.h 624 | 625 | 626 | 627 | NSResponder 628 | 629 | IBFrameworkSource 630 | AppKit.framework/Headers/NSInterfaceStyle.h 631 | 632 | 633 | 634 | NSResponder 635 | NSObject 636 | 637 | IBFrameworkSource 638 | AppKit.framework/Headers/NSResponder.h 639 | 640 | 641 | 642 | NSTextField 643 | NSControl 644 | 645 | IBFrameworkSource 646 | AppKit.framework/Headers/NSTextField.h 647 | 648 | 649 | 650 | NSTextFieldCell 651 | NSActionCell 652 | 653 | IBFrameworkSource 654 | AppKit.framework/Headers/NSTextFieldCell.h 655 | 656 | 657 | 658 | NSView 659 | 660 | IBFrameworkSource 661 | AppKit.framework/Headers/NSClipView.h 662 | 663 | 664 | 665 | NSView 666 | 667 | IBFrameworkSource 668 | AppKit.framework/Headers/NSMenuItem.h 669 | 670 | 671 | 672 | NSView 673 | 674 | IBFrameworkSource 675 | AppKit.framework/Headers/NSRulerView.h 676 | 677 | 678 | 679 | NSView 680 | NSResponder 681 | 682 | 683 | 684 | 685 | 0 686 | IBCocoaFramework 687 | 688 | com.apple.InterfaceBuilder.CocoaPlugin.macosx 689 | 690 | 691 | 692 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 693 | 694 | 695 | YES 696 | JAListView.xcodeproj 697 | 3 698 | 699 | 700 | -------------------------------------------------------------------------------- /DemoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoView.h 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 9/29/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JAListViewItem.h" 11 | 12 | 13 | @interface DemoView : JAListViewItem { 14 | NSGradient *gradient; 15 | BOOL selected; 16 | NSTextField *textField; 17 | NSTextField *shadowTextField; 18 | } 19 | 20 | + (DemoView *)demoView; 21 | 22 | @property (nonatomic, copy) NSString *text; 23 | @property (retain) IBOutlet NSTextField *textField; 24 | @property (retain) IBOutlet NSTextField *shadowTextField; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /DemoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoView.m 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 9/29/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import "DemoView.h" 10 | 11 | @interface DemoView () 12 | - (void)drawBackground; 13 | 14 | @property (nonatomic, readonly) NSGradient *gradient; 15 | @end 16 | 17 | 18 | @implementation DemoView 19 | 20 | + (DemoView *)demoView { 21 | static NSNib *nib = nil; 22 | if(nib == nil) { 23 | nib = [[NSNib alloc] initWithNibNamed:NSStringFromClass(self) bundle:nil]; 24 | } 25 | 26 | NSArray *objects = nil; 27 | [nib instantiateNibWithOwner:nil topLevelObjects:&objects]; 28 | for(id object in objects) { 29 | if([object isKindOfClass:self]) { 30 | return object; 31 | } 32 | } 33 | 34 | NSAssert1(NO, @"No view of class %@ found.", NSStringFromClass(self)); 35 | return nil; 36 | } 37 | 38 | 39 | #pragma mark NSView 40 | 41 | - (void)drawRect:(NSRect)rect { 42 | [super drawRect:rect]; 43 | [self drawBackground]; 44 | } 45 | 46 | 47 | #pragma mark API 48 | 49 | - (void)drawBackground { 50 | [self.gradient drawInRect:self.bounds angle:self.selected ? 270.0f : 90.0f]; 51 | 52 | [[NSColor colorWithDeviceWhite:0.5f alpha:1.0f] set]; 53 | NSRectFill(NSMakeRect(0.0f, 0.0f, self.bounds.size.width, 1.0f)); 54 | 55 | [[NSColor colorWithDeviceWhite:0.93f alpha:1.0f] set]; 56 | NSRectFill(NSMakeRect(0.0f, self.bounds.size.height - 1.0f, self.bounds.size.width, 1.0f)); 57 | } 58 | 59 | - (NSGradient *)gradient { 60 | if(gradient == nil) { 61 | gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithDeviceWhite:0.8f alpha:1.0f] endingColor:[NSColor colorWithDeviceWhite:0.85f alpha:1.0f]]; 62 | } 63 | 64 | return gradient; 65 | } 66 | 67 | - (void)setText:(NSString *)newText { 68 | NSString *newValue = [[newText copy] autorelease]; 69 | [self.textField setStringValue:newValue]; 70 | [self.shadowTextField setStringValue:newValue]; 71 | } 72 | 73 | - (NSString *)text { 74 | return [self.textField stringValue]; 75 | } 76 | 77 | - (void)setSelected:(BOOL)isSelected { 78 | selected = isSelected; 79 | 80 | [self setNeedsDisplay:YES]; 81 | } 82 | 83 | @synthesize selected; 84 | @synthesize textField; 85 | @synthesize shadowTextField; 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /DemoView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1060 5 | 10H574 6 | 823 7 | 1038.35 8 | 461.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 823 12 | 13 | 14 | YES 15 | 16 | 17 | 18 | YES 19 | com.apple.InterfaceBuilder.CocoaPlugin 20 | 21 | 22 | YES 23 | 24 | YES 25 | 26 | 27 | YES 28 | 29 | 30 | 31 | YES 32 | 33 | NSObject 34 | 35 | 36 | FirstResponder 37 | 38 | 39 | NSApplication 40 | 41 | 42 | 43 | 266 44 | 45 | YES 46 | 47 | 48 | 298 49 | {{17, 18}, {353, 17}} 50 | 51 | 2 52 | YES 53 | 54 | 67239488 55 | 272631808 56 | Label 57 | 58 | HelveticaNeue-Bold 59 | 14 60 | 16 61 | 62 | 63 | 64 | 6 65 | System 66 | controlColor 67 | 68 | 3 69 | MC42NjY2NjY2NjY3AA 70 | 71 | 72 | 73 | 6 74 | System 75 | controlTextColor 76 | 77 | 3 78 | MAA 79 | 80 | 81 | 82 | 83 | 84 | 85 | 298 86 | {{17, 13}, {353, 21}} 87 | 88 | 2 89 | YES 90 | 91 | 67239488 92 | 272631808 93 | Label 94 | 95 | 96 | 97 | 98 | 1 99 | MSAxIDEgMC43AA 100 | 101 | 102 | 103 | 104 | {387, 50} 105 | 106 | 2 107 | DemoView 108 | 109 | 110 | 111 | 112 | YES 113 | 114 | 115 | textField 116 | 117 | 118 | 119 | 7 120 | 121 | 122 | 123 | shadowTextField 124 | 125 | 126 | 127 | 10 128 | 129 | 130 | 131 | 132 | YES 133 | 134 | 0 135 | 136 | 137 | 138 | 139 | 140 | -2 141 | 142 | 143 | File's Owner 144 | 145 | 146 | -1 147 | 148 | 149 | First Responder 150 | 151 | 152 | -3 153 | 154 | 155 | Application 156 | 157 | 158 | 1 159 | 160 | 161 | YES 162 | 163 | 164 | 165 | 166 | 167 | 168 | 2 169 | 170 | 171 | YES 172 | 173 | 174 | 175 | 176 | 177 | 3 178 | 179 | 180 | 181 | 182 | 5 183 | 184 | 185 | YES 186 | 187 | 188 | 189 | 190 | 191 | 6 192 | 193 | 194 | 195 | 196 | 197 | 198 | YES 199 | 200 | YES 201 | 1.IBEditorWindowLastContentRect 202 | 1.IBPluginDependency 203 | 1.WindowOrigin 204 | 1.editorWindowContentRectSynchronizationRect 205 | 2.IBPluginDependency 206 | 2.IBViewBoundsToFrameTransform 207 | 3.IBPluginDependency 208 | 5.IBPluginDependency 209 | 6.IBPluginDependency 210 | 211 | 212 | YES 213 | {{322, 706}, {387, 50}} 214 | com.apple.InterfaceBuilder.CocoaPlugin 215 | {628, 654} 216 | {{217, 442}, {480, 272}} 217 | com.apple.InterfaceBuilder.CocoaPlugin 218 | 219 | P4AAAL+AAABCFAAAwoYAAA 220 | 221 | com.apple.InterfaceBuilder.CocoaPlugin 222 | com.apple.InterfaceBuilder.CocoaPlugin 223 | com.apple.InterfaceBuilder.CocoaPlugin 224 | 225 | 226 | 227 | YES 228 | 229 | 230 | YES 231 | 232 | 233 | 234 | 235 | YES 236 | 237 | 238 | YES 239 | 240 | 241 | 242 | 10 243 | 244 | 245 | 246 | YES 247 | 248 | DemoView 249 | JAListViewItemView 250 | 251 | YES 252 | 253 | YES 254 | shadowTextField 255 | textField 256 | 257 | 258 | YES 259 | NSTextField 260 | NSTextField 261 | 262 | 263 | 264 | YES 265 | 266 | YES 267 | shadowTextField 268 | textField 269 | 270 | 271 | YES 272 | 273 | shadowTextField 274 | NSTextField 275 | 276 | 277 | textField 278 | NSTextField 279 | 280 | 281 | 282 | 283 | IBProjectSource 284 | DemoView.h 285 | 286 | 287 | 288 | JAListViewItemView 289 | NSView 290 | 291 | IBProjectSource 292 | JAListViewItemView.h 293 | 294 | 295 | 296 | 297 | YES 298 | 299 | NSActionCell 300 | NSCell 301 | 302 | IBFrameworkSource 303 | AppKit.framework/Headers/NSActionCell.h 304 | 305 | 306 | 307 | NSApplication 308 | NSResponder 309 | 310 | IBFrameworkSource 311 | AppKit.framework/Headers/NSApplication.h 312 | 313 | 314 | 315 | NSApplication 316 | 317 | IBFrameworkSource 318 | AppKit.framework/Headers/NSApplicationScripting.h 319 | 320 | 321 | 322 | NSApplication 323 | 324 | IBFrameworkSource 325 | AppKit.framework/Headers/NSColorPanel.h 326 | 327 | 328 | 329 | NSApplication 330 | 331 | IBFrameworkSource 332 | AppKit.framework/Headers/NSHelpManager.h 333 | 334 | 335 | 336 | NSApplication 337 | 338 | IBFrameworkSource 339 | AppKit.framework/Headers/NSPageLayout.h 340 | 341 | 342 | 343 | NSApplication 344 | 345 | IBFrameworkSource 346 | AppKit.framework/Headers/NSUserInterfaceItemSearching.h 347 | 348 | 349 | 350 | NSCell 351 | NSObject 352 | 353 | IBFrameworkSource 354 | AppKit.framework/Headers/NSCell.h 355 | 356 | 357 | 358 | NSControl 359 | NSView 360 | 361 | IBFrameworkSource 362 | AppKit.framework/Headers/NSControl.h 363 | 364 | 365 | 366 | NSFormatter 367 | NSObject 368 | 369 | IBFrameworkSource 370 | Foundation.framework/Headers/NSFormatter.h 371 | 372 | 373 | 374 | NSMenu 375 | NSObject 376 | 377 | IBFrameworkSource 378 | AppKit.framework/Headers/NSMenu.h 379 | 380 | 381 | 382 | NSObject 383 | 384 | IBFrameworkSource 385 | AppKit.framework/Headers/NSAccessibility.h 386 | 387 | 388 | 389 | NSObject 390 | 391 | 392 | 393 | NSObject 394 | 395 | 396 | 397 | NSObject 398 | 399 | 400 | 401 | NSObject 402 | 403 | 404 | 405 | NSObject 406 | 407 | IBFrameworkSource 408 | AppKit.framework/Headers/NSDictionaryController.h 409 | 410 | 411 | 412 | NSObject 413 | 414 | IBFrameworkSource 415 | AppKit.framework/Headers/NSDragging.h 416 | 417 | 418 | 419 | NSObject 420 | 421 | IBFrameworkSource 422 | AppKit.framework/Headers/NSFontManager.h 423 | 424 | 425 | 426 | NSObject 427 | 428 | IBFrameworkSource 429 | AppKit.framework/Headers/NSFontPanel.h 430 | 431 | 432 | 433 | NSObject 434 | 435 | IBFrameworkSource 436 | AppKit.framework/Headers/NSKeyValueBinding.h 437 | 438 | 439 | 440 | NSObject 441 | 442 | 443 | 444 | NSObject 445 | 446 | IBFrameworkSource 447 | AppKit.framework/Headers/NSNibLoading.h 448 | 449 | 450 | 451 | NSObject 452 | 453 | IBFrameworkSource 454 | AppKit.framework/Headers/NSOutlineView.h 455 | 456 | 457 | 458 | NSObject 459 | 460 | IBFrameworkSource 461 | AppKit.framework/Headers/NSPasteboard.h 462 | 463 | 464 | 465 | NSObject 466 | 467 | IBFrameworkSource 468 | AppKit.framework/Headers/NSSavePanel.h 469 | 470 | 471 | 472 | NSObject 473 | 474 | IBFrameworkSource 475 | AppKit.framework/Headers/NSTableView.h 476 | 477 | 478 | 479 | NSObject 480 | 481 | IBFrameworkSource 482 | AppKit.framework/Headers/NSToolbarItem.h 483 | 484 | 485 | 486 | NSObject 487 | 488 | IBFrameworkSource 489 | AppKit.framework/Headers/NSView.h 490 | 491 | 492 | 493 | NSObject 494 | 495 | IBFrameworkSource 496 | Foundation.framework/Headers/NSArchiver.h 497 | 498 | 499 | 500 | NSObject 501 | 502 | IBFrameworkSource 503 | Foundation.framework/Headers/NSClassDescription.h 504 | 505 | 506 | 507 | NSObject 508 | 509 | IBFrameworkSource 510 | Foundation.framework/Headers/NSError.h 511 | 512 | 513 | 514 | NSObject 515 | 516 | IBFrameworkSource 517 | Foundation.framework/Headers/NSFileManager.h 518 | 519 | 520 | 521 | NSObject 522 | 523 | IBFrameworkSource 524 | Foundation.framework/Headers/NSKeyValueCoding.h 525 | 526 | 527 | 528 | NSObject 529 | 530 | IBFrameworkSource 531 | Foundation.framework/Headers/NSKeyValueObserving.h 532 | 533 | 534 | 535 | NSObject 536 | 537 | IBFrameworkSource 538 | Foundation.framework/Headers/NSKeyedArchiver.h 539 | 540 | 541 | 542 | NSObject 543 | 544 | IBFrameworkSource 545 | Foundation.framework/Headers/NSObject.h 546 | 547 | 548 | 549 | NSObject 550 | 551 | IBFrameworkSource 552 | Foundation.framework/Headers/NSObjectScripting.h 553 | 554 | 555 | 556 | NSObject 557 | 558 | IBFrameworkSource 559 | Foundation.framework/Headers/NSPortCoder.h 560 | 561 | 562 | 563 | NSObject 564 | 565 | IBFrameworkSource 566 | Foundation.framework/Headers/NSRunLoop.h 567 | 568 | 569 | 570 | NSObject 571 | 572 | IBFrameworkSource 573 | Foundation.framework/Headers/NSScriptClassDescription.h 574 | 575 | 576 | 577 | NSObject 578 | 579 | IBFrameworkSource 580 | Foundation.framework/Headers/NSScriptKeyValueCoding.h 581 | 582 | 583 | 584 | NSObject 585 | 586 | IBFrameworkSource 587 | Foundation.framework/Headers/NSScriptObjectSpecifiers.h 588 | 589 | 590 | 591 | NSObject 592 | 593 | IBFrameworkSource 594 | Foundation.framework/Headers/NSScriptWhoseTests.h 595 | 596 | 597 | 598 | NSObject 599 | 600 | IBFrameworkSource 601 | Foundation.framework/Headers/NSThread.h 602 | 603 | 604 | 605 | NSObject 606 | 607 | IBFrameworkSource 608 | Foundation.framework/Headers/NSURL.h 609 | 610 | 611 | 612 | NSObject 613 | 614 | IBFrameworkSource 615 | Foundation.framework/Headers/NSURLConnection.h 616 | 617 | 618 | 619 | NSObject 620 | 621 | IBFrameworkSource 622 | Foundation.framework/Headers/NSURLDownload.h 623 | 624 | 625 | 626 | NSResponder 627 | 628 | IBFrameworkSource 629 | AppKit.framework/Headers/NSInterfaceStyle.h 630 | 631 | 632 | 633 | NSResponder 634 | NSObject 635 | 636 | IBFrameworkSource 637 | AppKit.framework/Headers/NSResponder.h 638 | 639 | 640 | 641 | NSTextField 642 | NSControl 643 | 644 | IBFrameworkSource 645 | AppKit.framework/Headers/NSTextField.h 646 | 647 | 648 | 649 | NSTextFieldCell 650 | NSActionCell 651 | 652 | IBFrameworkSource 653 | AppKit.framework/Headers/NSTextFieldCell.h 654 | 655 | 656 | 657 | NSView 658 | 659 | IBFrameworkSource 660 | AppKit.framework/Headers/NSClipView.h 661 | 662 | 663 | 664 | NSView 665 | 666 | IBFrameworkSource 667 | AppKit.framework/Headers/NSMenuItem.h 668 | 669 | 670 | 671 | NSView 672 | 673 | IBFrameworkSource 674 | AppKit.framework/Headers/NSRulerView.h 675 | 676 | 677 | 678 | NSView 679 | NSResponder 680 | 681 | 682 | 683 | 684 | 0 685 | IBCocoaFramework 686 | 687 | com.apple.InterfaceBuilder.CocoaPlugin.macosx 688 | 689 | 690 | 691 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 692 | 693 | 694 | YES 695 | JAListView.xcodeproj 696 | 3 697 | 698 | 699 | -------------------------------------------------------------------------------- /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /JAEdgeInsets.h: -------------------------------------------------------------------------------- 1 | /* 2 | * JAEdgeInsets.h 3 | * GitHub 4 | * 5 | * Created by Josh Abernathy on 1/26/11. 6 | * Copyright 2011 GitHub. All rights reserved. 7 | * 8 | */ 9 | 10 | typedef struct { 11 | CGFloat top, left, bottom, right; 12 | } JAEdgeInsets; 13 | 14 | static inline JAEdgeInsets JAEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right) { 15 | return (JAEdgeInsets) { .top = top, .left = left, .bottom = bottom, .right = right }; 16 | } 17 | 18 | static const JAEdgeInsets JAEdgeInsetsZero = { .top = 0.0f, .left = 0.0f, .bottom = 0.0f, .right = 0.0f }; 19 | -------------------------------------------------------------------------------- /JAListView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.${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 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /JAListView.h: -------------------------------------------------------------------------------- 1 | // 2 | // JAListView.h 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 9/29/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JAEdgeInsets.h" 11 | 12 | @class JAListView; 13 | @class JAListViewItem; 14 | 15 | extern NSString * const JAListViewDraggingPasteboardType; 16 | 17 | @protocol JAListViewDataSource 18 | - (NSUInteger)numberOfItemsInListView:(JAListView *)listView; 19 | - (JAListViewItem *)listView:(JAListView *)listView viewAtIndex:(NSUInteger)index; 20 | @end 21 | 22 | @protocol JAListViewDelegate 23 | @optional 24 | /** 25 | * Mouse down/up doesn't necessarily mean the view will be selected. These methods will always get called regardless of the return value of -listView:shouldSelectView:. 26 | */ 27 | - (void)listView:(JAListView *)listView mouseDownOnView:(JAListViewItem *)view withEvent:(NSEvent *)event; 28 | - (void)listView:(JAListView *)listView mouseUpOnView:(JAListViewItem *)view withEvent:(NSEvent *)event; 29 | 30 | - (void)listView:(JAListView *)listView rightMouseDownOnView:(JAListViewItem *)view withEvent:(NSEvent *)event; 31 | 32 | /** 33 | * A view being selected doesn't necessarily mean it was selected by a click. For instance, keyboard selection will also trigger these events. If the selection is coming from a click, then -listView:willSelectView: gets called on mouse down and -listView:didSelectView: will get called on mouse up while still inside the view. 34 | */ 35 | - (BOOL)listView:(JAListView *)listView shouldSelectView:(JAListViewItem *)view; 36 | - (void)listView:(JAListView *)listView willSelectView:(JAListViewItem *)view; 37 | - (void)listView:(JAListView *)listView didSelectView:(JAListViewItem *)view; 38 | - (void)listView:(JAListView *)listView didDeselectView:(JAListViewItem *)view; 39 | 40 | - (void)listView:(JAListView *)listView didRemoveView:(JAListViewItem *)view; 41 | @end 42 | 43 | @protocol JAListViewDraggingSourceDelegate 44 | - (BOOL)listView:(JAListView *)listView shouldDragView:(JAListViewItem *)view; 45 | - (void)listView:(JAListView *)listView didStartDraggingView:(JAListViewItem *)view; 46 | - (void)listView:(JAListView *)listView didEndDraggingView:(JAListViewItem *)view; 47 | @end 48 | 49 | @protocol JAListViewDraggingDestinationDelegate 50 | - (BOOL)listView:(JAListView *)listView shouldAcceptViews:(NSArray *)draggedViews location:(NSPoint)location; 51 | - (void)listView:(JAListView *)listView dragEntered:(NSArray *)draggedViews location:(NSPoint)location; 52 | - (void)listView:(JAListView *)listView dragUpdated:(NSArray *)draggedViews location:(NSPoint)location; 53 | - (void)listView:(JAListView *)listView dragEnded:(NSArray *)draggedViews location:(NSPoint)location; 54 | - (void)listView:(JAListView *)listView dragExited:(NSArray *)draggedViews location:(NSPoint)location; 55 | @end 56 | 57 | 58 | @interface JAListView : NSView { 59 | NSMutableArray *cachedViews; 60 | NSArray *cachedVisibleViews; 61 | CGFloat heightForAllContent; 62 | id dataSource; 63 | id delegate; 64 | id draggingSourceDelegate; 65 | id draggingDestinationDelegate; 66 | BOOL canCallDataSourceInParallel; 67 | NSPoint margin; 68 | CGFloat *cachedLocations; 69 | __weak JAListViewItem *viewBeingSelected; 70 | NSColor *backgroundColor; 71 | BOOL isResizingManually; 72 | BOOL conditionallyUseLayerBacking; 73 | } 74 | 75 | - (void)setup; 76 | 77 | - (void)reloadData; 78 | - (void)reloadDataAnimated:(BOOL)animated; 79 | - (void)reloadDataWithAnimation:(void (^)(NSView *newSuperview, NSArray *viewsToAdd, NSArray *viewsToRemove, NSArray *viewsToMove))animationBlock; 80 | 81 | - (void)reloadLayout; 82 | - (void)reloadLayoutAnimated:(BOOL)animated; 83 | - (void)reloadLayoutWithAnimation:(void (^)(NSView *newSuperview, NSArray *viewsToAdd, NSArray *viewsToRemove, NSArray *viewsToMove))animationBlock; 84 | 85 | - (NSUInteger)numberOfViews; 86 | - (JAListViewItem *)viewAtIndex:(NSUInteger)index; 87 | - (NSInteger)indexForView:(JAListViewItem *)view; 88 | - (BOOL)isViewVisible:(JAListViewItem *)view; 89 | 90 | - (BOOL)containsViewItem:(JAListViewItem *)viewItem; 91 | 92 | - (JAListViewItem *)viewAtPoint:(NSPoint)point; 93 | 94 | /* 95 | * Default implementation returns proposedY. Subclasses could implement this to adjust the Y location for the view. 96 | */ 97 | - (CGFloat)yForView:(JAListViewItem *)view proposedY:(CGFloat)proposedY; 98 | 99 | /** 100 | * Default implementation returns proposedHeight. Subclasses could implement this to adjust the height of the view. 101 | */ 102 | - (CGFloat)heightForView:(JAListViewItem *)view proposedHeight:(CGFloat)proposedHeight; 103 | 104 | - (CGFloat)cachedYLocationForView:(JAListViewItem *)view; 105 | 106 | /** 107 | * Will call the delegate's listView:didSelectView: but not listView:shouldSelectView: or listView:willSelectView:. 108 | */ 109 | - (void)selectView:(JAListViewItem *)view; 110 | 111 | /** 112 | * Will call the delegate's listView:didDeselectView:. 113 | */ 114 | - (void)deselectView:(JAListViewItem *)view; 115 | 116 | /** 117 | * Will call the delegate's listView:didDeselectView: for each of the selected views. 118 | */ 119 | - (void)deselectAllViews; 120 | 121 | - (void)markViewBeingUsedForInertialScrolling:(JAListViewItem *)newView; 122 | - (void)unmarkViewBeingUsedForInertialScrolling:(JAListViewItem *)view; 123 | - (void)clearViewsBeingUsedForInertialScrolling; 124 | 125 | @property (nonatomic, readonly) NSScrollView *scrollView; 126 | @property (nonatomic, assign) IBOutlet id dataSource; 127 | @property (nonatomic, assign) IBOutlet id delegate; 128 | @property (nonatomic, assign) IBOutlet id draggingSourceDelegate; 129 | @property (nonatomic, assign) IBOutlet id draggingDestinationDelegate; 130 | @property (nonatomic, assign) BOOL canCallDataSourceInParallel; 131 | @property (nonatomic, readonly) NSArray *visibleViews; 132 | @property (nonatomic, assign) NSPoint margin; 133 | @property (nonatomic, assign) JAEdgeInsets padding; 134 | @property (nonatomic, retain) NSColor *backgroundColor; 135 | @property (nonatomic, readonly) CGFloat heightForAllContent; 136 | @property (nonatomic, assign) BOOL conditionallyUseLayerBacking; 137 | @property (nonatomic, readonly) NSArray *selectedViews; 138 | @property (nonatomic, assign) BOOL allowNoSelection; 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /JAListView.m: -------------------------------------------------------------------------------- 1 | // 2 | // JAListView.m 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 9/29/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import "JAListView.h" 10 | #import "JAListViewItem.h" 11 | #import 12 | 13 | static NSArray *viewsCurrentlyBeingDraggedGlobal = nil; 14 | 15 | static const NSTimeInterval defaultAnimationDuration = 0.25f; 16 | 17 | NSString * const JAListViewDraggingPasteboardType = @"JAListViewDraggingPasteboardType"; 18 | 19 | @interface JAListView () 20 | - (void)viewBoundsDidChange:(NSNotification *)notification; 21 | - (void)viewFrameDidChange:(NSNotification *)notification; 22 | - (void)sizeToFitIfNeeded; 23 | - (void)reloadAllViews; 24 | - (void)recacheAllLocations; 25 | - (void)showVisibleViewsAnimated:(BOOL)animated; 26 | - (void)updateCachedVisibleViews; 27 | - (void)clearCachedLocations; 28 | - (void)seriouslyShowVisibleViewsWithAnimation; 29 | - (void)getSelectionMinimumIndex:(NSUInteger *)minIndex maximumIndex:(NSUInteger *)maxIndex; 30 | - (JAListViewItem *)nextSelectableView; 31 | - (JAListViewItem *)nextSelectableViewFromIndex:(NSUInteger)startingIndex; 32 | - (JAListViewItem *)previousSelectableView; 33 | - (JAListViewItem *)previousSelectableViewFromIndex:(NSUInteger)startingIndex; 34 | - (void)standardLayoutRemoveViews:(NSArray *)viewsToRemove addViews:(NSArray *)viewsToAdd moveViews:(NSArray *)viewsToMove; 35 | - (void)standardLayoutAnimated:(BOOL)animated removeViews:(NSArray *)viewsToRemove addViews:(NSArray *)viewsToAdd moveViews:(NSArray *)viewsToMove; 36 | 37 | @property (readonly) NSMutableArray *cachedViews; 38 | @property (nonatomic, readonly) CGFloat *cachedLocations; 39 | @property (nonatomic, retain) NSArray *cachedVisibleViews; 40 | @property (nonatomic, assign) __weak JAListViewItem *viewBeingSelected; 41 | @property (nonatomic, retain) NSArray *viewsCurrentlyBeingDragged; 42 | @property (nonatomic, retain) NSMutableArray *currentlySelectedViews; 43 | @property (nonatomic, retain) NSTrackingArea *currentTrackingArea; 44 | @property (nonatomic, copy) void (^currentAnimationBlock)(NSView *newSuperview, NSArray *viewsToAdd, NSArray *viewsToRemove, NSArray *viewsToMove); 45 | @property (nonatomic, retain) NSMutableArray *viewsBeingUsedForInertialScrolling; 46 | @property (nonatomic, assign) NSUInteger minIndexForReLayout; 47 | @end 48 | 49 | 50 | @implementation JAListView 51 | 52 | - (void)finalize { 53 | if(cachedLocations != NULL) { 54 | free(cachedLocations); 55 | cachedLocations = NULL; 56 | } 57 | 58 | [super finalize]; 59 | } 60 | 61 | - (void)dealloc { 62 | if(cachedLocations != NULL) { 63 | free(cachedLocations); 64 | cachedLocations = NULL; 65 | } 66 | 67 | self.backgroundColor = nil; 68 | self.cachedVisibleViews = nil; 69 | self.currentlySelectedViews = nil; 70 | self.currentTrackingArea = nil; 71 | self.currentAnimationBlock = nil; 72 | self.viewsBeingUsedForInertialScrolling = nil; 73 | 74 | [super dealloc]; 75 | } 76 | 77 | 78 | #pragma mark NSView 79 | 80 | - (id)initWithFrame:(NSRect)frameRect { 81 | self = [super initWithFrame:frameRect]; 82 | if(self == nil) return nil; 83 | 84 | [self setup]; 85 | 86 | return self; 87 | } 88 | 89 | - (id)initWithCoder:(NSCoder *)decoder { 90 | self = [super initWithCoder:decoder]; 91 | if(self == nil) return nil; 92 | 93 | [self setup]; 94 | 95 | return self; 96 | } 97 | 98 | - (void)viewWillMoveToSuperview:(NSView *)newSuperview { 99 | if(self.superview != nil) { 100 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewBoundsDidChangeNotification object:self.superview]; 101 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:self.superview]; 102 | } 103 | } 104 | 105 | - (void)viewDidMoveToSuperview { 106 | if(self.superview == nil) return; 107 | 108 | if(self.scrollView == nil) { 109 | NSLog(@"%@ is not in a scroll view. Unless you know what you're doing, this is a mistake.", self); 110 | } 111 | 112 | [self.scrollView.contentView setPostsBoundsChangedNotifications:YES]; 113 | [self.scrollView.contentView setPostsFrameChangedNotifications:YES]; 114 | 115 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewBoundsDidChange:) name:NSViewBoundsDidChangeNotification object:self.scrollView.contentView]; 116 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewFrameDidChange:) name:NSViewFrameDidChangeNotification object:self.scrollView.contentView]; 117 | 118 | [self sizeToFitIfNeeded]; 119 | 120 | [self.scrollView setBackgroundColor:self.backgroundColor]; 121 | } 122 | 123 | - (BOOL)isFlipped { 124 | return YES; 125 | } 126 | 127 | - (void)drawRect:(NSRect)dirtyRect { 128 | [super drawRect:dirtyRect]; 129 | 130 | [self.backgroundColor set]; 131 | NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver); 132 | } 133 | 134 | 135 | #pragma mark NSResponder 136 | 137 | - (void)rightMouseDown:(NSEvent *)event { 138 | NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; 139 | JAListViewItem *view = [self viewAtPoint:location]; 140 | 141 | if([self.delegate respondsToSelector:@selector(listView:rightMouseDownOnView:withEvent:)]) { 142 | [self.delegate listView:self rightMouseDownOnView:view withEvent:event]; 143 | } 144 | } 145 | 146 | - (void)rightMouseUp:(NSEvent *)event { 147 | [self rightMouseDown:event]; 148 | } 149 | 150 | - (void)mouseDown:(NSEvent *)event { 151 | if(([event modifierFlags] & NSControlKeyMask) != 0 && [event clickCount] == 1) { 152 | [self rightMouseDown:event]; 153 | return; 154 | } 155 | 156 | NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; 157 | JAListViewItem *view = [self viewAtPoint:location]; 158 | if(view == nil) return; 159 | 160 | if([self.delegate respondsToSelector:@selector(listView:mouseDownOnView:withEvent:)]) { 161 | [self.delegate listView:self mouseDownOnView:view withEvent:event]; 162 | } 163 | 164 | BOOL shouldSelect = YES; 165 | if([self.delegate respondsToSelector:@selector(listView:shouldSelectView:)]) { 166 | shouldSelect = [self.delegate listView:self shouldSelectView:view]; 167 | } 168 | 169 | if(!shouldSelect) { 170 | return; 171 | } 172 | 173 | if(self.currentTrackingArea != nil) { 174 | [self removeTrackingArea:self.currentTrackingArea]; 175 | } 176 | 177 | self.currentTrackingArea = [[[NSTrackingArea alloc] initWithRect:view.frame options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag owner:self userInfo:nil] autorelease]; 178 | [self addTrackingArea:self.currentTrackingArea]; 179 | 180 | self.viewBeingSelected = view; 181 | self.viewBeingSelected.highlighted = YES; 182 | 183 | if([self.delegate respondsToSelector:@selector(listView:willSelectView:)]) { 184 | [self.delegate listView:self willSelectView:view]; 185 | } 186 | } 187 | 188 | - (void)mouseUp:(NSEvent *)event { 189 | // this is to fix a nasty AppKit bug where the event after a contexual menu gets routed oddly and we end up with a mouseUp event that doesn't really belong to us 190 | if(!NSPointInRect([self convertPoint:[event locationInWindow] fromView:nil], self.frame)) { 191 | return; 192 | } 193 | 194 | [self removeTrackingArea:self.currentTrackingArea]; 195 | self.currentTrackingArea = nil; 196 | 197 | NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; 198 | JAListViewItem *view = [self viewAtPoint:location]; 199 | BOOL respondsToUnSelect = [self.delegate respondsToSelector:@selector(listView:didDeselectView:)]; 200 | if(view == nil) { 201 | if(self.viewBeingSelected != nil) { 202 | if(respondsToUnSelect) { 203 | [self.delegate listView:self didDeselectView:self.viewBeingSelected]; 204 | } 205 | 206 | self.viewBeingSelected.highlighted = NO; 207 | self.viewBeingSelected = nil; 208 | } else { 209 | if(self.allowNoSelection) { 210 | for(JAListViewItem *selectedView in [[self.currentlySelectedViews copy] autorelease]) { 211 | selectedView.selected = NO; 212 | [self.currentlySelectedViews removeObject:selectedView]; 213 | 214 | if(respondsToUnSelect) { 215 | [self.delegate listView:self didDeselectView:selectedView]; 216 | } 217 | } 218 | } 219 | } 220 | } else { 221 | if([self.delegate respondsToSelector:@selector(listView:mouseUpOnView:withEvent:)]) { 222 | [self.delegate listView:self mouseUpOnView:view withEvent:event]; 223 | } 224 | 225 | self.viewBeingSelected.highlighted = NO; 226 | 227 | BOOL shouldSelect = YES; 228 | if([self.delegate respondsToSelector:@selector(listView:shouldSelectView:)]) { 229 | shouldSelect = [self.delegate listView:self shouldSelectView:view]; 230 | } 231 | 232 | if(!shouldSelect) { 233 | return; 234 | } 235 | 236 | if([event modifierFlags] & NSCommandKeyMask) { 237 | if([self.currentlySelectedViews containsObject:view]) { 238 | if(!self.allowNoSelection && self.currentlySelectedViews.count < 2) return; 239 | 240 | [[view retain] autorelease]; 241 | view.selected = NO; 242 | [self.currentlySelectedViews removeObject:view]; 243 | 244 | if(respondsToUnSelect) { 245 | [self.delegate listView:self didDeselectView:view]; 246 | } 247 | } else { 248 | view.selected = YES; 249 | [self.currentlySelectedViews addObject:view]; 250 | 251 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 252 | [self.delegate listView:self didSelectView:view]; 253 | } 254 | } 255 | } else if([event modifierFlags] & NSShiftKeyMask) { 256 | JAListViewItem *lastSelectedView = [self.currentlySelectedViews lastObject]; 257 | NSInteger lastIndex = [self indexForView:lastSelectedView]; 258 | NSInteger indexOfView = [self indexForView:view]; 259 | 260 | if(lastIndex == NSNotFound || indexOfView == NSNotFound) return; 261 | 262 | if(indexOfView < lastIndex) { 263 | JAListViewItem *previousView = [self previousSelectableViewFromIndex:(NSUInteger) lastIndex]; 264 | NSInteger currentIndex = [self indexForView:previousView]; 265 | while(previousView != nil && currentIndex > indexOfView) { 266 | previousView.selected = YES; 267 | [self.currentlySelectedViews addObject:previousView]; 268 | 269 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 270 | [self.delegate listView:self didSelectView:previousView]; 271 | } 272 | 273 | previousView = [self previousSelectableView]; 274 | currentIndex = [self indexForView:previousView]; 275 | } 276 | } else { 277 | JAListViewItem *nextView = [self nextSelectableViewFromIndex:(NSUInteger) lastIndex]; 278 | NSInteger currentIndex = [self indexForView:nextView]; 279 | while(nextView != nil && currentIndex < indexOfView) { 280 | nextView.selected = YES; 281 | [self.currentlySelectedViews addObject:nextView]; 282 | 283 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 284 | [self.delegate listView:self didSelectView:nextView]; 285 | } 286 | 287 | nextView = [self nextSelectableView]; 288 | currentIndex = [self indexForView:nextView]; 289 | } 290 | } 291 | 292 | view.selected = YES; 293 | [self.currentlySelectedViews addObject:view]; 294 | 295 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 296 | [self.delegate listView:self didSelectView:view]; 297 | } 298 | } else { 299 | for(JAListViewItem *selectedView in [[self.currentlySelectedViews copy] autorelease]) { 300 | selectedView.selected = NO; 301 | [self.currentlySelectedViews removeObject:selectedView]; 302 | 303 | if(respondsToUnSelect) { 304 | [self.delegate listView:self didDeselectView:selectedView]; 305 | } 306 | } 307 | 308 | view.selected = YES; 309 | [self.currentlySelectedViews addObject:view]; 310 | [self.window makeFirstResponder:view]; 311 | 312 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 313 | [self.delegate listView:self didSelectView:view]; 314 | } 315 | } 316 | 317 | self.viewBeingSelected = nil; 318 | } 319 | } 320 | 321 | - (void)mouseExited:(NSEvent *)event { 322 | if(self.viewBeingSelected != nil && [self.delegate respondsToSelector:@selector(listView:didDeselectView:)]) { 323 | [self.delegate listView:self didDeselectView:self.viewBeingSelected]; 324 | self.viewBeingSelected = nil; 325 | 326 | [self removeTrackingArea:self.currentTrackingArea]; 327 | self.currentTrackingArea = nil; 328 | } 329 | } 330 | 331 | - (void)swipeWithEvent:(NSEvent *)event { 332 | JAListViewItem *newView = nil; 333 | if([event deltaY] < 0.0f && [self nextSelectableView] != nil) { 334 | newView = [self nextSelectableView]; 335 | } else if([event deltaY] > 0.0f && [self previousSelectableView] != nil) { 336 | newView = [self previousSelectableView]; 337 | } else { 338 | [super swipeWithEvent:event]; 339 | } 340 | 341 | if(newView != nil) { 342 | for(JAListViewItem *view in [[self.currentlySelectedViews copy] autorelease]) { 343 | [[view retain] autorelease]; 344 | view.selected = NO; 345 | [self.currentlySelectedViews removeObject:view]; 346 | 347 | if([self.delegate respondsToSelector:@selector(listView:didDeselectView:)]) { 348 | [self.delegate listView:self didDeselectView:view]; 349 | } 350 | } 351 | 352 | newView.selected = YES; 353 | [self.window makeFirstResponder:newView]; 354 | [self.currentlySelectedViews addObject:newView]; 355 | 356 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 357 | [self.delegate listView:self didSelectView:newView]; 358 | } 359 | 360 | NSRect viewFrame = newView.frame; 361 | viewFrame.origin.y = self.cachedLocations[[self indexForView:newView]]; 362 | [(NSView *) self.scrollView.documentView scrollRectToVisible:viewFrame]; 363 | } 364 | } 365 | 366 | - (void)mouseDragged:(NSEvent *)event { 367 | NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; 368 | NSView *viewUnderMouse = [self viewAtPoint:location]; 369 | if(viewUnderMouse == nil || ![viewUnderMouse isKindOfClass:[JAListViewItem class]]) { 370 | return; 371 | } 372 | 373 | JAListViewItem *listItemView = (JAListViewItem *) viewUnderMouse; 374 | if(![self.currentlySelectedViews containsObject:listItemView]) { 375 | BOOL shouldSelect = YES; 376 | if([self.delegate respondsToSelector:@selector(listView:shouldSelectView:)]) { 377 | shouldSelect = [self.delegate listView:self shouldSelectView:listItemView]; 378 | } 379 | 380 | if(!shouldSelect) return; 381 | 382 | [self deselectAllViews]; 383 | [self selectView:listItemView]; 384 | } 385 | 386 | NSMutableArray *viewsToDrag = [NSMutableArray array]; 387 | 388 | for(JAListViewItem *view in self.currentlySelectedViews) { 389 | BOOL shouldDrag = NO; 390 | if([self.draggingSourceDelegate respondsToSelector:@selector(listView:shouldDragView:)]) { 391 | shouldDrag = [self.draggingSourceDelegate listView:self shouldDragView:view]; 392 | } 393 | 394 | if(shouldDrag) { 395 | [viewsToDrag addObject:view]; 396 | } 397 | } 398 | 399 | if(viewsToDrag.count < 1) { 400 | return; 401 | } 402 | 403 | [[NSPasteboard pasteboardWithName:NSDragPboard] declareTypes:[NSArray arrayWithObject:JAListViewDraggingPasteboardType] owner:self]; 404 | [[NSPasteboard pasteboardWithName:NSDragPboard] setString:JAListViewDraggingPasteboardType forType:JAListViewDraggingPasteboardType]; 405 | 406 | self.viewsCurrentlyBeingDragged = viewsToDrag; 407 | 408 | NSMutableArray *images = [NSMutableArray array]; 409 | CGFloat height = 0.0f; 410 | CGFloat width = 0.0f; 411 | for(JAListViewItem *view in self.viewsCurrentlyBeingDragged) { 412 | NSImage *image = [view draggingImage]; 413 | height += image.size.height; 414 | width = MAX(width, image.size.width); 415 | [images addObject:image]; 416 | } 417 | 418 | NSImage *masterImage = [[[NSImage alloc] initWithSize:NSMakeSize(width, height)] autorelease]; 419 | [masterImage lockFocus]; 420 | NSUInteger index = 0; 421 | for(NSImage *image in images) { 422 | JAListViewItem *view = [self.viewsCurrentlyBeingDragged objectAtIndex:index]; 423 | CGFloat y = height - ((index + 1) * view.frame.size.height); 424 | [image drawAtPoint:NSMakePoint(0.0f, y) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0f]; 425 | 426 | index++; 427 | } 428 | [masterImage unlockFocus]; 429 | 430 | NSPoint dragPoint = viewUnderMouse.frame.origin; 431 | dragPoint.y += viewUnderMouse.bounds.size.height; 432 | [self dragImage:masterImage at:dragPoint offset:NSZeroSize event:event pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] source:self slideBack:YES]; 433 | } 434 | 435 | - (void)keyDown:(NSEvent *)event { 436 | JAListViewItem *newView = nil; 437 | BOOL isShiftDown = ([event modifierFlags] & NSShiftKeyMask) != 0; 438 | BOOL areUnhandledKeys = NO; 439 | NSString *characters = [event characters]; 440 | for(NSUInteger characterIndex = 0; characterIndex < characters.length; characterIndex++) { 441 | unichar character = [characters characterAtIndex:characterIndex]; 442 | if(character == NSUpArrowFunctionKey) { 443 | newView = [self previousSelectableView]; 444 | 445 | if(!isShiftDown && newView != nil) { 446 | for(JAListViewItem *view in [[self.currentlySelectedViews copy] autorelease]) { 447 | [[view retain] autorelease]; 448 | view.selected = NO; 449 | [self.currentlySelectedViews removeObject:view]; 450 | 451 | if([self.delegate respondsToSelector:@selector(listView:didDeselectView:)]) { 452 | [self.delegate listView:self didDeselectView:view]; 453 | } 454 | } 455 | } 456 | } else if(character == NSDownArrowFunctionKey) { 457 | newView = [self nextSelectableView]; 458 | 459 | if(!isShiftDown && newView != nil) { 460 | for(JAListViewItem *view in [[self.currentlySelectedViews copy] autorelease]) { 461 | [[view retain] autorelease]; 462 | view.selected = NO; 463 | [self.currentlySelectedViews removeObject:view]; 464 | 465 | if([self.delegate respondsToSelector:@selector(listView:didDeselectView:)]) { 466 | [self.delegate listView:self didDeselectView:view]; 467 | } 468 | } 469 | } 470 | } else { 471 | areUnhandledKeys = YES; 472 | } 473 | } 474 | 475 | if(newView != nil) { 476 | newView.selected = YES; 477 | [self.window makeFirstResponder:newView]; 478 | [self.currentlySelectedViews addObject:newView]; 479 | 480 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 481 | [self.delegate listView:self didSelectView:newView]; 482 | } 483 | 484 | NSRect viewFrame = newView.frame; 485 | viewFrame.origin.y = self.cachedLocations[[self indexForView:newView]]; 486 | [(NSView *) self.scrollView.documentView scrollRectToVisible:viewFrame]; 487 | } 488 | 489 | if(areUnhandledKeys) { 490 | [super keyDown:event]; 491 | } 492 | } 493 | 494 | - (BOOL)acceptsFirstResponder { 495 | return YES; 496 | } 497 | 498 | - (BOOL)becomeFirstResponder { 499 | return YES; 500 | } 501 | 502 | - (BOOL)resignFirstResponder { 503 | return YES; 504 | } 505 | 506 | 507 | #pragma mark NSDraggingSource 508 | 509 | - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { 510 | return isLocal ? NSDragOperationMove : NSDragOperationNone; 511 | } 512 | 513 | - (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenPoint { 514 | 515 | } 516 | 517 | - (void)draggedImage:(NSImage *)image endedAt:(NSPoint)point operation:(NSDragOperation)operation { 518 | 519 | } 520 | 521 | 522 | #pragma mark NSDraggingDestination 523 | 524 | - (NSDragOperation)draggingEntered:(id)sender { 525 | NSPasteboard *pasteboard = [sender draggingPasteboard]; 526 | if([pasteboard.types containsObject:JAListViewDraggingPasteboardType]) { 527 | NSPoint location = [self convertPoint:[sender draggingLocation] fromView:nil]; 528 | [self.draggingDestinationDelegate listView:self dragEntered:self.viewsCurrentlyBeingDragged location:location]; 529 | 530 | return NSDragOperationMove; 531 | } 532 | 533 | return NSDragOperationNone; 534 | } 535 | 536 | - (NSDragOperation)draggingUpdated:(id)sender { 537 | NSPasteboard *pasteboard = [sender draggingPasteboard]; 538 | if([pasteboard.types containsObject:JAListViewDraggingPasteboardType]) { 539 | NSPoint location = [self convertPoint:[sender draggingLocation] fromView:nil]; 540 | [self.draggingDestinationDelegate listView:self dragUpdated:self.viewsCurrentlyBeingDragged location:location]; 541 | 542 | return NSDragOperationMove; 543 | } 544 | 545 | return NSDragOperationNone; 546 | } 547 | 548 | - (BOOL)performDragOperation:(id)sender { 549 | NSPasteboard *pasteboard = [sender draggingPasteboard]; 550 | if([pasteboard.types containsObject:JAListViewDraggingPasteboardType]) { 551 | NSPoint location = [self convertPoint:[sender draggingLocation] fromView:nil]; 552 | [self.draggingDestinationDelegate listView:self dragEnded:self.viewsCurrentlyBeingDragged location:location]; 553 | 554 | self.viewsCurrentlyBeingDragged = nil; 555 | 556 | return YES; 557 | } 558 | 559 | return NO; 560 | } 561 | 562 | - (void)draggingExited:(id)sender { 563 | NSPoint location = [self convertPoint:[sender draggingLocation] fromView:nil]; 564 | [self.draggingDestinationDelegate listView:self dragExited:self.viewsCurrentlyBeingDragged location:location]; 565 | 566 | self.viewsCurrentlyBeingDragged = nil; 567 | } 568 | 569 | 570 | #pragma mark API 571 | 572 | - (void)setup { 573 | canCallDataSourceInParallel = NO; 574 | margin = NSZeroPoint; 575 | self.backgroundColor = [NSColor darkGrayColor]; 576 | [self registerForDraggedTypes:[NSArray arrayWithObject:JAListViewDraggingPasteboardType]]; 577 | self.currentlySelectedViews = [NSMutableArray array]; 578 | self.viewsBeingUsedForInertialScrolling = [NSMutableArray array]; 579 | self.allowNoSelection = YES; 580 | } 581 | 582 | - (void)viewBoundsDidChange:(NSNotification *)notification { 583 | if(isResizingManually) return; 584 | 585 | [self reloadLayout]; 586 | } 587 | 588 | - (void)viewFrameDidChange:(NSNotification *)notification { 589 | if(isResizingManually) return; 590 | 591 | [self reloadLayout]; 592 | } 593 | 594 | - (JAListViewItem *)viewAtPoint:(NSPoint)point { 595 | for(JAListViewItem *view in self.cachedVisibleViews) { 596 | if(NSPointInRect(point, view.frame)) { 597 | return view; 598 | } 599 | } 600 | 601 | return nil; 602 | } 603 | 604 | - (void)sizeToFitIfNeeded { 605 | CGFloat currentScrollOffset = self.scrollView.contentView.bounds.origin.y; 606 | 607 | CGFloat previousHeight = self.frame.size.height; 608 | CGFloat newHeight = self.heightForAllContent; 609 | if(previousHeight != newHeight) { 610 | [self setFrameSize:NSMakeSize(self.superview.bounds.size.width, MAX(newHeight, self.superview.bounds.size.height))]; 611 | 612 | NSPoint newScrollPoint = self.scrollView.contentView.bounds.origin; 613 | newScrollPoint.y = currentScrollOffset; 614 | [self.scrollView.documentView scrollPoint:newScrollPoint]; 615 | } 616 | } 617 | 618 | - (void)reloadData { 619 | [self reloadDataAnimated:NO]; 620 | } 621 | 622 | - (void)reloadDataAnimated:(BOOL)animated { 623 | [self reloadDataWithAnimation:animated ? ^(NSView *newSuperview, NSArray *viewsToAdd, NSArray *viewsToRemove, NSArray *viewsToMove) { 624 | [self standardLayoutAnimated:YES removeViews:viewsToRemove addViews:viewsToAdd moveViews:viewsToMove]; 625 | } : nil]; 626 | } 627 | 628 | - (void)reloadDataWithAnimation:(void (^)(NSView *newSuperview, NSArray *viewsToAdd, NSArray *viewsToRemove, NSArray *viewsToMove))animationBlock { 629 | self.currentAnimationBlock = animationBlock; 630 | 631 | if(self.currentAnimationBlock != nil && self.conditionallyUseLayerBacking) { 632 | [self setWantsLayer:YES]; 633 | } 634 | 635 | [self reloadAllViews]; 636 | [self reloadLayoutWithAnimation:animationBlock]; 637 | } 638 | 639 | - (void)reloadAllViews { 640 | [self.cachedViews removeAllObjects]; 641 | 642 | NSUInteger numberOfViews = [self numberOfViews]; 643 | NSMutableArray *views = [NSMutableArray arrayWithCapacity:numberOfViews]; 644 | 645 | // fill the array with dummy values so that the data source can be called in parallel 646 | for(NSUInteger index = 0; index < numberOfViews; index++) { 647 | [views addObject:[NSNull null]]; 648 | } 649 | 650 | if(self.canCallDataSourceInParallel) { 651 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 652 | dispatch_apply(numberOfViews, queue, ^(size_t index) { 653 | [views replaceObjectAtIndex:index withObject:[self viewAtIndex:index]]; 654 | }); 655 | } else { 656 | for(NSUInteger index = 0; index < numberOfViews; index++) { 657 | [views replaceObjectAtIndex:index withObject:[self viewAtIndex:index]]; 658 | } 659 | } 660 | 661 | for(JAListViewItem *viewItem in views) { 662 | viewItem.listView = self; 663 | } 664 | 665 | [self.cachedViews addObjectsFromArray:views]; 666 | [self clearCachedLocations]; 667 | } 668 | 669 | - (void)showVisibleViewsAnimated:(BOOL)animated { 670 | [self updateCachedVisibleViews]; 671 | 672 | NSArray *newViews = self.cachedVisibleViews; 673 | NSArray *existingViews = [[self.subviews copy] autorelease]; 674 | NSMutableArray *viewsToAdd = [NSMutableArray arrayWithArray:newViews]; 675 | NSMutableArray *viewsToRemove = [NSMutableArray arrayWithArray:existingViews]; 676 | // don't do any unnecessary adding/removing otherwise layer-backed views feel the pain 677 | for(JAListViewItem *view in newViews) { 678 | if([existingViews containsObject:view]) { 679 | [viewsToAdd removeObject:view]; 680 | } 681 | } 682 | 683 | for(NSView *view in existingViews) { 684 | if([newViews containsObject:view]) { 685 | [viewsToRemove removeObject:view]; 686 | } 687 | } 688 | 689 | [viewsToAdd removeObjectsInArray:self.viewsBeingUsedForInertialScrolling]; 690 | [viewsToRemove removeObjectsInArray:self.viewsBeingUsedForInertialScrolling]; 691 | 692 | if(animated) { 693 | CFTimeInterval duration = ([self.window currentEvent].modifierFlags & NSShiftKeyMask) ? 10.0f : defaultAnimationDuration; 694 | 695 | [CATransaction begin]; 696 | [CATransaction setAnimationDuration:duration]; 697 | [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; 698 | 699 | if(self.conditionallyUseLayerBacking) { 700 | [CATransaction setCompletionBlock:^{ 701 | [self setWantsLayer:NO]; 702 | }]; 703 | } 704 | } 705 | 706 | CGFloat minY = CGFLOAT_MAX; 707 | for(NSView *view in [[viewsToRemove copy] autorelease]) { 708 | if([view isKindOfClass:[JAListViewItem class]]) { 709 | minY = MIN(view.frame.origin.y, minY); 710 | } else { 711 | [viewsToRemove removeObject:view]; 712 | } 713 | } 714 | 715 | for(JAListViewItem *view in [[viewsToAdd copy] autorelease]) { 716 | if(view.ignoreInListViewLayout) { 717 | [viewsToAdd removeObject:view]; 718 | continue; 719 | } 720 | 721 | view.autoresizingMask = NSViewWidthSizable | NSViewMaxYMargin; 722 | 723 | CGFloat y = self.cachedLocations[[self.cachedViews indexOfObject:view]]; 724 | minY = MIN(y, minY); 725 | } 726 | 727 | NSMutableArray *viewsToMove = [NSMutableArray array]; 728 | NSUInteger index = 0; 729 | for(NSView *view in existingViews) { 730 | if([view isKindOfClass:[JAListViewItem class]]) { 731 | JAListViewItem *listItemView = (JAListViewItem *) view; 732 | // only layout views after the first new view 733 | if((listItemView.frame.origin.y >= minY || index >= self.minIndexForReLayout) && !listItemView.ignoreInListViewLayout) { 734 | [viewsToMove addObject:listItemView]; 735 | } 736 | } 737 | 738 | index++; 739 | } 740 | 741 | if(self.currentAnimationBlock != nil) { 742 | self.currentAnimationBlock(self, viewsToAdd, viewsToRemove, viewsToMove); 743 | } else { 744 | [self standardLayoutRemoveViews:viewsToRemove addViews:viewsToAdd moveViews:viewsToMove]; 745 | } 746 | 747 | if([self.delegate respondsToSelector:@selector(listView:didRemoveView:)]) { 748 | for(JAListViewItem *viewItem in viewsToRemove) { 749 | [self.delegate listView:self didRemoveView:viewItem]; 750 | } 751 | } 752 | 753 | self.minIndexForReLayout = NSUIntegerMax; 754 | 755 | self.currentAnimationBlock = nil; 756 | 757 | if(animated) { 758 | [CATransaction commit]; 759 | } 760 | } 761 | 762 | - (CGFloat)cachedYLocationForView:(JAListViewItem *)view { 763 | NSUInteger index = [self.cachedViews indexOfObject:view]; 764 | if(index == NSNotFound) { 765 | return 0.0f; 766 | } 767 | 768 | return self.cachedLocations[index]; 769 | } 770 | 771 | - (void)standardLayoutRemoveViews:(NSArray *)viewsToRemove addViews:(NSArray *)viewsToAdd moveViews:(NSArray *)viewsToMove { 772 | [self standardLayoutAnimated:NO removeViews:viewsToRemove addViews:viewsToAdd moveViews:viewsToMove]; 773 | } 774 | 775 | - (void)standardLayoutAnimated:(BOOL)animated removeViews:(NSArray *)viewsToRemove addViews:(NSArray *)viewsToAdd moveViews:(NSArray *)viewsToMove { 776 | for(JAListViewItem *view in viewsToRemove) { 777 | id viewOrProxy = animated ? [view animator] : view; 778 | [viewOrProxy removeFromSuperview]; 779 | } 780 | 781 | for(JAListViewItem *view in viewsToAdd) { 782 | CGFloat y = self.cachedLocations[[self.cachedViews indexOfObject:view]]; 783 | CGFloat height = [self heightForView:view proposedHeight:view.bounds.size.height]; 784 | NSRect viewFrame = NSMakeRect(view.ignoresListViewPadding ? 0.0f : self.padding.left, y, view.ignoresListViewPadding ? self.bounds.size.width : self.bounds.size.width - (self.padding.left + self.padding.right), height); 785 | view.frame = NSIntegralRect(viewFrame); 786 | 787 | id viewOrProxy = animated ? [self animator] : self; 788 | [viewOrProxy addSubview:view]; 789 | } 790 | 791 | for(JAListViewItem *view in viewsToMove) { 792 | NSUInteger indexOfView = [self.cachedViews indexOfObject:view]; 793 | if (indexOfView == NSNotFound) { 794 | continue; 795 | } 796 | CGFloat y = self.cachedLocations[indexOfView]; //!!!: boom - bad access 797 | id viewOrProxy = animated ? [view animator] : view; 798 | [viewOrProxy setFrame:NSMakeRect(view.ignoresListViewPadding ? 0.0f : self.padding.left, y, view.bounds.size.width, view.bounds.size.height)]; 799 | } 800 | } 801 | 802 | - (void)reloadLayout { 803 | [self reloadLayoutAnimated:NO]; 804 | } 805 | 806 | - (void)seriouslyShowVisibleViewsWithAnimation { 807 | [self showVisibleViewsAnimated:YES]; 808 | } 809 | 810 | - (void)reloadLayoutAnimated:(BOOL)animated { 811 | [self reloadLayoutWithAnimation:animated ? ^(NSView *newSuperview, NSArray *viewsToAdd, NSArray *viewsToRemove, NSArray *viewsToMove) { 812 | [self standardLayoutAnimated:YES removeViews:viewsToRemove addViews:viewsToAdd moveViews:viewsToMove]; 813 | } : nil]; 814 | } 815 | 816 | - (void)reloadLayoutWithAnimation:(void (^)(NSView *newSuperview, NSArray *viewsToAdd, NSArray *viewsToRemove, NSArray *viewsToMove))animationBlock { 817 | self.currentAnimationBlock = animationBlock; 818 | 819 | [self recacheAllLocations]; 820 | isResizingManually = YES; 821 | [self sizeToFitIfNeeded]; 822 | if(self.currentAnimationBlock != nil) { 823 | [self performSelector:@selector(seriouslyShowVisibleViewsWithAnimation) withObject:nil afterDelay:0]; 824 | } else { 825 | [self showVisibleViewsAnimated:NO]; 826 | } 827 | isResizingManually = NO; 828 | } 829 | 830 | - (void)recacheAllLocations { 831 | self.minIndexForReLayout = NSUIntegerMax; 832 | 833 | CGFloat currentY = self.margin.y + self.padding.top; 834 | NSUInteger index = 0; 835 | for(JAListViewItem *view in self.cachedViews) { 836 | CGFloat newY = [self yForView:view proposedY:currentY]; 837 | if(newY != self.cachedLocations[index]) { 838 | self.minIndexForReLayout = MIN(self.minIndexForReLayout, index); 839 | } 840 | self.cachedLocations[index] = newY; 841 | currentY += [self heightForView:view proposedHeight:view.bounds.size.height + self.margin.y]; 842 | index++; 843 | } 844 | 845 | heightForAllContent = currentY + self.padding.bottom; 846 | 847 | self.minIndexForReLayout = 0; 848 | } 849 | 850 | - (CGFloat)yForView:(JAListViewItem *)view proposedY:(CGFloat)proposedY { 851 | return proposedY; 852 | } 853 | 854 | - (CGFloat)heightForView:(JAListViewItem *)view proposedHeight:(CGFloat)proposedHeight { 855 | return proposedHeight; 856 | } 857 | 858 | - (NSUInteger)numberOfViews { 859 | return [self.dataSource numberOfItemsInListView:self]; 860 | } 861 | 862 | - (JAListViewItem *)viewAtIndex:(NSUInteger)index { 863 | JAListViewItem *view = [self.dataSource listView:self viewAtIndex:index]; 864 | NSAssert2([view isKindOfClass:[JAListViewItem class]], @"View must be a %@, instead: %@", NSStringFromClass([JAListViewItem class]), view); 865 | return view; 866 | } 867 | 868 | - (NSInteger)indexForView:(JAListViewItem *)view { 869 | NSInteger index = 0; 870 | for(JAListViewItem *viewAtIndex in self.cachedViews) { 871 | if(viewAtIndex == view) { 872 | return index; 873 | } 874 | 875 | index++; 876 | } 877 | 878 | return NSNotFound; 879 | } 880 | 881 | - (BOOL)isViewVisible:(JAListViewItem *)view { 882 | return [self.visibleViews containsObject:view]; 883 | } 884 | 885 | - (BOOL)containsViewItem:(JAListViewItem *)viewItem { 886 | return [self.cachedViews containsObject:viewItem]; 887 | } 888 | 889 | - (NSArray *)visibleViews { 890 | return self.cachedVisibleViews; 891 | } 892 | 893 | - (void)updateCachedVisibleViews { 894 | NSRect visibleRect = [self.scrollView documentVisibleRect]; 895 | NSMutableArray *newVisibleViews = [NSMutableArray array]; 896 | NSUInteger index = 0; 897 | for(JAListViewItem *view in self.cachedViews) { 898 | if(NSIntersectsRect(visibleRect, NSMakeRect(view.frame.origin.x, self.cachedLocations[index], MAX(view.frame.size.width, 1.0f), MAX(view.frame.size.height, 1.0f)))) { 899 | [newVisibleViews addObject:view]; 900 | } 901 | 902 | index++; 903 | } 904 | 905 | self.cachedVisibleViews = newVisibleViews; 906 | } 907 | 908 | - (NSScrollView *)scrollView { 909 | return [self enclosingScrollView]; 910 | } 911 | 912 | - (void)clearCachedLocations { 913 | if(cachedLocations != NULL) { 914 | free(cachedLocations); 915 | cachedLocations = NULL; 916 | } 917 | } 918 | 919 | - (NSMutableArray *)cachedViews { 920 | if(cachedViews == nil) { 921 | cachedViews = [[NSMutableArray alloc] init]; 922 | } 923 | 924 | return cachedViews; 925 | } 926 | 927 | - (CGFloat *)cachedLocations { 928 | if(cachedLocations == NULL) { 929 | cachedLocations = (CGFloat *) calloc(self.cachedViews.count, sizeof(CGFloat)); 930 | } 931 | 932 | return cachedLocations; 933 | } 934 | 935 | - (void)setDataSource:(id)newDataSource { 936 | dataSource = newDataSource; 937 | 938 | [self reloadData]; 939 | } 940 | 941 | - (NSArray *)viewsCurrentlyBeingDragged { 942 | return viewsCurrentlyBeingDraggedGlobal; 943 | } 944 | 945 | - (void)setViewsCurrentlyBeingDragged:(NSArray *)views { 946 | [views retain]; 947 | [viewsCurrentlyBeingDraggedGlobal release]; 948 | viewsCurrentlyBeingDraggedGlobal = views; 949 | } 950 | 951 | - (void)setBackgroundColor:(NSColor *)newColor { 952 | [newColor retain]; 953 | [backgroundColor release]; 954 | backgroundColor = newColor; 955 | 956 | [self.scrollView setBackgroundColor:self.backgroundColor]; 957 | 958 | [self setNeedsDisplay:YES]; 959 | } 960 | 961 | - (NSArray *)selectedViews { 962 | return self.currentlySelectedViews; 963 | } 964 | 965 | - (void)getSelectionMinimumIndex:(NSUInteger *)minIndex maximumIndex:(NSUInteger *)maxIndex { 966 | if(minIndex != NULL) *minIndex = NSUIntegerMax; 967 | if(maxIndex != NULL) *maxIndex = 0; 968 | 969 | for(JAListViewItem *view in self.currentlySelectedViews) { 970 | NSUInteger index = (NSUInteger) [self indexForView:view]; 971 | if(minIndex != NULL) *minIndex = MIN(index, *minIndex); 972 | if(maxIndex != NULL) *maxIndex = MAX(index, *maxIndex); 973 | } 974 | } 975 | 976 | - (JAListViewItem *)nextSelectableView { 977 | NSUInteger maxIndex = 0; 978 | [self getSelectionMinimumIndex:NULL maximumIndex:&maxIndex]; 979 | 980 | return [self nextSelectableViewFromIndex:maxIndex]; 981 | } 982 | 983 | - (JAListViewItem *)previousSelectableView { 984 | NSUInteger minIndex = 0; 985 | [self getSelectionMinimumIndex:&minIndex maximumIndex:NULL]; 986 | 987 | return [self previousSelectableViewFromIndex:minIndex]; 988 | } 989 | 990 | - (JAListViewItem *)nextSelectableViewFromIndex:(NSUInteger)startingIndex { 991 | BOOL respondsToShouldSelect = [self.delegate respondsToSelector:@selector(listView:shouldSelectView:)]; 992 | NSUInteger numberOfViews = [self numberOfViews]; 993 | JAListViewItem *nextView = nil; 994 | NSUInteger index = startingIndex + 1; 995 | while(nextView == nil) { 996 | if(index >= numberOfViews) break; 997 | 998 | JAListViewItem *potentialView = [self viewAtIndex:index]; 999 | if(respondsToShouldSelect) { 1000 | if([self.delegate listView:self shouldSelectView:potentialView]) { 1001 | nextView = potentialView; 1002 | } 1003 | } else { 1004 | nextView = potentialView; 1005 | } 1006 | 1007 | index++; 1008 | 1009 | } 1010 | 1011 | return nextView; 1012 | } 1013 | 1014 | - (JAListViewItem *)previousSelectableViewFromIndex:(NSUInteger)startingIndex { 1015 | BOOL respondsToShouldSelect = [self.delegate respondsToSelector:@selector(listView:shouldSelectView:)]; 1016 | JAListViewItem *previousView = nil; 1017 | NSInteger index = (NSInteger) startingIndex - 1; 1018 | while(previousView == nil) { 1019 | if(index < 0) break; 1020 | 1021 | JAListViewItem *potentialView = [self viewAtIndex:(NSUInteger) index]; 1022 | if(respondsToShouldSelect) { 1023 | if([self.delegate listView:self shouldSelectView:potentialView]) { 1024 | previousView = potentialView; 1025 | } 1026 | } else { 1027 | previousView = potentialView; 1028 | } 1029 | 1030 | index--; 1031 | 1032 | } 1033 | 1034 | return previousView; 1035 | } 1036 | 1037 | - (void)selectView:(JAListViewItem *)view { 1038 | [self.window makeFirstResponder:view]; 1039 | view.selected = YES; 1040 | [self.currentlySelectedViews addObject:view]; 1041 | 1042 | if([self.delegate respondsToSelector:@selector(listView:didSelectView:)]) { 1043 | [self.delegate listView:self didSelectView:view]; 1044 | } 1045 | } 1046 | 1047 | - (void)deselectView:(JAListViewItem *)view { 1048 | if(![self.currentlySelectedViews containsObject:view]) return; 1049 | 1050 | [[view retain] autorelease]; 1051 | view.selected = NO; 1052 | [self.currentlySelectedViews removeObject:view]; 1053 | 1054 | if([self.delegate respondsToSelector:@selector(listView:didDeselectView:)]) { 1055 | [self.delegate listView:self didDeselectView:view]; 1056 | } 1057 | } 1058 | 1059 | - (void)deselectAllViews { 1060 | for(JAListViewItem *view in [[self.currentlySelectedViews copy] autorelease]) { 1061 | [self deselectView:view]; 1062 | } 1063 | } 1064 | 1065 | - (void)markViewBeingUsedForInertialScrolling:(JAListViewItem *)newView { 1066 | // This value is fairly arbitrary. I've seen it use 2 different views so we'll go one over that for now. 1067 | static const NSUInteger maxNumberOfViewsForInertialScrolling = 3; 1068 | 1069 | NSAssert(newView != nil, @"Cannot mark a nil view as being used for inertial scrolling."); 1070 | 1071 | [self.viewsBeingUsedForInertialScrolling addObject:newView]; 1072 | while(self.viewsBeingUsedForInertialScrolling.count > maxNumberOfViewsForInertialScrolling) { 1073 | [self.viewsBeingUsedForInertialScrolling removeObjectAtIndex:0]; 1074 | } 1075 | } 1076 | 1077 | - (void)unmarkViewBeingUsedForInertialScrolling:(JAListViewItem *)view { 1078 | [self.viewsBeingUsedForInertialScrolling removeObject:view]; 1079 | } 1080 | 1081 | - (void)clearViewsBeingUsedForInertialScrolling { 1082 | [self.viewsBeingUsedForInertialScrolling removeAllObjects]; 1083 | } 1084 | 1085 | @synthesize dataSource; 1086 | @synthesize delegate; 1087 | @synthesize draggingSourceDelegate; 1088 | @synthesize draggingDestinationDelegate; 1089 | @synthesize cachedVisibleViews; 1090 | @synthesize canCallDataSourceInParallel; 1091 | @synthesize margin; 1092 | @synthesize padding; 1093 | @synthesize viewBeingSelected; 1094 | @synthesize backgroundColor; 1095 | @synthesize heightForAllContent; 1096 | @synthesize conditionallyUseLayerBacking; 1097 | @synthesize currentlySelectedViews; 1098 | @synthesize currentTrackingArea; 1099 | @synthesize currentAnimationBlock; 1100 | @synthesize viewsBeingUsedForInertialScrolling; 1101 | @synthesize minIndexForReLayout; 1102 | @synthesize allowNoSelection; 1103 | 1104 | @end 1105 | -------------------------------------------------------------------------------- /JAListView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 11 | 256AC3DA0F4B6AC300CF3369 /* JAListViewAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 256AC3D90F4B6AC300CF3369 /* JAListViewAppDelegate.m */; }; 12 | 8842256A12AD686600A4B4D9 /* JAObjectListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8842256912AD686600A4B4D9 /* JAObjectListView.m */; }; 13 | 885CF18B12A044CC0069C4CF /* JASectionedListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 885CF18A12A044CC0069C4CF /* JASectionedListView.m */; }; 14 | 885CF19F12A045750069C4CF /* DemoSectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 885CF19E12A045750069C4CF /* DemoSectionView.m */; }; 15 | 885CF1A112A045A70069C4CF /* DemoSectionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 885CF1A012A045A70069C4CF /* DemoSectionView.xib */; }; 16 | 885CF38512A051AD0069C4CF /* NSIndexPath+JAListViewExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 885CF38412A051AD0069C4CF /* NSIndexPath+JAListViewExtensions.m */; }; 17 | 88DA161A1254594E00A1D5AA /* JAListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DA16191254594E00A1D5AA /* JAListView.m */; }; 18 | 88DA167A125461A200A1D5AA /* DemoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DA1679125461A200A1D5AA /* DemoView.m */; }; 19 | 88DA167E1254624A00A1D5AA /* DemoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 88DA167D1254624A00A1D5AA /* DemoView.xib */; }; 20 | 88DC4E7312AEDCC300F37F1C /* JAListViewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DC4E7212AEDCC300F37F1C /* JAListViewItem.m */; }; 21 | 88DC4E8912AEDD0000F37F1C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88DC4E8812AEDD0000F37F1C /* QuartzCore.framework */; }; 22 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 23 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 24 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 29 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 30 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 31 | 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 32 | 256AC3D80F4B6AC300CF3369 /* JAListViewAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JAListViewAppDelegate.h; sourceTree = ""; }; 33 | 256AC3D90F4B6AC300CF3369 /* JAListViewAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JAListViewAppDelegate.m; sourceTree = ""; }; 34 | 256AC3F00F4B6AF500CF3369 /* JAListView_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JAListView_Prefix.pch; sourceTree = ""; }; 35 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 36 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 37 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 38 | 8842256812AD686600A4B4D9 /* JAObjectListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JAObjectListView.h; sourceTree = ""; }; 39 | 8842256912AD686600A4B4D9 /* JAObjectListView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JAObjectListView.m; sourceTree = ""; }; 40 | 885CF05412A0369F0069C4CF /* JAListViewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JAListViewItem.h; sourceTree = ""; }; 41 | 885CF18912A044CC0069C4CF /* JASectionedListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JASectionedListView.h; sourceTree = ""; }; 42 | 885CF18A12A044CC0069C4CF /* JASectionedListView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JASectionedListView.m; sourceTree = ""; }; 43 | 885CF19D12A045750069C4CF /* DemoSectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoSectionView.h; sourceTree = ""; }; 44 | 885CF19E12A045750069C4CF /* DemoSectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoSectionView.m; sourceTree = ""; }; 45 | 885CF1A012A045A70069C4CF /* DemoSectionView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DemoSectionView.xib; sourceTree = ""; }; 46 | 885CF38312A051AD0069C4CF /* NSIndexPath+JAListViewExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexPath+JAListViewExtensions.h"; sourceTree = ""; }; 47 | 885CF38412A051AD0069C4CF /* NSIndexPath+JAListViewExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexPath+JAListViewExtensions.m"; sourceTree = ""; }; 48 | 888799BB12B2EB89006A9135 /* Demo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Demo-Info.plist"; sourceTree = ""; }; 49 | 88D30AC413249FEC00F32DB2 /* JAEdgeInsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JAEdgeInsets.h; sourceTree = ""; }; 50 | 88DA16181254594E00A1D5AA /* JAListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JAListView.h; sourceTree = ""; }; 51 | 88DA16191254594E00A1D5AA /* JAListView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JAListView.m; sourceTree = ""; }; 52 | 88DA1678125461A200A1D5AA /* DemoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoView.h; sourceTree = ""; }; 53 | 88DA1679125461A200A1D5AA /* DemoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoView.m; sourceTree = ""; }; 54 | 88DA167D1254624A00A1D5AA /* DemoView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DemoView.xib; sourceTree = ""; }; 55 | 88DC4E7212AEDCC300F37F1C /* JAListViewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JAListViewItem.m; sourceTree = ""; }; 56 | 88DC4E8812AEDD0000F37F1C /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 57 | 8D1107310486CEB800E47090 /* JAListView-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "JAListView-Info.plist"; sourceTree = ""; }; 58 | 8D1107320486CEB800E47090 /* JAListView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JAListView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 67 | 88DC4E8912AEDD0000F37F1C /* QuartzCore.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 080E96DDFE201D6D7F000001 /* Classes */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 888799BF12B2EBA3006A9135 /* JAListView */, 78 | 256AC3D80F4B6AC300CF3369 /* JAListViewAppDelegate.h */, 79 | 256AC3D90F4B6AC300CF3369 /* JAListViewAppDelegate.m */, 80 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, 81 | 88DA1678125461A200A1D5AA /* DemoView.h */, 82 | 88DA1679125461A200A1D5AA /* DemoView.m */, 83 | 88DA167D1254624A00A1D5AA /* DemoView.xib */, 84 | 885CF19D12A045750069C4CF /* DemoSectionView.h */, 85 | 885CF19E12A045750069C4CF /* DemoSectionView.m */, 86 | 885CF1A012A045A70069C4CF /* DemoSectionView.xib */, 87 | ); 88 | name = Classes; 89 | sourceTree = ""; 90 | }; 91 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 95 | 88DC4E8812AEDD0000F37F1C /* QuartzCore.framework */, 96 | ); 97 | name = "Linked Frameworks"; 98 | sourceTree = ""; 99 | }; 100 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 104 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 105 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 106 | ); 107 | name = "Other Frameworks"; 108 | sourceTree = ""; 109 | }; 110 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 8D1107320486CEB800E47090 /* JAListView.app */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 29B97314FDCFA39411CA2CEA /* JAListView */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 080E96DDFE201D6D7F000001 /* Classes */, 122 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 123 | 29B97317FDCFA39411CA2CEA /* Resources */, 124 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 125 | 19C28FACFE9D520D11CA2CBB /* Products */, 126 | 888799BB12B2EB89006A9135 /* Demo-Info.plist */, 127 | ); 128 | name = JAListView; 129 | sourceTree = ""; 130 | }; 131 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 256AC3F00F4B6AF500CF3369 /* JAListView_Prefix.pch */, 135 | 29B97316FDCFA39411CA2CEA /* main.m */, 136 | ); 137 | name = "Other Sources"; 138 | sourceTree = ""; 139 | }; 140 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 8D1107310486CEB800E47090 /* JAListView-Info.plist */, 144 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 145 | ); 146 | name = Resources; 147 | sourceTree = ""; 148 | }; 149 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 153 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 154 | ); 155 | name = Frameworks; 156 | sourceTree = ""; 157 | }; 158 | 888799BF12B2EBA3006A9135 /* JAListView */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 88DA16181254594E00A1D5AA /* JAListView.h */, 162 | 88DA16191254594E00A1D5AA /* JAListView.m */, 163 | 88D30AC413249FEC00F32DB2 /* JAEdgeInsets.h */, 164 | 885CF18912A044CC0069C4CF /* JASectionedListView.h */, 165 | 885CF18A12A044CC0069C4CF /* JASectionedListView.m */, 166 | 8842256812AD686600A4B4D9 /* JAObjectListView.h */, 167 | 8842256912AD686600A4B4D9 /* JAObjectListView.m */, 168 | 885CF05412A0369F0069C4CF /* JAListViewItem.h */, 169 | 88DC4E7212AEDCC300F37F1C /* JAListViewItem.m */, 170 | 885CF38312A051AD0069C4CF /* NSIndexPath+JAListViewExtensions.h */, 171 | 885CF38412A051AD0069C4CF /* NSIndexPath+JAListViewExtensions.m */, 172 | ); 173 | name = JAListView; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | 8D1107260486CEB800E47090 /* JAListView */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "JAListView" */; 182 | buildPhases = ( 183 | 8D1107290486CEB800E47090 /* Resources */, 184 | 8D11072C0486CEB800E47090 /* Sources */, 185 | 8D11072E0486CEB800E47090 /* Frameworks */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = JAListView; 192 | productInstallPath = "$(HOME)/Applications"; 193 | productName = JAListView; 194 | productReference = 8D1107320486CEB800E47090 /* JAListView.app */; 195 | productType = "com.apple.product-type.application"; 196 | }; 197 | /* End PBXNativeTarget section */ 198 | 199 | /* Begin PBXProject section */ 200 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 201 | isa = PBXProject; 202 | attributes = { 203 | ORGANIZATIONNAME = "Maybe Apps"; 204 | }; 205 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "JAListView" */; 206 | compatibilityVersion = "Xcode 3.1"; 207 | developmentRegion = English; 208 | hasScannedForEncodings = 1; 209 | knownRegions = ( 210 | English, 211 | Japanese, 212 | French, 213 | German, 214 | ); 215 | mainGroup = 29B97314FDCFA39411CA2CEA /* JAListView */; 216 | projectDirPath = ""; 217 | projectRoot = ""; 218 | targets = ( 219 | 8D1107260486CEB800E47090 /* JAListView */, 220 | ); 221 | }; 222 | /* End PBXProject section */ 223 | 224 | /* Begin PBXResourcesBuildPhase section */ 225 | 8D1107290486CEB800E47090 /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 230 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, 231 | 88DA167E1254624A00A1D5AA /* DemoView.xib in Resources */, 232 | 885CF1A112A045A70069C4CF /* DemoSectionView.xib in Resources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXSourcesBuildPhase section */ 239 | 8D11072C0486CEB800E47090 /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 244 | 256AC3DA0F4B6AC300CF3369 /* JAListViewAppDelegate.m in Sources */, 245 | 88DA161A1254594E00A1D5AA /* JAListView.m in Sources */, 246 | 88DA167A125461A200A1D5AA /* DemoView.m in Sources */, 247 | 885CF18B12A044CC0069C4CF /* JASectionedListView.m in Sources */, 248 | 885CF19F12A045750069C4CF /* DemoSectionView.m in Sources */, 249 | 885CF38512A051AD0069C4CF /* NSIndexPath+JAListViewExtensions.m in Sources */, 250 | 8842256A12AD686600A4B4D9 /* JAObjectListView.m in Sources */, 251 | 88DC4E7312AEDCC300F37F1C /* JAListViewItem.m in Sources */, 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | /* End PBXSourcesBuildPhase section */ 256 | 257 | /* Begin PBXVariantGroup section */ 258 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { 259 | isa = PBXVariantGroup; 260 | children = ( 261 | 089C165DFE840E0CC02AAC07 /* English */, 262 | ); 263 | name = InfoPlist.strings; 264 | sourceTree = ""; 265 | }; 266 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { 267 | isa = PBXVariantGroup; 268 | children = ( 269 | 1DDD58150DA1D0A300B32029 /* English */, 270 | ); 271 | name = MainMenu.xib; 272 | sourceTree = ""; 273 | }; 274 | /* End PBXVariantGroup section */ 275 | 276 | /* Begin XCBuildConfiguration section */ 277 | C01FCF4B08A954540054247B /* Debug */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 282 | COPY_PHASE_STRIP = NO; 283 | GCC_DYNAMIC_NO_PIC = NO; 284 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 285 | GCC_MODEL_TUNING = G5; 286 | GCC_OPTIMIZATION_LEVEL = 0; 287 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 288 | GCC_PREFIX_HEADER = JAListView_Prefix.pch; 289 | INFOPLIST_FILE = "JAListView-Info.plist"; 290 | INSTALL_PATH = "$(HOME)/Applications"; 291 | PRODUCT_NAME = JAListView; 292 | }; 293 | name = Debug; 294 | }; 295 | C01FCF4C08A954540054247B /* Release */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 300 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 301 | GCC_MODEL_TUNING = G5; 302 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 303 | GCC_PREFIX_HEADER = JAListView_Prefix.pch; 304 | INFOPLIST_FILE = "JAListView-Info.plist"; 305 | INSTALL_PATH = "$(HOME)/Applications"; 306 | PRODUCT_NAME = JAListView; 307 | }; 308 | name = Release; 309 | }; 310 | C01FCF4F08A954540054247B /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 314 | GCC_C_LANGUAGE_STANDARD = gnu99; 315 | GCC_ENABLE_OBJC_GC = required; 316 | GCC_OPTIMIZATION_LEVEL = 0; 317 | GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; 318 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 319 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 321 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 322 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 323 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 324 | GCC_WARN_MISSING_PARENTHESES = YES; 325 | GCC_WARN_SHADOW = YES; 326 | GCC_WARN_SIGN_COMPARE = YES; 327 | GCC_WARN_STRICT_SELECTOR_MATCH = NO; 328 | GCC_WARN_UNDECLARED_SELECTOR = YES; 329 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 330 | GCC_WARN_UNUSED_VALUE = YES; 331 | GCC_WARN_UNUSED_VARIABLE = YES; 332 | ONLY_ACTIVE_ARCH = YES; 333 | PREBINDING = NO; 334 | SDKROOT = macosx10.6; 335 | WARNING_CFLAGS = ( 336 | "-Werror", 337 | "-Wall", 338 | ); 339 | }; 340 | name = Debug; 341 | }; 342 | C01FCF5008A954540054247B /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 346 | GCC_C_LANGUAGE_STANDARD = gnu99; 347 | GCC_ENABLE_OBJC_GC = required; 348 | GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; 349 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 350 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 353 | GCC_WARN_SHADOW = YES; 354 | GCC_WARN_SIGN_COMPARE = YES; 355 | GCC_WARN_STRICT_SELECTOR_MATCH = NO; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 358 | GCC_WARN_UNUSED_VALUE = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | PREBINDING = NO; 361 | SDKROOT = macosx10.6; 362 | WARNING_CFLAGS = ( 363 | "-Werror", 364 | "-Wall", 365 | ); 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "JAListView" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | C01FCF4B08A954540054247B /* Debug */, 376 | C01FCF4C08A954540054247B /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "JAListView" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | C01FCF4F08A954540054247B /* Debug */, 385 | C01FCF5008A954540054247B /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | }; 392 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 393 | } 394 | -------------------------------------------------------------------------------- /JAListView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /JAListViewAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // JAListViewAppDelegate.h 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 9/29/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JAListView.h" 11 | #import "JASectionedListView.h" 12 | 13 | 14 | @interface JAListViewAppDelegate : NSObject { 15 | NSWindow *window; 16 | JAListView *listView; 17 | JASectionedListView *sectionedListView; 18 | } 19 | 20 | @property (assign) IBOutlet NSWindow *window; 21 | @property (assign) IBOutlet JAListView *listView; 22 | @property (assign) IBOutlet JASectionedListView *sectionedListView; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /JAListViewAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // JAListViewAppDelegate.m 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 9/29/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import "JAListViewAppDelegate.h" 10 | #import "DemoView.h" 11 | #import "DemoSectionView.h" 12 | 13 | 14 | @implementation JAListViewAppDelegate 15 | 16 | 17 | #pragma mark NSApplicationDelegate 18 | 19 | - (void)applicationDidFinishLaunching:(NSNotification *)notification { 20 | self.listView.canCallDataSourceInParallel = YES; 21 | self.sectionedListView.canCallDataSourceInParallel = YES; 22 | 23 | [self.listView reloadData]; 24 | [self.sectionedListView reloadData]; 25 | 26 | [self.window makeKeyAndOrderFront:nil]; 27 | } 28 | 29 | 30 | #pragma mark JAListViewDelegate 31 | 32 | - (void)listView:(JAListView *)list willSelectView:(JAListViewItem *)view { 33 | if(list == self.listView) { 34 | DemoView *demoView = (DemoView *) view; 35 | demoView.selected = YES; 36 | } 37 | } 38 | 39 | - (void)listView:(JAListView *)list didSelectView:(JAListViewItem *)view { 40 | if(list == self.listView) { 41 | DemoView *demoView = (DemoView *) view; 42 | demoView.selected = NO; 43 | } 44 | } 45 | 46 | - (void)listView:(JAListView *)list didUnSelectView:(JAListViewItem *)view { 47 | if(list == self.listView) { 48 | DemoView *demoView = (DemoView *) view; 49 | demoView.selected = NO; 50 | } 51 | } 52 | 53 | 54 | #pragma mark JAListViewDataSource 55 | 56 | - (NSUInteger)numberOfItemsInListView:(JAListView *)listView { 57 | return 100; 58 | } 59 | 60 | - (JAListViewItem *)listView:(JAListView *)listView viewAtIndex:(NSUInteger)index { 61 | DemoView *view = [DemoView demoView]; 62 | view.text = [NSString stringWithFormat:@"Row %d", index + 1]; 63 | return view; 64 | } 65 | 66 | 67 | #pragma mark JASectionedListViewDataSource 68 | 69 | - (NSUInteger)numberOfSectionsInListView:(JASectionedListView *)listView { 70 | return 3; 71 | } 72 | 73 | - (NSUInteger)listView:(JASectionedListView *)listView numberOfViewsInSection:(NSUInteger)section { 74 | if(section == 0) { 75 | return 10; 76 | } else if(section == 1) { 77 | return 2; 78 | } else if(section == 2) { 79 | return 7; 80 | } 81 | 82 | return 0; 83 | } 84 | 85 | - (JAListViewItem *)listView:(JAListView *)listView sectionHeaderViewForSection:(NSUInteger)section { 86 | DemoSectionView *view = [DemoSectionView demoSectionView]; 87 | view.text = [NSString stringWithFormat:@"Section %d", section + 1]; 88 | return view; 89 | } 90 | 91 | - (JAListViewItem *)listView:(JAListView *)listView viewForSection:(NSUInteger)section index:(NSUInteger)index { 92 | DemoView *view = [DemoView demoView]; 93 | view.text = [NSString stringWithFormat:@"Section %d: Row %d", section + 1, index + 1]; 94 | return view; 95 | } 96 | 97 | 98 | #pragma mark NSSplitViewDelegate 99 | 100 | - (void)splitViewWillResizeSubviews:(NSNotification *)aNotification { 101 | if(listView.frame.size.width < 1) { 102 | [listView reloadData]; 103 | } else if(sectionedListView.frame.size.width < 1) { 104 | [sectionedListView reloadData]; 105 | } 106 | } 107 | 108 | 109 | #pragma mark API 110 | 111 | @synthesize window; 112 | @synthesize listView; 113 | @synthesize sectionedListView; 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /JAListViewItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // JAListViewItem.h 3 | // 4 | // Created by Josh Abernathy on 10/27/10. 5 | // Copyright 2010 Maybe Apps. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | typedef enum { 11 | JAListViewPositionTop = 1 << 1, 12 | JAListViewPositionMiddle = 1 << 2, 13 | JAListViewPositionBottom = 1 << 3, 14 | JAListViewPositionNone = 0, 15 | } JAListViewPosition; 16 | 17 | @class JAListView; 18 | 19 | 20 | @interface JAListViewItem : NSView {} 21 | 22 | - (NSImage *)draggingImage; 23 | 24 | @property (nonatomic, assign) JAListView *listView; 25 | @property (nonatomic, assign) BOOL ignoreInListViewLayout; 26 | @property (nonatomic, assign, getter=isSelected) BOOL selected; 27 | @property (nonatomic, assign, getter=isHighlighted) BOOL highlighted; 28 | @property (nonatomic, readonly) JAListViewPosition listViewPosition; 29 | @property (nonatomic, assign) BOOL ignoresListViewPadding; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /JAListViewItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // JAListViewItem.m 3 | // 4 | // Created by Josh Abernathy on 10/27/10. 5 | // Copyright 2010 Maybe Apps. All rights reserved. 6 | // 7 | 8 | #import "JAListViewItem.h" 9 | #import "JAListView.h" 10 | #import "JASectionedListView.h" 11 | 12 | 13 | @implementation JAListViewItem 14 | 15 | 16 | #pragma mark NSView 17 | 18 | - (void)scrollWheel:(NSEvent *)event { 19 | [self.listView markViewBeingUsedForInertialScrolling:self]; 20 | [super scrollWheel:event]; 21 | } 22 | 23 | 24 | #pragma mark NSResponder 25 | 26 | - (BOOL)acceptsFirstResponder { 27 | return YES; 28 | } 29 | 30 | 31 | #pragma mark API 32 | 33 | @synthesize ignoreInListViewLayout; 34 | @synthesize listView; 35 | @synthesize selected; 36 | @synthesize highlighted; 37 | @synthesize listViewPosition; 38 | @synthesize ignoresListViewPadding; 39 | 40 | - (NSImage *)draggingImage { 41 | NSBitmapImageRep *bitmap = [self bitmapImageRepForCachingDisplayInRect:self.bounds]; 42 | [self cacheDisplayInRect:self.bounds toBitmapImageRep:bitmap]; 43 | 44 | NSSize imageSize = [bitmap size]; 45 | NSImage *image = [[[NSImage alloc] initWithSize:imageSize] autorelease]; 46 | [image addRepresentation:bitmap]; 47 | 48 | NSImage *result = [[[NSImage alloc] initWithSize:imageSize] autorelease]; 49 | [result lockFocus]; 50 | NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 51 | NSImageInterpolation savedInterpolation = [currentContext imageInterpolation]; 52 | [currentContext setImageInterpolation:NSImageInterpolationHigh]; 53 | [image drawInRect:NSMakeRect(0, 0, imageSize.width, imageSize.height) fromRect:NSMakeRect(0, 0, imageSize.width, imageSize.height) operation:NSCompositeSourceOver fraction:.5]; 54 | [currentContext setImageInterpolation:savedInterpolation]; 55 | [result unlockFocus]; 56 | 57 | return result; 58 | } 59 | 60 | - (JAListViewPosition)listViewPosition { 61 | JAListViewPosition position = JAListViewPositionNone; 62 | NSUInteger index = (NSUInteger) [self.listView indexForView:self]; 63 | NSUInteger numberOfViews = [self.listView numberOfViews]; 64 | 65 | if([self.listView isKindOfClass:[JASectionedListView class]]) { 66 | JASectionedListView *sectionedListView = (JASectionedListView *) self.listView; 67 | NSUInteger section = 0; 68 | NSUInteger newIndex = index; 69 | [sectionedListView getSection:§ion andIndex:&newIndex fromAbsoluteIndex:index]; 70 | index = newIndex; 71 | numberOfViews = [sectionedListView numberOfViewsInSection:section]; 72 | } 73 | 74 | if(index == numberOfViews - 1) { 75 | position |= JAListViewPositionBottom; 76 | } 77 | 78 | if(index == 0) { 79 | position |= JAListViewPositionTop; 80 | } 81 | 82 | if(position == JAListViewPositionNone) { 83 | position = JAListViewPositionMiddle; 84 | } 85 | 86 | return position; 87 | } 88 | 89 | - (void)setSelected:(BOOL)newValue { 90 | selected = newValue; 91 | 92 | [self setNeedsDisplay:YES]; 93 | } 94 | 95 | - (void)setHighlighted:(BOOL)newValue { 96 | highlighted = newValue; 97 | 98 | [self setNeedsDisplay:YES]; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /JAListView_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'JAListView' target in the 'JAListView' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /JAObjectListView.h: -------------------------------------------------------------------------------- 1 | // 2 | // JAObjectListView.h 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 12/6/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JASectionedListView.h" 11 | 12 | @class JAObjectListViewItem; 13 | 14 | 15 | @interface JAObjectListView : JASectionedListView {} 16 | 17 | - (void)addListViewItem:(JAObjectListViewItem *)view inSection:(NSUInteger)section atIndex:(NSUInteger)index; 18 | - (void)addListViewItem:(JAObjectListViewItem *)view inSection:(NSUInteger)section; 19 | - (void)removeListViewItemInSection:(NSUInteger)section atIndex:(NSUInteger)index; 20 | 21 | - (void)addListViewItem:(JAObjectListViewItem *)view forHeaderForSection:(NSUInteger)section; 22 | - (void)removeListViewItemForHeaderForSection:(NSUInteger)section; 23 | 24 | - (void)removeListViewItem:(JAListViewItem *)view; 25 | - (void)removeAllListViewItems; 26 | - (NSArray *)viewsInSection:(NSUInteger)section; 27 | - (NSUInteger)numberOfSections; 28 | 29 | - (JAListViewItem *)headerForSection:(NSUInteger)section; 30 | - (JAObjectListViewItem *)viewItemForObject:(id)object; 31 | - (NSIndexPath *)indexPathForObject:(id)object; 32 | - (void)removeListViewItemForObject:(id)object; 33 | - (NSArray *)allObjects; 34 | 35 | - (NSString *)debugString; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /JAObjectListView.m: -------------------------------------------------------------------------------- 1 | // 2 | // JAObjectListView.m 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 12/6/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import "JAObjectListView.h" 10 | #import "JAObjectListViewItem.h" 11 | 12 | @interface JAListView (Private) 13 | - (NSArray *)cachedViews; 14 | @end 15 | 16 | @interface JAObjectListView () 17 | - (void)reallyAddListViewItem:(JAObjectListViewItem *)view inSection:(NSUInteger)section atIndex:(NSUInteger)index; 18 | 19 | @property (nonatomic, retain) NSMutableArray *sectionRowViews; 20 | @end 21 | 22 | 23 | @implementation JAObjectListView 24 | 25 | - (void)dealloc { 26 | self.sectionRowViews = nil; 27 | 28 | [super dealloc]; 29 | } 30 | 31 | 32 | #pragma mark JAListView 33 | 34 | - (void)setup { 35 | [super setup]; 36 | 37 | self.sectionDataSource = self; 38 | self.sectionRowViews = [NSMutableArray array]; 39 | } 40 | 41 | 42 | #pragma mark JASectionedListView 43 | 44 | - (void)setSectionDataSource:(id)newDataSource { 45 | NSAssert([newDataSource isKindOfClass:[self class]], @"Do not set the sectionedDataSource manually."); 46 | 47 | [super setSectionDataSource:newDataSource]; 48 | } 49 | 50 | 51 | #pragma mark JASectionedListViewDataSource 52 | 53 | - (NSUInteger)numberOfSectionsInListView:(JASectionedListView *)listView { 54 | return [self numberOfSections]; 55 | } 56 | 57 | - (NSUInteger)listView:(JASectionedListView *)listView numberOfViewsInSection:(NSUInteger)section { 58 | return [self viewsInSection:section].count; 59 | } 60 | 61 | - (JAListViewItem *)listView:(JAListView *)listView sectionHeaderViewForSection:(NSUInteger)section { 62 | return [[self.sectionRowViews objectAtIndex:section] objectAtIndex:0]; 63 | } 64 | 65 | - (JAListViewItem *)listView:(JAListView *)listView viewForSection:(NSUInteger)section index:(NSUInteger)index { 66 | return [[self viewsInSection:section] objectAtIndex:index]; 67 | } 68 | 69 | 70 | #pragma mark API 71 | 72 | @synthesize sectionRowViews; 73 | 74 | - (void)addListViewItem:(JAObjectListViewItem *)view inSection:(NSUInteger)section atIndex:(NSUInteger)index { 75 | [self reallyAddListViewItem:view inSection:section atIndex:index + 1]; 76 | } 77 | 78 | - (void)reallyAddListViewItem:(JAObjectListViewItem *)view inSection:(NSUInteger)section atIndex:(NSUInteger)index { 79 | NSMutableArray *sectionViews = nil; 80 | if(section < self.sectionRowViews.count) { 81 | sectionViews = [self.sectionRowViews objectAtIndex:section]; 82 | } else if(section != NSNotFound) { 83 | sectionViews = [NSMutableArray array]; 84 | [self.sectionRowViews insertObject:sectionViews atIndex:section]; 85 | } else { 86 | NSAssert1(NO, @"Tried to insert view into a non-existent section: %@", view); 87 | } 88 | 89 | if(index != NSNotFound) { 90 | [sectionViews insertObject:view atIndex:index]; 91 | } else { 92 | NSAssert1(NO, @"Tried to insert view into a non-existent row: %@", view); 93 | } 94 | } 95 | 96 | - (void)addListViewItem:(JAObjectListViewItem *)view inSection:(NSUInteger)section { 97 | [self addListViewItem:view inSection:section atIndex:[self viewsInSection:section].count]; 98 | } 99 | 100 | - (void)removeListViewItemInSection:(NSUInteger)section atIndex:(NSUInteger)index { 101 | NSMutableArray *sectionViews = [self.sectionRowViews objectAtIndex:section]; 102 | 103 | NSUInteger trueIndex = index + 1; 104 | JAObjectListViewItem *view = [sectionViews objectAtIndex:trueIndex]; 105 | [self unmarkViewBeingUsedForInertialScrolling:view]; 106 | 107 | [self deselectView:view]; 108 | 109 | [sectionViews removeObjectAtIndex:trueIndex]; 110 | } 111 | 112 | - (void)addListViewItem:(JAObjectListViewItem *)view forHeaderForSection:(NSUInteger)section { 113 | NSMutableArray *sectionViews = [NSMutableArray array]; 114 | [self.sectionRowViews insertObject:sectionViews atIndex:section]; 115 | [sectionViews insertObject:view atIndex:0]; 116 | } 117 | 118 | - (void)removeListViewItemForHeaderForSection:(NSUInteger)section { 119 | NSArray *views = [self.sectionRowViews objectAtIndex:section]; 120 | for(JAObjectListViewItem *item in views) { 121 | [self unmarkViewBeingUsedForInertialScrolling:item]; 122 | 123 | [self deselectView:item]; 124 | } 125 | 126 | [self.sectionRowViews removeObjectAtIndex:section]; 127 | } 128 | 129 | - (void)removeListViewItem:(JAListViewItem *)view { 130 | [self unmarkViewBeingUsedForInertialScrolling:view]; 131 | 132 | [self deselectView:view]; 133 | 134 | for(NSUInteger sectionIndex = 0; sectionIndex < self.sectionRowViews.count; sectionIndex++) { 135 | NSMutableArray *sectionViews = [self.sectionRowViews objectAtIndex:sectionIndex]; 136 | NSUInteger index = [sectionViews indexOfObject:view]; 137 | if(index != NSNotFound) { 138 | if(index == 0) { 139 | [self removeListViewItemForHeaderForSection:sectionIndex]; 140 | } else { 141 | [sectionViews removeObjectAtIndex:index]; 142 | } 143 | } 144 | } 145 | } 146 | 147 | - (void)removeAllListViewItems { 148 | [self clearViewsBeingUsedForInertialScrolling]; 149 | [self deselectAllViews]; 150 | [self.sectionRowViews removeAllObjects]; 151 | } 152 | 153 | - (NSArray *)viewsInSection:(NSUInteger)section { 154 | NSParameterAssert(section < self.sectionRowViews.count); 155 | 156 | NSMutableArray *views = [[[self.sectionRowViews objectAtIndex:section] mutableCopy] autorelease]; 157 | if(views.count < 1) return nil; 158 | 159 | [views removeObjectAtIndex:0]; 160 | return views; 161 | } 162 | 163 | - (NSUInteger)numberOfSections { 164 | return self.sectionRowViews.count; 165 | } 166 | 167 | - (JAObjectListViewItem *)viewItemForObject:(id)object { 168 | for(NSArray *views in self.sectionRowViews) { 169 | for(JAObjectListViewItem *item in views) { 170 | if([item.object isEqual:object]) { 171 | return item; 172 | } 173 | } 174 | } 175 | 176 | return nil; 177 | } 178 | 179 | - (NSIndexPath *)indexPathForObject:(id)object { 180 | NSAssert(object != nil, @"Invalid to look for the index path of a nil object."); 181 | 182 | NSUInteger section = 0; 183 | for(NSArray *views in self.sectionRowViews) { 184 | 185 | NSUInteger index = 0; 186 | for(JAObjectListViewItem *item in views) { 187 | if([item.object isEqual:object]) { 188 | if(index == 0) { 189 | return [NSIndexPath indexPathForIndex:JASectionedListViewHeaderIndex inSection:section]; 190 | } else { 191 | return [NSIndexPath indexPathForIndex:index - 1 inSection:section]; 192 | } 193 | } 194 | 195 | index++; 196 | } 197 | 198 | section++; 199 | } 200 | 201 | return nil; 202 | } 203 | 204 | - (JAListViewItem *)headerForSection:(NSUInteger)section { 205 | return [[self.sectionRowViews objectAtIndex:section] objectAtIndex:0]; 206 | } 207 | 208 | - (void)removeListViewItemForObject:(id)object { 209 | [self removeListViewItem:[self viewItemForObject:object]]; 210 | } 211 | 212 | - (NSArray *)allObjects { 213 | NSMutableArray *objects = [NSMutableArray array]; 214 | for(NSArray *views in self.sectionRowViews) { 215 | for(JAObjectListViewItem *item in views) { 216 | if(item.object != nil) [objects addObject:item.object]; 217 | } 218 | } 219 | 220 | return objects; 221 | } 222 | 223 | - (NSString *)debugString { 224 | return [NSString stringWithFormat:@"%@", self.sectionRowViews]; 225 | } 226 | 227 | @end 228 | -------------------------------------------------------------------------------- /JAObjectListViewItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // JAObjectListViewItem.h 3 | // GitHub 4 | // 5 | // Created by Josh Abernathy on 12/29/10. 6 | // Copyright 2010 GitHub. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JAListViewItem.h" 11 | 12 | 13 | @interface JAObjectListViewItem : JAListViewItem {} 14 | 15 | @property (nonatomic, retain) id object; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /JAObjectListViewItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // JAObjectListViewItem.m 3 | // GitHub 4 | // 5 | // Created by Josh Abernathy on 12/29/10. 6 | // Copyright 2010 GitHub. All rights reserved. 7 | // 8 | 9 | #import "JAObjectListViewItem.h" 10 | 11 | 12 | @implementation JAObjectListViewItem 13 | 14 | 15 | #pragma mark API 16 | 17 | @synthesize object; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /JASectionedListView.h: -------------------------------------------------------------------------------- 1 | // 2 | // JASectionedListView.h 3 | // DemoApp 4 | // 5 | // Created by Josh Abernathy on 11/26/10. 6 | // Copyright 2010 Maybe Apps, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JAListView.h" 11 | #import "NSIndexPath+JAListViewExtensions.h" 12 | 13 | enum { JASectionedListViewHeaderIndex = NSUIntegerMax }; 14 | 15 | @class JASectionedListView; 16 | 17 | @protocol JASectionedListViewDataSource 18 | - (NSUInteger)numberOfSectionsInListView:(JASectionedListView *)listView; 19 | - (NSUInteger)listView:(JASectionedListView *)listView numberOfViewsInSection:(NSUInteger)section; 20 | - (JAListViewItem *)listView:(JAListView *)listView sectionHeaderViewForSection:(NSUInteger)section; 21 | - (JAListViewItem *)listView:(JAListView *)listView viewForSection:(NSUInteger)section index:(NSUInteger)index; 22 | @end 23 | 24 | 25 | @interface JASectionedListView : JAListView {} 26 | 27 | - (NSUInteger)numberOfSections; 28 | - (NSUInteger)numberOfViewsInSection:(NSUInteger)section; 29 | 30 | - (JAListViewItem *)viewInSection:(NSUInteger)section atIndex:(NSUInteger)index; 31 | - (JAListViewItem *)viewAtIndexPath:(NSIndexPath *)indexPath; 32 | 33 | - (NSUInteger)absoluteIndexFromSection:(NSUInteger)section index:(NSUInteger)index; 34 | - (NSUInteger)absoluteIndexFromIndexPath:(NSIndexPath *)indexPath; 35 | 36 | - (NSIndexPath *)indexPathForView:(JAListViewItem *)view; 37 | 38 | - (BOOL)isViewSectionHeaderView:(JAListViewItem *)view; 39 | 40 | /* 41 | * If the absolute index is a header view, index will be set to JASectionedListViewHeaderIndex. 42 | */ 43 | - (void)getSection:(NSUInteger *)section andIndex:(NSUInteger *)index fromAbsoluteIndex:(NSUInteger)absoluteIndex; 44 | - (NSIndexPath *)indexPathFromAbsoluteIndex:(NSUInteger)absoluteIndex; 45 | 46 | @property (nonatomic, assign) IBOutlet id sectionDataSource; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /JASectionedListView.m: -------------------------------------------------------------------------------- 1 | // 2 | // JASectionedListView.m 3 | // DemoApp 4 | // 5 | // Created by Josh Abernathy on 11/26/10. 6 | // Copyright 2010 Maybe Apps, LLC. All rights reserved. 7 | // 8 | 9 | #import "JASectionedListView.h" 10 | #import "JAListViewItem.h" 11 | 12 | @interface JASectionedListView () 13 | - (NSUInteger)numberOfTotalViews; 14 | @end 15 | 16 | 17 | @implementation JASectionedListView 18 | 19 | 20 | #pragma mark JAListView 21 | 22 | - (void)setup { 23 | [super setup]; 24 | 25 | self.dataSource = self; 26 | } 27 | 28 | - (void)setDataSource:(id)newDataSource { 29 | NSAssert1([newDataSource isKindOfClass:[self class]], @"Please use sectionDataSource for %@.", NSStringFromClass([self class])); 30 | 31 | [super setDataSource:newDataSource]; 32 | } 33 | 34 | 35 | #pragma mark JAListViewDataSource 36 | 37 | - (NSUInteger)numberOfItemsInListView:(JAListView *)listView { 38 | return [self numberOfTotalViews]; 39 | } 40 | 41 | - (JAListViewItem *)listView:(JAListView *)listView viewAtIndex:(NSUInteger)index { 42 | NSUInteger relativeIndex; 43 | NSUInteger section; 44 | 45 | [self getSection:§ion andIndex:&relativeIndex fromAbsoluteIndex:index]; 46 | 47 | if(relativeIndex == JASectionedListViewHeaderIndex) { 48 | return [self.sectionDataSource listView:self sectionHeaderViewForSection:section]; 49 | } 50 | 51 | return [self.sectionDataSource listView:self viewForSection:section index:relativeIndex]; 52 | } 53 | 54 | 55 | #pragma mark API 56 | 57 | @synthesize sectionDataSource; 58 | 59 | - (void)getSection:(NSUInteger *)section andIndex:(NSUInteger *)index fromAbsoluteIndex:(NSUInteger)absoluteIndex { 60 | *index = absoluteIndex; 61 | for(NSUInteger sectionIndex = 0; sectionIndex < [self numberOfSections]; sectionIndex++) { 62 | NSUInteger numberOfViews = [self numberOfViewsInSection:sectionIndex] + 1; 63 | if(*index < numberOfViews) { 64 | *section = sectionIndex; 65 | break; 66 | } else { 67 | *index -= numberOfViews; 68 | } 69 | } 70 | 71 | // We want index 0 to be the first row, not the header view, so we reinterpret that as JASectionedListViewHeaderIndex. 72 | if(*index == 0) { 73 | *index = JASectionedListViewHeaderIndex; 74 | } else { 75 | *index -= 1; 76 | } 77 | } 78 | 79 | - (NSIndexPath *)indexPathFromAbsoluteIndex:(NSUInteger)absoluteIndex { 80 | NSUInteger section; 81 | NSUInteger index; 82 | [self getSection:§ion andIndex:&index fromAbsoluteIndex:absoluteIndex]; 83 | return [NSIndexPath indexPathForIndex:index inSection:section]; 84 | } 85 | 86 | - (NSUInteger)absoluteIndexFromSection:(NSUInteger)section index:(NSUInteger)index { 87 | NSUInteger absoluteIndex = 0; 88 | for(NSUInteger sectionIndex = 0; sectionIndex < section; sectionIndex++) { 89 | NSUInteger numberOfViews = [self numberOfViewsInSection:sectionIndex] + 1; 90 | absoluteIndex += numberOfViews; 91 | } 92 | 93 | absoluteIndex += index + 1; 94 | return absoluteIndex; 95 | } 96 | 97 | - (NSUInteger)absoluteIndexFromIndexPath:(NSIndexPath *)indexPath { 98 | return [self absoluteIndexFromSection:indexPath.section index:indexPath.index]; 99 | } 100 | 101 | - (NSIndexPath *)indexPathForView:(JAListViewItem *)view { 102 | return [self indexPathFromAbsoluteIndex:(NSUInteger) [self indexForView:view]]; 103 | } 104 | 105 | - (BOOL)isViewSectionHeaderView:(JAListViewItem *)view { 106 | return [self indexPathForView:view].index == JASectionedListViewHeaderIndex; 107 | } 108 | 109 | - (JAListViewItem *)viewInSection:(NSUInteger)section atIndex:(NSUInteger)index { 110 | return [self viewAtIndex:[self absoluteIndexFromSection:section index:index]]; 111 | } 112 | 113 | - (JAListViewItem *)viewAtIndexPath:(NSIndexPath *)indexPath { 114 | return [self viewInSection:indexPath.section atIndex:indexPath.index]; 115 | } 116 | 117 | - (NSUInteger)numberOfSections { 118 | return [self.sectionDataSource numberOfSectionsInListView:self]; 119 | } 120 | 121 | - (NSUInteger)numberOfViewsInSection:(NSUInteger)section { 122 | return [self.sectionDataSource listView:self numberOfViewsInSection:section]; 123 | } 124 | 125 | - (NSUInteger)numberOfTotalViews { 126 | // one section header view per section 127 | NSUInteger totalCount = [self numberOfSections]; 128 | for(NSUInteger sectionIndex = 0; sectionIndex < [self numberOfSections]; sectionIndex++) { 129 | totalCount += [self numberOfViewsInSection:sectionIndex]; 130 | } 131 | 132 | return totalCount; 133 | } 134 | 135 | - (void)setSectionDataSource:(id )newDataSource { 136 | sectionDataSource = newDataSource; 137 | 138 | [self reloadData]; 139 | } 140 | 141 | @end 142 | -------------------------------------------------------------------------------- /NSIndexPath+JAListViewExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+JAListViewExtensions.h 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 11/26/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface NSIndexPath (JAListViewExtensions) 13 | + (NSIndexPath *)indexPathForIndex:(NSUInteger)index inSection:(NSUInteger)section; 14 | + (NSIndexPath *)indexPathForSection:(NSUInteger)section; 15 | 16 | @property (nonatomic, readonly) NSUInteger index; 17 | @property (nonatomic, readonly) NSUInteger section; 18 | @end 19 | -------------------------------------------------------------------------------- /NSIndexPath+JAListViewExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+JAListViewExtensions.m 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 11/26/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import "NSIndexPath+JAListViewExtensions.h" 10 | 11 | 12 | @implementation NSIndexPath (JAListViewExtensions) 13 | 14 | + (NSIndexPath *)indexPathForIndex:(NSUInteger)index inSection:(NSUInteger)section { 15 | NSUInteger indices[2]; 16 | indices[0] = section; 17 | indices[1] = index; 18 | return [NSIndexPath indexPathWithIndexes:indices length:2]; 19 | } 20 | 21 | + (NSIndexPath *)indexPathForSection:(NSUInteger)section { 22 | return [NSIndexPath indexPathWithIndex:section]; 23 | } 24 | 25 | - (NSUInteger)index { 26 | return [self indexAtPosition:1]; 27 | } 28 | 29 | - (NSUInteger)section { 30 | return [self indexAtPosition:0]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Stock New BSD License: 2 | 3 | Copyright (c) 2010, Josh Abernathy 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | * Neither the name of the Josh Abernathy nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | 14 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JAListView 4 | // 5 | // Created by Josh Abernathy on 9/29/10. 6 | // Copyright 2010 Maybe Apps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | srandom((unsigned int) time(NULL)); 14 | 15 | return NSApplicationMain(argc, (const char **) argv); 16 | } 17 | --------------------------------------------------------------------------------