Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ADBIndexedTableView component and demo project
- Loading branch information
1 parent
89d5179
commit d71dd2f
Showing
10 changed files
with
501 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Xcode | ||
build/* | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
*.xcworkspace | ||
!default.xcworkspace | ||
xcuserdata | ||
profile | ||
*.moved-aside | ||
.DS_Store | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// | ||
// ADBIndexedTableView.h | ||
// PassDesk | ||
// | ||
// Created by Alberto De Bortoli on 11/4/12. | ||
// Copyright (c) 2012 Alberto De Bortoli. All rights reserved. | ||
// | ||
// ADBIndexedTableView inherits from UITableView (use it as a UITableView). | ||
// | ||
// 1. Set up delegate and dataSource as usual (using Interface Builder or programmatically). | ||
// 2. Set indexDataSource and implement 'objectsFieldForIndexedTableView:' method | ||
// (used for sorting and indexing). | ||
// 3. dataSource 'tableView:cellForRowAtIndexPath' implementation can retrieve object for | ||
// the given indexPath using 'objectAtIndexPath:' method. | ||
// 4. Implementing 'indexedTableView:cellForRowAtIndexPath:objectAtIndexPath:' is required | ||
// and it will be used only if 'tableView:cellForRowAtIndexPath' implementation is not | ||
// provided by the dataSource. | ||
// 5. Reload the table sending unsorted objects via 'reloadDataWithObjects:' method to let | ||
// indexedTableView create the data structure. Use 'reloadData' for subsequent reloadings. | ||
// | ||
|
||
#import <UIKit/UIKit.h> | ||
#import "ADBMessageInterceptor.h" | ||
|
||
@class ADBIndexedTableView; | ||
|
||
@protocol ADBIndexedTableViewDataSource <NSObject> | ||
|
||
@required | ||
/** | ||
@return field used to retrieve the first letter (used for index) | ||
@param tableView, the caller | ||
Return value will be used for KVC on 'indexedObjects' objects | ||
*/ | ||
- (NSString *)objectsFieldForIndexedTableView:(ADBIndexedTableView *)tableView; | ||
|
||
@optional | ||
/** | ||
@return cell for row at index path | ||
@param tableView, the caller | ||
@param indexPath, the indexPath | ||
@param object, the object at indexPath | ||
Surrogate method for dataSource method 'tableView:cellForRowAtIndexPath:' | ||
Required only if 'tableView:cellForRowAtIndexPath' implementation is not provided by dataSource. | ||
*/ | ||
- (UITableViewCell *)indexedTableView:(ADBIndexedTableView *)tableView | ||
cellForRowAtIndexPath:(NSIndexPath *)indexPath | ||
objectAtIndexPath:(id)object; | ||
@end | ||
|
||
@interface ADBIndexedTableView : UITableView { | ||
|
||
@private | ||
NSMutableDictionary *_indexedObjects; | ||
NSArray *_objectsInitials; | ||
ADBMessageInterceptor *_dataSourceInterceptor; | ||
id <ADBIndexedTableViewDataSource> __weak _indexDataSource; | ||
} | ||
|
||
/** | ||
@param objects, array of objects that will be organized in sections | ||
Surrogate method for 'reloadData', creates data structure and calls 'reloadData' on super | ||
Subsequent 'reloadData' messages use previously created data structure. | ||
*/ | ||
- (void)reloadDataWithObjects:(NSArray *)objects; | ||
|
||
- (id)objectAtIndexPath:(NSIndexPath *)indexPath; | ||
- (void)removeObjectAtIndexPath:(NSIndexPath *)indexPath; | ||
- (id)objectsWithInitials:(NSString *)initial; | ||
- (id)objectsInSection:(NSUInteger)section; | ||
|
||
@property (nonatomic, strong) NSMutableDictionary *indexedObjects; | ||
@property (nonatomic, strong) NSArray *objectsInitials; | ||
@property (nonatomic, weak) IBOutlet id <ADBIndexedTableViewDataSource> indexDataSource; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// | ||
// ADBIndexedTableView.m | ||
// PassDesk | ||
// | ||
// Created by Alberto De Bortoli on 11/4/12. | ||
// Copyright (c) 2012 Alberto De Bortoli. All rights reserved. | ||
// | ||
|
||
#import "ADBIndexedTableView.h" | ||
|
||
@implementation ADBIndexedTableView | ||
|
||
#pragma mark - Message forwarding | ||
|
||
- (id <UITableViewDataSource>)dataSource | ||
{ | ||
return (id <UITableViewDataSource>)_dataSourceInterceptor; | ||
} | ||
|
||
- (void)setDataSource:(id <UITableViewDataSource>)dataSource | ||
{ | ||
[super setDataSource:nil]; | ||
|
||
_dataSourceInterceptor = [[ADBMessageInterceptor alloc] init]; | ||
[_dataSourceInterceptor setReceiver:dataSource]; | ||
[_dataSourceInterceptor setSecondChance:self]; | ||
|
||
[super setDataSource:(id)_dataSourceInterceptor]; | ||
} | ||
|
||
#pragma mark - Core | ||
|
||
- (void)reloadDataWithObjects:(NSArray *)objects | ||
{ | ||
NSString *field = [_indexDataSource objectsFieldForIndexedTableView:self]; | ||
|
||
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:field ascending:YES]; | ||
objects = [objects sortedArrayUsingDescriptors:@[sortDescriptor]]; | ||
|
||
// calculate needed initials | ||
NSMutableArray *initials = [NSMutableArray array]; | ||
for (NSString *property in [objects valueForKey:field]) { | ||
if ([property length]) { | ||
NSString *initial = [[property substringToIndex:1] capitalizedString]; | ||
if (![initials containsObject:initial]) { | ||
[initials addObject:initial]; | ||
} | ||
} | ||
} | ||
|
||
_objectsInitials = initials; | ||
|
||
_indexedObjects = [NSMutableDictionary dictionary]; | ||
|
||
// create dictionary with objects grouped for initial | ||
for (NSString *initial in _objectsInitials) { | ||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.%@ beginswith[cd] %@", field, initial]; | ||
NSArray *filteredForInitial = [objects filteredArrayUsingPredicate:predicate]; | ||
[_indexedObjects setObject:filteredForInitial forKey:initial]; | ||
} | ||
|
||
[self reloadData]; | ||
} | ||
|
||
- (id)objectAtIndexPath:(NSIndexPath *)indexPath | ||
{ | ||
NSString *initial = self.objectsInitials[indexPath.section]; | ||
return self.indexedObjects[initial][indexPath.row]; | ||
} | ||
|
||
- (void)removeObjectAtIndexPath:(NSIndexPath *)indexPath | ||
{ | ||
NSString *initial = self.objectsInitials[indexPath.section]; | ||
NSMutableArray *objectsForGivenInitial = [self.indexedObjects[initial] mutableCopy]; | ||
[objectsForGivenInitial removeObjectAtIndex:indexPath.row]; | ||
[self.indexedObjects setObject:objectsForGivenInitial forKey:initial]; | ||
[self deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; | ||
} | ||
|
||
- (id)objectsWithInitials:(NSString *)initial | ||
{ | ||
NSArray *retVal = _indexedObjects[[initial uppercaseString]]; | ||
return retVal; | ||
} | ||
|
||
- (id)objectsInSection:(NSUInteger)section | ||
{ | ||
NSString *initial = [_objectsInitials objectAtIndex:section]; | ||
NSArray *retVal = _indexedObjects[initial]; | ||
return retVal; | ||
} | ||
|
||
#pragma mark - UITableViewDatasource | ||
|
||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView | ||
{ | ||
return [_objectsInitials count]; | ||
} | ||
|
||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section | ||
{ | ||
NSString *initial = _objectsInitials[section]; | ||
return [_indexedObjects[initial] count]; | ||
} | ||
|
||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section | ||
{ | ||
return _objectsInitials[section]; | ||
} | ||
|
||
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView | ||
{ | ||
return _objectsInitials; | ||
} | ||
|
||
- (NSInteger)tableView:(UITableView *)tableView | ||
sectionForSectionIndexTitle:(NSString *)title | ||
atIndex:(NSInteger)index | ||
{ | ||
return [_objectsInitials indexOfObject:title]; | ||
} | ||
|
||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath | ||
{ | ||
id objectAtIndexPath = [self objectAtIndexPath:indexPath]; | ||
|
||
UITableViewCell *cell = nil; | ||
|
||
// since 'indexedTableView:cellForRowAtIndexPath:objectAtIndexPath:' is marked ad @optional | ||
// we should check if indexDataSource responds to it, but if we get here dataSource does not implement | ||
// 'tableView:cellForRowAtIndexPath:' and so indexDataSource must implement | ||
// 'indexedTableView:cellForRowAtIndexPath:objectAtIndexPath:' (required in this scenario) | ||
cell = [_indexDataSource indexedTableView:self | ||
cellForRowAtIndexPath:indexPath | ||
objectAtIndexPath:objectAtIndexPath]; | ||
|
||
return cell; | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// | ||
// ADBMessageInterceptor.h | ||
// PassDesk | ||
// | ||
// Created by Alberto De Bortoli on 11/4/12. | ||
// Copyright (c) 2012 Alberto De Bortoli. All rights reserved. | ||
// | ||
// MessageInterceptor, proxy class to handle message forwarding. | ||
// | ||
// If you set a message inspector as a delegate object (for your delegating object) | ||
// it will check if the real delegate (receiver) can respond to the message, otherwise | ||
// it will check if the surrogate delegate (secondChance) can handle the message. | ||
// If both tests fail, a check on super (NSObject) will be performed as last wish. | ||
// | ||
// Useful to let a class (instance I) implement some delegate methods and to let the | ||
// rest of the delegate methods be implemented by the real delegate (R). | ||
// In this scenario receiver must be the real delegate (R) and mainInTheMiddle must be the class object (I). | ||
// | ||
// Classes that encapsulate a interceptor must: | ||
// 1. hold a ADBMessageInterceptor *iVar (here named _delegateInterceptor) | ||
// 2. implement the following methods | ||
// | ||
// example given using UITableViewDelegate | ||
// | ||
// #pragma mark - Message forwarding | ||
// | ||
// - (id <UITableViewDelegate>)delegate | ||
// { | ||
// return (id <UITableViewDelegate>)_delegateInterceptor; | ||
// } | ||
// | ||
// - (void)setDelegate:(id <UITableViewDelegate>)delegate | ||
// { | ||
// [super setDelegate:nil]; | ||
// | ||
// _delegateInterceptor = [[ADBMessageInterceptor alloc] init]; | ||
// [_delegateInterceptor setSecondChance:self]; | ||
// [_delegateInterceptor setReceiver:delegate]; | ||
// | ||
// [super setDelegate:(id)_delegateInterceptor]; | ||
// } | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
@interface ADBMessageInterceptor : NSObject { | ||
|
||
id __weak _receiver; | ||
id __weak _secondChance; | ||
} | ||
|
||
@property (nonatomic, weak) id receiver; | ||
@property (nonatomic, weak) id secondChance; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// | ||
// ADBMessageInterceptor.m | ||
// PassDesk | ||
// | ||
// Created by Alberto De Bortoli on 11/4/12. | ||
// Copyright (c) 2012 Alberto De Bortoli. All rights reserved. | ||
// | ||
|
||
#import "ADBMessageInterceptor.h" | ||
|
||
@implementation ADBMessageInterceptor | ||
|
||
- (id)forwardingTargetForSelector:(SEL)aSelector | ||
{ | ||
if ([_receiver respondsToSelector:aSelector]) { | ||
return _receiver; | ||
} | ||
if ([_secondChance respondsToSelector:aSelector]) { | ||
return _secondChance; | ||
} | ||
return [super forwardingTargetForSelector:aSelector]; | ||
} | ||
|
||
- (BOOL)respondsToSelector:(SEL)aSelector | ||
{ | ||
if ([_receiver respondsToSelector:aSelector]) { | ||
return YES; | ||
} | ||
if ([_secondChance respondsToSelector:aSelector]) { | ||
return YES; | ||
} | ||
return [super respondsToSelector:aSelector]; | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.