diff --git a/Code/Three20/RKObjectTTTableViewDataSource.h b/Code/Three20/RKObjectTTTableViewDataSource.h new file mode 100644 index 00000000..7e3e3069 --- /dev/null +++ b/Code/Three20/RKObjectTTTableViewDataSource.h @@ -0,0 +1,62 @@ +// +// RKObjectTTTableViewDataSource.h +// RestKit +// +// Created by Blake Watters on 4/26/11. +// Copyright 2011 Two Toasters. All rights reserved. +// + +#import +#import "../RestKit.h" + +/** + Provides a data source for interfacing RestKit object loaders with Three20 + Table Views. The dataSource is intended to be used with an instance of + RKObjectLoaderTTModel to perform a remote load of objects. The data source + then allows you to turn your loaded objects into Three20 table items without + a bunch of intermediary code. + + @see RKObjectLoaderTTModel + */ +@interface RKObjectTTTableViewDataSource : TTTableViewDataSource { + NSMutableDictionary* _objectToTableCellMappings; + NSMutableDictionary* _objectClassToTableItemMappings; +} + +/** + The collection of model objects fetched from the remote system via + the RKObjectLoaderTTModel instance set as the dataSource's model property + */ +@property (nonatomic, readonly) NSArray* modelObjects; + +/** + Returns a new auto-released data source + */ ++ (id)dataSource; + +/** + Registers a mapping from a class to a Three20 Table Item using an object mapping. The + object mapping should target an instance of the Three20 Table Item classes. + + For example, consider that we want to create a simple TTTableTextItem with text and a URL: + RKObjectTTTableViewDataSource* dataSource = [RKObjectTTTableViewDataSource dataSource]; + RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[TTTableTextItem class]]; + [mapping mapKeyPath:@"name" toAttribute:@"text"]; + [mapping mapKeyPath:@"attributeWithURL" toAttribute:@"URL"]; + [dataSource mapObjectClass:[MyModel class] toTableItemWithMapping:mapping]; + */ +- (void)mapObjectClass:(Class)objectClass toTableItemWithMapping:(RKObjectMapping*)mapping; + +/** + Registers a mapping from an object class to a custom UITableViewCell class. When the dataSource + loads, any objects matching the class will be marshalled into a temporary Three20 table item + and then passed to an instance of the specified UITableViewCell. + + This method is used to implement totally custom table cell within Three20 without having to create + intermediary Table Items. + + @see RKMappableObjectTableItem + */ +- (void)mapObjectClass:(Class)objectClass toTableCellClass:(Class)cellClass; + +@end diff --git a/Code/Three20/RKObjectTTTableViewDataSource.m b/Code/Three20/RKObjectTTTableViewDataSource.m new file mode 100644 index 00000000..dac9ed76 --- /dev/null +++ b/Code/Three20/RKObjectTTTableViewDataSource.m @@ -0,0 +1,112 @@ +// +// RKObjectTTTableViewDataSource.m +// RestKit +// +// Created by Blake Watters on 4/26/11. +// Copyright 2011 Two Toasters. All rights reserved. +// + +#import "RKObjectTTTableViewDataSource.h" +#import "RKMappableObjectTableItem.h" +#import "RKObjectLoaderTTModel.h" + +@implementation RKObjectTTTableViewDataSource + ++ (id)dataSource { + return [[self new] autorelease]; +} + +- (id)init { + self = [super init]; + if (self) { + _objectToTableCellMappings = [NSMutableDictionary new]; + _objectClassToTableItemMappings = [NSMutableDictionary new]; + } + + return self; +} + +- (void)setModel:(id)model { + if (NO == [model isKindOfClass:[RKObjectLoaderTTModel class]]) { + [NSException raise:nil format:@"RKTableViewDataSource is designed to work with RestKit TTModel implementations only"]; + } + + [super setModel:model]; +} + +- (void)dealloc { + [_objectToTableCellMappings release]; + + [super dealloc]; +} + +- (NSArray*)modelObjects { + return [(RKObjectLoaderTTModel*)self.model objects]; +} + +- (void)mapObjectClass:(Class)objectClass toTableCellClass:(Class)cellClass { + [_objectToTableCellMappings setObject:cellClass forKey:objectClass]; +} + +- (void)mapObjectClass:(Class)objectClass toTableItemWithMapping:(RKObjectMapping*)mapping { + [_objectClassToTableItemMappings setObject:mapping forKey:objectClass]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [self.modelObjects count]; +} + +- (Class)tableView:(UITableView*)tableView cellClassForObject:(id)object { + if ([object isKindOfClass:[RKMappableObjectTableItem class]]) { + RKMappableObjectTableItem* tableItem = (RKMappableObjectTableItem*)object; + Class cellClass = [_objectToTableCellMappings objectForKey:[tableItem.object class]]; + return cellClass; + } + + return [super tableView:tableView cellClassForObject:object]; +} + +// Return the table item... +- (id)tableView:(UITableView*)tableView objectForRowAtIndexPath:(NSIndexPath*)indexPath { + NSObject* mappableObject = [self.modelObjects objectAtIndex:indexPath.row]; + + if (indexPath.row < [self.modelObjects count]) { + // See if we have a TableItem mapping for this class + RKObjectMapping* mapping = [_objectClassToTableItemMappings objectForKey:[mappableObject class]]; + if (mapping) { + NSError* error = nil; + TTTableItem* tableItem = [[mapping.objectClass new] autorelease]; + RKObjectMappingOperation* operation = [RKObjectMappingOperation mappingOperationFromObject:mappableObject toObject:tableItem withObjectMapping:mapping]; + BOOL success = [operation performMapping:&error]; + if (success) { + return tableItem; + } else { + NSLog(@"ERROR: Unable to map object to table item: %@", error); + } + } + + // Otherwise enclose the object in a mappable table item + return [RKMappableObjectTableItem itemWithObject:mappableObject]; + } else { + // TODO: Log a warning + return nil; + } +} + +- (NSIndexPath*)tableView:(UITableView*)tableView indexPathForObject:(id)object { + NSUInteger objectIndex = [self.modelObjects indexOfObject:object]; + if (objectIndex != NSNotFound) { + return [NSIndexPath indexPathForRow:objectIndex inSection:0]; + } + return nil; +} + +- (NSString*)titleForEmpty { + return @"Empty!"; +} + +- (void)tableViewDidLoadModel:(UITableView*)tableView { + // Model finished load. This should be an RKObjectLoaderTTModel... +} + +@end diff --git a/Code/Three20/RKTableViewDataSource.h b/Code/Three20/RKTableViewDataSource.h deleted file mode 100644 index 1044bb14..00000000 --- a/Code/Three20/RKTableViewDataSource.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// RKTableViewDataSource.h -// RestKit -// -// Created by Blake Watters on 4/26/11. -// Copyright 2011 Two Toasters. All rights reserved. -// - -#import -#import "../RestKit.h" - -@interface RKTableViewDataSource : TTTableViewDataSource { - NSMutableDictionary* _objectToTableCellMappings; -} - -// The objects loaded via the RKObjectLoaderTTModel instance... -@property (nonatomic, readonly) NSArray* modelObjects; - -+ (id)dataSource; -- (void)registerCellClass:(Class)cellCell forObjectClass:(Class)objectClass; // TODO: Better method name?? - -// TODO: Delegate? - -@end diff --git a/Code/Three20/RKTableViewDataSource.m b/Code/Three20/RKTableViewDataSource.m deleted file mode 100644 index 8f4edc94..00000000 --- a/Code/Three20/RKTableViewDataSource.m +++ /dev/null @@ -1,84 +0,0 @@ -// -// RKTableViewDataSource.m -// RestKit -// -// Created by Blake Watters on 4/26/11. -// Copyright 2011 Two Toasters. All rights reserved. -// - -#import "RKTableViewDataSource.h" -#import "RKMappableObjectTableItem.h" -#import "RKObjectLoaderTTModel.h" - -@implementation RKTableViewDataSource - -+ (id)dataSource { - return [[self new] autorelease]; -} - -- (id)init { - self = [super init]; - if (self) { - _objectToTableCellMappings = [NSMutableDictionary new]; - } - - return self; -} - -- (void)setModel:(id)model { - if (NO == [model isKindOfClass:[RKObjectLoaderTTModel class]]) { - [NSException raise:nil format:@"RKTableViewDataSource is designed to work with RestKit TTModel implementations only"]; - } - - [super setModel:model]; -} - -- (void)dealloc { - [_objectToTableCellMappings release]; - - [super dealloc]; -} - -- (NSArray*)modelObjects { - return [(RKObjectLoaderTTModel*)self.model objects]; -} - -- (void)registerCellClass:(Class)cellCell forObjectClass:(Class)objectClass { - [_objectToTableCellMappings setObject:cellCell forKey:objectClass]; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return [self.modelObjects count]; -} - -- (Class)tableView:(UITableView*)tableView cellClassForObject:(id)object { - RKMappableObjectTableItem* tableItem = (RKMappableObjectTableItem*)object; - Class cellClass = [_objectToTableCellMappings objectForKey:[tableItem.object class]]; - NSAssert(cellClass != nil, @"Must have a registered cell class for type"); - return cellClass; -} - -- (id)tableView:(UITableView*)tableView objectForRowAtIndexPath:(NSIndexPath*)indexPath { - if (indexPath.row < [self.modelObjects count]) { - NSObject* mappableObject = [self.modelObjects objectAtIndex:indexPath.row]; - RKMappableObjectTableItem* tableItem = [RKMappableObjectTableItem itemWithObject:mappableObject]; - return tableItem; - } else { - return nil; - } -} - - -- (NSIndexPath*)tableView:(UITableView*)tableView indexPathForObject:(id)object { - NSUInteger objectIndex = [self.modelObjects indexOfObject:object]; - if (objectIndex != NSNotFound) { - return [NSIndexPath indexPathForRow:objectIndex inSection:0]; - } - return nil; -} - -- (NSString*)titleForEmpty { - return @"Empty!"; -} - -@end diff --git a/Code/Three20/Three20.h b/Code/Three20/Three20.h index 8af65751..dbcde269 100644 --- a/Code/Three20/Three20.h +++ b/Code/Three20/Three20.h @@ -9,4 +9,4 @@ #import "RKObjectLoaderTTModel.h" #import "RKFilterableObjectLoaderTTModel.h" #import "RKMappableObjectTableItem.h" -#import "RKTableViewDataSource.h" +#import "RKObjectTTTableViewDataSource.h" diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m index 6d1496e7..894687f2 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m @@ -21,10 +21,19 @@ return self; } -- (void)createModel { +- (void)createModel { + /** + Map loaded objects into Three20 Table Item instances! + */ + RKObjectTTTableViewDataSource* dataSource = [RKObjectTTTableViewDataSource dataSource]; + RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[TTTableTextItem class]]; + [mapping mapKeyPath:@"name" toAttribute:@"text"]; + [mapping mapKeyPath:@"topicNavURL" toAttribute:@"URL"]; + [dataSource mapObjectClass:[DBTopic class] toTableItemWithMapping:mapping]; RKObjectLoader* objectLoader = [[RKObjectManager sharedManager] objectLoaderWithResourcePath:@"/topics" delegate:nil]; - self.model = [RKObjectLoaderTTModel modelWithObjectLoader:objectLoader]; - + dataSource.model = [RKObjectLoaderTTModel modelWithObjectLoader:objectLoader]; + self.dataSource = dataSource; + UIBarButtonItem* item = nil; if ([[DBUser currentUser] isLoggedIn]) { item = [[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStyleBordered target:self action:@selector(logoutButtonWasPressed:)]; @@ -49,22 +58,4 @@ [[DBUser currentUser] logout]; } -- (void)didLoadModel:(BOOL)firstTime { - [super didLoadModel:firstTime]; - - RKObjectLoaderTTModel* model = (RKObjectLoaderTTModel*)self.model; - NSMutableArray* items = [NSMutableArray arrayWithCapacity:[model.objects count]]; - - for (DBTopic* topic in model.objects) { - NSString* topicPostsURL = RKMakePathWithObject(@"db://topics/(topicID)/posts", topic); - [items addObject:[TTTableTextItem itemWithText:topic.name URL:topicPostsURL]]; - } - - // Ensure that the datasource's model is still the RKRequestTTModel; - // Otherwise isOutdated will not work. - TTListDataSource* dataSource = [TTListDataSource dataSourceWithItems:items]; - dataSource.model = model; - self.dataSource = dataSource; -} - @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h index 4c310e8e..85b29d55 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h @@ -34,4 +34,9 @@ */ @property (nonatomic, retain) NSNumber* topicID; +/** + The Three20 Navigator URL to this Topic + */ +@property (nonatomic, readonly) NSString* topicNavURL; + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m index 8af5ebba..36baa9fb 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m @@ -18,4 +18,8 @@ return [self.topicID intValue] == 0; } +- (NSString*)topicNavURL { + return RKMakePathWithObject(@"db://topics/(topicID)/posts", self); +} + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m index cb51102c..51ac2558 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m @@ -15,7 +15,7 @@ - (NSArray*)fetchRequestsForResourcePath:(NSString*)resourcePath { if ([resourcePath isEqualToString:@"/topics"]) { NSFetchRequest* request = [DBTopic fetchRequest]; - NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:YES]; + NSSortDescriptor* sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"createdAt" ascending:YES] autorelease]; [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; return [NSArray arrayWithObject:request]; } diff --git a/RestKit.xcodeproj/project.pbxproj b/RestKit.xcodeproj/project.pbxproj index 6b988f5b..f5e4aa8b 100644 --- a/RestKit.xcodeproj/project.pbxproj +++ b/RestKit.xcodeproj/project.pbxproj @@ -249,8 +249,8 @@ 3F741A5E139D74F1002E7304 /* RKMappableObjectTableItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A56139D74F1002E7304 /* RKMappableObjectTableItem.m */; }; 3F741A5F139D74F1002E7304 /* RKObjectLoaderTTModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F741A57139D74F1002E7304 /* RKObjectLoaderTTModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F741A60139D74F1002E7304 /* RKObjectLoaderTTModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A58139D74F1002E7304 /* RKObjectLoaderTTModel.m */; }; - 3F741A61139D74F1002E7304 /* RKTableViewDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F741A59139D74F1002E7304 /* RKTableViewDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3F741A62139D74F1002E7304 /* RKTableViewDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A5A139D74F1002E7304 /* RKTableViewDataSource.m */; }; + 3F741A61139D74F1002E7304 /* RKObjectTTTableViewDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F741A59139D74F1002E7304 /* RKObjectTTTableViewDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3F741A62139D74F1002E7304 /* RKObjectTTTableViewDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A5A139D74F1002E7304 /* RKObjectTTTableViewDataSource.m */; }; 3FD12C851379AD64008B996A /* RKRouter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FD12C841379AD64008B996A /* RKRouter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 73057FD61331AD5E001908EE /* JSONKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 73057FC71331AA3D001908EE /* JSONKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 73057FD71331AD67001908EE /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 73057FC81331AA3D001908EE /* JSONKit.m */; }; @@ -633,8 +633,8 @@ 3F741A56139D74F1002E7304 /* RKMappableObjectTableItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKMappableObjectTableItem.m; path = ../../../../../RestKit/Code/Three20/RKMappableObjectTableItem.m; sourceTree = ""; }; 3F741A57139D74F1002E7304 /* RKObjectLoaderTTModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKObjectLoaderTTModel.h; path = ../../../../../RestKit/Code/Three20/RKObjectLoaderTTModel.h; sourceTree = ""; }; 3F741A58139D74F1002E7304 /* RKObjectLoaderTTModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKObjectLoaderTTModel.m; path = ../../../../../RestKit/Code/Three20/RKObjectLoaderTTModel.m; sourceTree = ""; }; - 3F741A59139D74F1002E7304 /* RKTableViewDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKTableViewDataSource.h; path = ../../../../../RestKit/Code/Three20/RKTableViewDataSource.h; sourceTree = ""; }; - 3F741A5A139D74F1002E7304 /* RKTableViewDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKTableViewDataSource.m; path = ../../../../../RestKit/Code/Three20/RKTableViewDataSource.m; sourceTree = ""; }; + 3F741A59139D74F1002E7304 /* RKObjectTTTableViewDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKObjectTTTableViewDataSource.h; path = ../../../../../RestKit/Code/Three20/RKObjectTTTableViewDataSource.h; sourceTree = ""; }; + 3F741A5A139D74F1002E7304 /* RKObjectTTTableViewDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKObjectTTTableViewDataSource.m; path = ../../../../../RestKit/Code/Three20/RKObjectTTTableViewDataSource.m; sourceTree = ""; }; 3FD12C841379AD64008B996A /* RKRouter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKRouter.h; path = Code/ObjectMapping/RKRouter.h; sourceTree = SOURCE_ROOT; }; 73057FC41331A8F6001908EE /* RKJSONParserJSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKJSONParserJSONKit.m; sourceTree = ""; }; 73057FC71331AA3D001908EE /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSONKit.h; path = Vendor/JSONKit/JSONKit.h; sourceTree = ""; }; @@ -965,8 +965,8 @@ 3F741A56139D74F1002E7304 /* RKMappableObjectTableItem.m */, 3F741A57139D74F1002E7304 /* RKObjectLoaderTTModel.h */, 3F741A58139D74F1002E7304 /* RKObjectLoaderTTModel.m */, - 3F741A59139D74F1002E7304 /* RKTableViewDataSource.h */, - 3F741A5A139D74F1002E7304 /* RKTableViewDataSource.m */, + 3F741A59139D74F1002E7304 /* RKObjectTTTableViewDataSource.h */, + 3F741A5A139D74F1002E7304 /* RKObjectTTTableViewDataSource.m */, 253A08A512551D8D00976E89 /* Three20.h */, ); path = Three20; @@ -1344,7 +1344,7 @@ 3F741A5B139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.h in Headers */, 3F741A5D139D74F1002E7304 /* RKMappableObjectTableItem.h in Headers */, 3F741A5F139D74F1002E7304 /* RKObjectLoaderTTModel.h in Headers */, - 3F741A61139D74F1002E7304 /* RKTableViewDataSource.h in Headers */, + 3F741A61139D74F1002E7304 /* RKObjectTTTableViewDataSource.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1846,7 +1846,7 @@ 3F741A5C139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.m in Sources */, 3F741A5E139D74F1002E7304 /* RKMappableObjectTableItem.m in Sources */, 3F741A60139D74F1002E7304 /* RKObjectLoaderTTModel.m in Sources */, - 3F741A62139D74F1002E7304 /* RKTableViewDataSource.m in Sources */, + 3F741A62139D74F1002E7304 /* RKObjectTTTableViewDataSource.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };