mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-17 12:20:20 +08:00
Merge branch 'release/0.10.1' into development
This commit is contained in:
@@ -42,7 +42,17 @@ extern NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubs
|
||||
Programmatically configured values take precedence over the user info
|
||||
dictionary.
|
||||
*/
|
||||
@property (nonatomic, retain) NSString *primaryKeyAttribute;
|
||||
@property (nonatomic, retain) NSString *primaryKeyAttributeName;
|
||||
|
||||
/**
|
||||
The attribute description object for the attribute designated as the primary key for the receiver.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSAttributeDescription *primaryKeyAttribute;
|
||||
|
||||
/**
|
||||
The class representing the value of the attribute designated as the primary key for the receiver.
|
||||
*/
|
||||
@property (nonatomic, readonly) Class primaryKeyAttributeClass;
|
||||
|
||||
/**
|
||||
Returns a cached predicate specifying that the primary key attribute is equal to the $PRIMARY_KEY_VALUE
|
||||
@@ -61,9 +71,23 @@ extern NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubs
|
||||
value. This predicate is constructed by evaluating the cached predicate returned by the
|
||||
predicateForPrimaryKeyAttribute with a dictionary of substitution variables specifying that
|
||||
$PRIMARY_KEY_VALUE is equal to the given value.
|
||||
|
||||
|
||||
**NOTE**: This method considers the type of the receiver's primary key attribute when constructing
|
||||
the predicate. It will coerce the given value into either an NSString or an NSNumber as
|
||||
appropriate. This behavior is a convenience to avoid annoying issues related to Core Data's
|
||||
handling of predicates for NSString and NSNumber types that were not appropriately casted.
|
||||
|
||||
@return A predicate speciying that the value of the primary key attribute is equal to a given value.
|
||||
*/
|
||||
- (NSPredicate *)predicateForPrimaryKeyAttributeWithValue:(id)value;
|
||||
|
||||
/**
|
||||
Coerces the given value into the class representing the primary key. Currently support NSString
|
||||
and NSNumber coercsions.
|
||||
|
||||
@bug **NOTE** This API is temporary and will be deprecated and replaced.
|
||||
@since 0.10.1
|
||||
*/
|
||||
- (id)coerceValueForPrimaryKey:(id)primaryKeyValue;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
NSString * const RKEntityDescriptionPrimaryKeyAttributeUserInfoKey = @"primaryKeyAttribute";
|
||||
NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubstitutionVariable = @"PRIMARY_KEY_VALUE";
|
||||
|
||||
static char primaryKeyAttributeKey, primaryKeyPredicateKey;
|
||||
static char primaryKeyAttributeNameKey, primaryKeyPredicateKey;
|
||||
|
||||
@implementation NSEntityDescription (RKAdditions)
|
||||
|
||||
- (void)setPredicateForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
{
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == $PRIMARY_KEY_VALUE", primaryKeyAttribute];
|
||||
NSPredicate *predicate = (primaryKeyAttribute) ? [NSPredicate predicateWithFormat:@"%K == $PRIMARY_KEY_VALUE", primaryKeyAttribute] : nil;
|
||||
objc_setAssociatedObject(self,
|
||||
&primaryKeyPredicateKey,
|
||||
predicate,
|
||||
@@ -27,10 +27,25 @@ static char primaryKeyAttributeKey, primaryKeyPredicateKey;
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (NSString *)primaryKeyAttribute
|
||||
- (NSAttributeDescription *)primaryKeyAttribute
|
||||
{
|
||||
return [[self attributesByName] valueForKey:[self primaryKeyAttributeName]];
|
||||
}
|
||||
|
||||
- (Class)primaryKeyAttributeClass
|
||||
{
|
||||
NSAttributeDescription *attributeDescription = [self primaryKeyAttribute];
|
||||
if (attributeDescription) {
|
||||
return NSClassFromString(attributeDescription.attributeValueClassName);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)primaryKeyAttributeName
|
||||
{
|
||||
// Check for an associative object reference
|
||||
NSString *primaryKeyAttribute = (NSString *) objc_getAssociatedObject(self, &primaryKeyAttributeKey);
|
||||
NSString *primaryKeyAttribute = (NSString *) objc_getAssociatedObject(self, &primaryKeyAttributeNameKey);
|
||||
|
||||
// Fall back to the userInfo dictionary
|
||||
if (! primaryKeyAttribute) {
|
||||
@@ -45,24 +60,46 @@ static char primaryKeyAttributeKey, primaryKeyPredicateKey;
|
||||
return primaryKeyAttribute;
|
||||
}
|
||||
|
||||
- (void)setPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
|
||||
- (void)setPrimaryKeyAttributeName:(NSString *)primaryKeyAttributeName
|
||||
{
|
||||
objc_setAssociatedObject(self,
|
||||
&primaryKeyAttributeKey,
|
||||
primaryKeyAttribute,
|
||||
&primaryKeyAttributeNameKey,
|
||||
primaryKeyAttributeName,
|
||||
OBJC_ASSOCIATION_RETAIN);
|
||||
[self setPredicateForPrimaryKeyAttribute:primaryKeyAttribute];
|
||||
[self setPredicateForPrimaryKeyAttribute:primaryKeyAttributeName];
|
||||
}
|
||||
|
||||
|
||||
- (NSPredicate *)predicateForPrimaryKeyAttribute
|
||||
{
|
||||
return (NSPredicate *) objc_getAssociatedObject(self, &primaryKeyPredicateKey);
|
||||
}
|
||||
|
||||
- (id)coerceValueForPrimaryKey:(id)primaryKeyValue
|
||||
{
|
||||
id searchValue = primaryKeyValue;
|
||||
Class theClass = [self primaryKeyAttributeClass];
|
||||
if (theClass) {
|
||||
// TODO: This coercsion behavior should be pluggable and reused from the mapper
|
||||
if ([theClass isSubclassOfClass:[NSNumber class]] && ![searchValue isKindOfClass:[NSNumber class]]) {
|
||||
// Handle NSString -> NSNumber
|
||||
if ([searchValue isKindOfClass:[NSString class]]) {
|
||||
searchValue = [NSNumber numberWithDouble:[searchValue doubleValue]];
|
||||
}
|
||||
} else if ([theClass isSubclassOfClass:[NSString class]] && ![searchValue isKindOfClass:[NSString class]]) {
|
||||
// Coerce to string
|
||||
if ([searchValue respondsToSelector:@selector(stringValue)]) {
|
||||
searchValue = [searchValue stringValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return searchValue;
|
||||
}
|
||||
|
||||
- (NSPredicate *)predicateForPrimaryKeyAttributeWithValue:(id)value
|
||||
{
|
||||
NSDictionary *variables = [NSDictionary dictionaryWithObject:value
|
||||
id substitutionValue = [self coerceValueForPrimaryKey:value];
|
||||
NSDictionary *variables = [NSDictionary dictionaryWithObject:substitutionValue
|
||||
forKey:RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubstitutionVariable];
|
||||
return [[self predicateForPrimaryKeyAttribute] predicateWithSubstitutionVariables:variables];
|
||||
}
|
||||
|
||||
@@ -138,14 +138,13 @@ RK_FIX_CATEGORY_BUG(NSManagedObject_ActiveRecord)
|
||||
}
|
||||
|
||||
+ (id)findByPrimaryKey:(id)primaryKeyValue inContext:(NSManagedObjectContext *)context {
|
||||
NSEntityDescription *entity = [self entityDescriptionInContext:context];
|
||||
NSString *primaryKeyAttribute = entity.primaryKeyAttribute;
|
||||
if (! primaryKeyAttribute) {
|
||||
RKLogWarning(@"Attempt to findByPrimaryKey for entity with nil primaryKeyAttribute. Set the primaryKeyAttribute and try again! %@", entity);
|
||||
NSPredicate *predicate = [[self entityDescriptionInContext:context] predicateForPrimaryKeyAttributeWithValue:primaryKeyValue];
|
||||
if (! predicate) {
|
||||
RKLogWarning(@"Attempt to findByPrimaryKey for entity with nil primaryKeyAttribute. Set the primaryKeyAttributeName and try again! %@", self);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self findFirstByAttribute:primaryKeyAttribute withValue:primaryKeyValue inContext:context];
|
||||
|
||||
return [self findFirstWithPredicate:predicate inContext:context];
|
||||
}
|
||||
|
||||
+ (id)findByPrimaryKey:(id)primaryKeyValue {
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
- (void)load
|
||||
{
|
||||
RKLogInfo(@"Loading entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
|
||||
RKLogDebug(@"Loading entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:self.entity];
|
||||
[fetchRequest setResultType:NSManagedObjectIDResultType];
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
- (void)flush
|
||||
{
|
||||
RKLogInfo(@"Flushing entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
|
||||
RKLogDebug(@"Flushing entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
|
||||
self.attributeValuesToObjectIDs = nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
// Use cached predicate if primary key matches
|
||||
NSPredicate *predicate = nil;
|
||||
if ([entity.primaryKeyAttribute isEqualToString:primaryKeyAttribute]) {
|
||||
if ([entity.primaryKeyAttributeName isEqualToString:primaryKeyAttribute]) {
|
||||
predicate = [entity predicateForPrimaryKeyAttributeWithValue:searchValue];
|
||||
} else {
|
||||
// Parse a predicate
|
||||
|
||||
@@ -182,7 +182,8 @@
|
||||
object = [[[NSManagedObject alloc] initWithEntity:entity
|
||||
insertIntoManagedObjectContext:[_objectStore managedObjectContextForCurrentThread]] autorelease];
|
||||
if (primaryKeyAttribute && primaryKeyValue && ![primaryKeyValue isEqual:[NSNull null]]) {
|
||||
[object setValue:primaryKeyValue forKey:primaryKeyAttribute];
|
||||
id coercedPrimaryKeyValue = [entity coerceValueForPrimaryKey:primaryKeyValue];
|
||||
[object setValue:coercedPrimaryKeyValue forKey:primaryKeyAttribute];
|
||||
}
|
||||
|
||||
if ([self.objectStore.cacheStrategy respondsToSelector:@selector(didCreateObject:)]) {
|
||||
@@ -202,17 +203,17 @@
|
||||
}
|
||||
|
||||
/*
|
||||
Allows the primaryKeyAttribute property on the NSEntityDescription to configure the mapping and vice-versa
|
||||
Allows the primaryKeyAttributeName property on the NSEntityDescription to configure the mapping and vice-versa
|
||||
*/
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
if ([keyPath isEqualToString:@"entity"]) {
|
||||
if (! self.primaryKeyAttribute) {
|
||||
self.primaryKeyAttribute = [self.entity primaryKeyAttribute];
|
||||
self.primaryKeyAttribute = [self.entity primaryKeyAttributeName];
|
||||
}
|
||||
} else if ([keyPath isEqualToString:@"primaryKeyAttribute"]) {
|
||||
if (! self.entity.primaryKeyAttribute) {
|
||||
self.entity.primaryKeyAttribute = self.primaryKeyAttribute;
|
||||
self.entity.primaryKeyAttributeName = self.primaryKeyAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ RKRequestMethod RKRequestMethodTypeFromName(NSString *methodName) {
|
||||
@interface RKRequest ()
|
||||
@property (nonatomic, assign, readwrite, getter = isLoaded) BOOL loaded;
|
||||
@property (nonatomic, assign, readwrite, getter = isLoading) BOOL loading;
|
||||
@property (nonatomic, assign, readwrite) BOOL canceled;
|
||||
@property (nonatomic, assign, readwrite, getter = isCancelled) BOOL cancelled;
|
||||
@property (nonatomic, retain, readwrite) RKResponse *response;
|
||||
@end
|
||||
|
||||
@@ -123,13 +123,12 @@ RKRequestMethod RKRequestMethodTypeFromName(NSString *methodName) {
|
||||
@synthesize onDidFailLoadWithError;
|
||||
@synthesize additionalRootCertificates = _additionalRootCertificates;
|
||||
@synthesize disableCertificateValidation = _disableCertificateValidation;
|
||||
@synthesize cancelled = _cancelled;
|
||||
@synthesize followRedirect = _followRedirect;
|
||||
@synthesize runLoopMode = _runLoopMode;
|
||||
@synthesize loaded = _loaded;
|
||||
@synthesize loading = _loading;
|
||||
@synthesize canceled = _canceled;
|
||||
@synthesize response = _response;
|
||||
@synthesize cancelled = _cancelled;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
@synthesize backgroundPolicy = _backgroundPolicy;
|
||||
@@ -184,7 +183,7 @@ RKRequestMethod RKRequestMethodTypeFromName(NSString *methodName) {
|
||||
_connection = nil;
|
||||
self.loading = NO;
|
||||
self.loaded = NO;
|
||||
self.canceled = NO;
|
||||
self.cancelled = NO;
|
||||
}
|
||||
|
||||
- (void)cleanupBackgroundTask {
|
||||
@@ -407,7 +406,7 @@ RKRequestMethod RKRequestMethodTypeFromName(NSString *methodName) {
|
||||
}
|
||||
|
||||
- (void)cancelAndInformDelegate:(BOOL)informDelegate {
|
||||
_cancelled = YES;
|
||||
self.cancelled = YES;
|
||||
[_connection cancel];
|
||||
[_connection release];
|
||||
_connection = nil;
|
||||
@@ -436,7 +435,7 @@ RKRequestMethod RKRequestMethodTypeFromName(NSString *methodName) {
|
||||
- (void)fireAsynchronousRequest {
|
||||
RKLogDebug(@"Sending asynchronous %@ request to URL %@.", [self HTTPMethod], [[self URL] absoluteString]);
|
||||
if (![self prepareURLRequest]) {
|
||||
// TODO: Logging
|
||||
RKLogWarning(@"Failed to send request asynchronously: prepareURLRequest returned NO.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -565,7 +564,7 @@ RKRequestMethod RKRequestMethodTypeFromName(NSString *methodName) {
|
||||
RKLogDebug(@"Sending synchronous %@ request to URL %@.", [self HTTPMethod], [[self URL] absoluteString]);
|
||||
|
||||
if (![self prepareURLRequest]) {
|
||||
// TODO: Logging
|
||||
RKLogWarning(@"Failed to send request synchronously: prepareURLRequest returned NO.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
reference and canceling the request.
|
||||
|
||||
Useful when an object that acts as the delegate for one or more requests
|
||||
is being deallocated and all outstanding requests should be canceled
|
||||
is being deallocated and all outstanding requests should be cancelled
|
||||
without generating any further delegate callbacks.
|
||||
|
||||
@param delegate The object acting as the delegate for all enqueued requests that are to be aborted.
|
||||
@@ -314,7 +314,7 @@
|
||||
- (void)requestQueue:(RKRequestQueue *)queue didLoadResponse:(RKResponse *)response;
|
||||
|
||||
/**
|
||||
Sent when queue has canceled a request.
|
||||
Sent when queue has cancelled a request.
|
||||
|
||||
@param queue The queue that cancelled the request.
|
||||
@param request The cancelled request.
|
||||
|
||||
@@ -389,7 +389,7 @@ static const NSTimeInterval kFlushDelay = 0.3;
|
||||
|
||||
- (void)cancelRequest:(RKRequest*)request loadNext:(BOOL)loadNext {
|
||||
if ([request isUnsent]) {
|
||||
RKLogDebug(@"Canceled undispatched request %@ and removed from queue %@", request, self);
|
||||
RKLogDebug(@"Cancelled undispatched request %@ and removed from queue %@", request, self);
|
||||
|
||||
[self removeRequest:request];
|
||||
request.delegate = nil;
|
||||
@@ -398,7 +398,7 @@ static const NSTimeInterval kFlushDelay = 0.3;
|
||||
[_delegate requestQueue:self didCancelRequest:request];
|
||||
}
|
||||
} else if ([self containsRequest:request] && [request isLoading]) {
|
||||
RKLogDebug(@"Canceled loading request %@ and removed from queue %@", request, self);
|
||||
RKLogDebug(@"Cancelled loading request %@ and removed from queue %@", request, self);
|
||||
|
||||
[request cancel];
|
||||
request.delegate = nil;
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
- (void)finalizeLoad:(BOOL)successful {
|
||||
self.loading = NO;
|
||||
self.loaded = successful;
|
||||
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(objectLoaderDidFinishLoading:)]) {
|
||||
[(NSObject<RKObjectLoaderDelegate>*)self.delegate performSelectorOnMainThread:@selector(objectLoaderDidFinishLoading:)
|
||||
withObject:self waitUntilDone:YES];
|
||||
@@ -407,7 +407,10 @@
|
||||
object:self
|
||||
userInfo:userInfo];
|
||||
}
|
||||
[self informDelegateOfError:error];
|
||||
|
||||
if (! self.isCancelled) {
|
||||
[self informDelegateOfError:error];
|
||||
}
|
||||
|
||||
[self finalizeLoad:NO];
|
||||
}
|
||||
|
||||
@@ -138,6 +138,12 @@ lcl_configure_by_name("App", level);
|
||||
} \
|
||||
} while(false);
|
||||
|
||||
/**
|
||||
Temporarily turns off logging for the given logging component during execution of the block.
|
||||
After the block has finished execution, the logging level is restored to its previous state.
|
||||
*/
|
||||
#define RKLogSilenceComponentWhileExecutingBlock(component, _block) \
|
||||
RKLogToComponentWithLevelWhileExecutingBlock(component, RKLogLevelOff, _block)
|
||||
|
||||
/**
|
||||
Temporarily changes the logging level for the configured RKLogComponent and executes the block. Any logging
|
||||
@@ -149,7 +155,7 @@ lcl_configure_by_name("App", level);
|
||||
|
||||
|
||||
/**
|
||||
Temporarily turns off logging for the execution of the block.
|
||||
Temporarily turns off logging for current logging component during execution of the block.
|
||||
After the block has finished execution, the logging level is restored to its previous state.
|
||||
*/
|
||||
#define RKLogSilenceWhileExecutingBlock(_block) \
|
||||
@@ -205,3 +211,9 @@ void RKLogConfigureFromEnvironment(void);
|
||||
of a failed key-value validation error.
|
||||
*/
|
||||
void RKLogValidationError(NSError *);
|
||||
|
||||
/**
|
||||
Logs the value of an NSUInteger as a binary string. Useful when
|
||||
examining integers containing bitmasked values.
|
||||
*/
|
||||
void RKLogIntegerAsBinary(NSUInteger);
|
||||
|
||||
@@ -159,3 +159,13 @@ void RKLogValidationError(NSError *validationError) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RKLogIntegerAsBinary(NSUInteger bitMask) {
|
||||
NSUInteger bit = ~(NSUIntegerMax >> 1);
|
||||
NSMutableString *string = [NSMutableString string];
|
||||
do {
|
||||
[string appendString:(((NSUInteger)bitMask & bit) ? @"1" : @"0")];
|
||||
} while ( bit >>= 1 );
|
||||
|
||||
NSLog(@"Value of %ld in binary: %@", (long) bitMask, string);
|
||||
}
|
||||
|
||||
31
Code/Testing/RKTableControllerTestDelegate.h
Normal file
31
Code/Testing/RKTableControllerTestDelegate.h
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// RKTableControllerTestDelegate.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 5/23/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#import "RKTableController.h"
|
||||
#import "RKFetchedResultsTableController.h"
|
||||
|
||||
@interface RKAbstractTableControllerTestDelegate : NSObject <RKAbstractTableControllerDelegate>
|
||||
|
||||
@property(nonatomic, readonly, getter = isCancelled) BOOL cancelled;
|
||||
@property(nonatomic, assign) NSTimeInterval timeout;
|
||||
@property(nonatomic, assign) BOOL awaitingResponse;
|
||||
|
||||
+ (id)tableControllerDelegate;
|
||||
- (void)waitForLoad;
|
||||
|
||||
@end
|
||||
|
||||
@interface RKTableControllerTestDelegate : RKAbstractTableControllerTestDelegate <RKTableControllerDelegate>
|
||||
@end
|
||||
|
||||
@interface RKFetchedResultsTableControllerTestDelegate : RKAbstractTableControllerTestDelegate <RKFetchedResultsTableControllerDelegate>
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
139
Code/Testing/RKTableControllerTestDelegate.m
Normal file
139
Code/Testing/RKTableControllerTestDelegate.m
Normal file
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// RKTableControllerTestDelegate.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 5/23/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKTableControllerTestDelegate.h"
|
||||
#import "RKLog.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
@implementation RKAbstractTableControllerTestDelegate
|
||||
|
||||
@synthesize timeout = _timeout;
|
||||
@synthesize awaitingResponse = _awaitingResponse;
|
||||
@synthesize cancelled = _cancelled;
|
||||
|
||||
+ (id)tableControllerDelegate {
|
||||
return [[self new] autorelease];
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_timeout = 1.0;
|
||||
_awaitingResponse = NO;
|
||||
_cancelled = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)waitForLoad {
|
||||
_awaitingResponse = YES;
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
while (_awaitingResponse) {
|
||||
RKLogTrace(@"Awaiting response = %d", _awaitingResponse);
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
if ([[NSDate date] timeIntervalSinceDate:startDate] > self.timeout) {
|
||||
NSLog(@"%@: Timed out!!!", self);
|
||||
_awaitingResponse = NO;
|
||||
[NSException raise:nil format:@"*** Operation timed out after %f seconds...", self.timeout];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma RKTableControllerDelegate methods
|
||||
|
||||
- (void)tableControllerDidFinishLoad:(RKAbstractTableController *)tableController {
|
||||
_awaitingResponse = NO;
|
||||
}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController didFailLoadWithError:(NSError *)error {
|
||||
_awaitingResponse = NO;
|
||||
}
|
||||
|
||||
- (void)tableControllerDidCancelLoad:(RKAbstractTableController *)tableController {
|
||||
_awaitingResponse = NO;
|
||||
_cancelled = YES;
|
||||
}
|
||||
|
||||
- (void)tableControllerDidFinalizeLoad:(RKAbstractTableController *)tableController {
|
||||
_awaitingResponse = NO;
|
||||
}
|
||||
|
||||
// NOTE - Delegate methods below are implemented to allow trampoline through
|
||||
// OCMock expectations
|
||||
|
||||
- (void)tableControllerDidStartLoad:(RKAbstractTableController *)tableController
|
||||
{}
|
||||
|
||||
- (void)tableControllerDidBecomeEmpty:(RKAbstractTableController *)tableController
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willLoadTableWithObjectLoader:(RKObjectLoader *)objectLoader
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didLoadTableWithObjectLoader:(RKObjectLoader *)objectLoader
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController willBeginEditing:(id)object atIndexPath:(NSIndexPath*)indexPath
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController didEndEditing:(id)object atIndexPath:(NSIndexPath*)indexPath
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController didInsertSection:(RKTableSection*)section atIndex:(NSUInteger)sectionIndex
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController didRemoveSection:(RKTableSection*)section atIndex:(NSUInteger)sectionIndex
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController didInsertObject:(id)object atIndexPath:(NSIndexPath*)indexPath
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController didUpdateObject:(id)object atIndexPath:(NSIndexPath*)indexPath
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController didDeleteObject:(id)object atIndexPath:(NSIndexPath*)indexPath
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController willAddSwipeView:(UIView*)swipeView toCell:(UITableViewCell*)cell forObject:(id)object
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController*)tableController willRemoveSwipeView:(UIView*)swipeView fromCell:(UITableViewCell*)cell forObject:(id)object
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKTableController *)tableController didLoadObjects:(NSArray *)objects inSection:(NSUInteger)sectionIndex
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willDisplayCell:(UITableViewCell *)cell forObject:(id)object atIndexPath:(NSIndexPath *)indexPath
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didSelectCell:(UITableViewCell *)cell forObject:(id)object atIndexPath:(NSIndexPath *)indexPath
|
||||
{}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RKTableControllerTestDelegate
|
||||
|
||||
- (void)tableController:(RKTableController *)tableController didLoadObjects:(NSArray *)objects inSection:(RKTableSection *)section
|
||||
{}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RKFetchedResultsTableControllerTestDelegate
|
||||
|
||||
- (void)tableController:(RKFetchedResultsTableController *)tableController didInsertSectionAtIndex:(NSUInteger)sectionIndex
|
||||
{}
|
||||
|
||||
- (void)tableController:(RKFetchedResultsTableController *)tableController didDeleteSectionAtIndex:(NSUInteger)sectionIndex
|
||||
{}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -77,20 +77,32 @@ static RKTestFactory *sharedFactory = nil;
|
||||
- (void)defineDefaultFactories
|
||||
{
|
||||
[self defineFactory:RKTestFactoryDefaultNamesClient withBlock:^id {
|
||||
RKClient *client = [RKClient clientWithBaseURL:self.baseURL];
|
||||
client.requestQueue.suspended = NO;
|
||||
[client.reachabilityObserver getFlags];
|
||||
__block RKClient *client;
|
||||
|
||||
RKLogSilenceComponentWhileExecutingBlock(lcl_cRestKitNetworkReachability, ^{
|
||||
RKLogSilenceComponentWhileExecutingBlock(lcl_cRestKitSupport, ^{
|
||||
client = [RKClient clientWithBaseURL:self.baseURL];
|
||||
client.requestQueue.suspended = NO;
|
||||
[client.reachabilityObserver getFlags];
|
||||
});
|
||||
});
|
||||
|
||||
return client;
|
||||
}];
|
||||
|
||||
[self defineFactory:RKTestFactoryDefaultNamesObjectManager withBlock:^id {
|
||||
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:self.baseURL];
|
||||
RKObjectMappingProvider *mappingProvider = [self objectFromFactory:RKTestFactoryDefaultNamesMappingProvider];
|
||||
objectManager.mappingProvider = mappingProvider;
|
||||
|
||||
// Force reachability determination
|
||||
[objectManager.client.reachabilityObserver getFlags];
|
||||
__block RKObjectManager *objectManager;
|
||||
|
||||
RKLogSilenceComponentWhileExecutingBlock(lcl_cRestKitNetworkReachability, ^{
|
||||
RKLogSilenceComponentWhileExecutingBlock(lcl_cRestKitSupport, ^{
|
||||
objectManager = [RKObjectManager managerWithBaseURL:self.baseURL];
|
||||
RKObjectMappingProvider *mappingProvider = [self objectFromFactory:RKTestFactoryDefaultNamesMappingProvider];
|
||||
objectManager.mappingProvider = mappingProvider;
|
||||
|
||||
// Force reachability determination
|
||||
[objectManager.client.reachabilityObserver getFlags];
|
||||
});
|
||||
});
|
||||
|
||||
return objectManager;
|
||||
}];
|
||||
@@ -194,7 +206,12 @@ static RKTestFactory *sharedFactory = nil;
|
||||
{
|
||||
[RKObjectManager setDefaultMappingQueue:dispatch_queue_create("org.restkit.ObjectMapping", DISPATCH_QUEUE_SERIAL)];
|
||||
[RKObjectMapping setDefaultDateFormatters:nil];
|
||||
[RKManagedObjectStore deleteStoreInApplicationDataDirectoryWithFilename:RKTestFactoryDefaultStoreFilename];
|
||||
|
||||
// Delete the store if it exists
|
||||
NSString *path = [[RKDirectory applicationDataDirectory] stringByAppendingPathComponent:RKTestFactoryDefaultStoreFilename];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
[RKManagedObjectStore deleteStoreInApplicationDataDirectoryWithFilename:RKTestFactoryDefaultStoreFilename];
|
||||
}
|
||||
|
||||
if ([self respondsToSelector:@selector(didSetUp)]) {
|
||||
[self didSetUp];
|
||||
@@ -218,7 +235,7 @@ static RKTestFactory *sharedFactory = nil;
|
||||
NSString* cachePath = [RKDirectory cachesDirectory];
|
||||
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:cachePath error:&error];
|
||||
if (success) {
|
||||
RKLogInfo(@"Cleared cache directory...");
|
||||
RKLogDebug(@"Cleared cache directory...");
|
||||
success = [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:&error];
|
||||
if (!success) {
|
||||
RKLogError(@"Failed creation of cache path '%@': %@", cachePath, [error localizedDescription]);
|
||||
|
||||
@@ -21,60 +21,102 @@
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "RKTableSection.h"
|
||||
#import "RKTableViewCellMappings.h"
|
||||
#import "RKTableItem.h"
|
||||
#import "RKObjectManager.h"
|
||||
#import "RKObjectMapping.h"
|
||||
#import "RKObjectLoader.h"
|
||||
|
||||
/** @name Constants */
|
||||
|
||||
/** Posted when the table view model starts loading */
|
||||
extern NSString* const RKTableControllerDidStartLoadNotification;
|
||||
|
||||
/** Posted when the table view model finishes loading */
|
||||
extern NSString* const RKTableControllerDidFinishLoadNotification;
|
||||
|
||||
/** Posted when the table view model has loaded objects into the table view */
|
||||
extern NSString* const RKTableControllerDidLoadObjectsNotification;
|
||||
|
||||
/** Posted when the table view model has loaded an empty collection of objects into the table view */
|
||||
extern NSString* const RKTableControllerDidLoadEmptyNotification;
|
||||
|
||||
/** Posted when the table view model has loaded an error */
|
||||
extern NSString* const RKTableControllerDidLoadErrorNotification;
|
||||
|
||||
/** Posted when the table view model has transitioned from offline to online */
|
||||
extern NSString* const RKTableControllerDidBecomeOnline;
|
||||
|
||||
/** Posted when the table view model has transitioned from online to offline */
|
||||
extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
|
||||
@protocol RKTableControllerDelegate;
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Constants
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
RestKit's table view abstraction leverages the object mapping engine to transform
|
||||
local objects into UITableViewCell representations. The table view model encapsulates
|
||||
the functionality of a UITableView dataSource and delegate into a single reusable
|
||||
component.
|
||||
Posted when the table controller starts loading.
|
||||
*/
|
||||
@interface RKAbstractTableController : NSObject <UITableViewDataSource, UITableViewDelegate> {
|
||||
@protected
|
||||
UIView *_tableOverlayView;
|
||||
UIImageView *_stateOverlayImageView;
|
||||
UIView *_pullToRefreshHeaderView;
|
||||
RKCache *_cache;
|
||||
}
|
||||
extern NSString * const RKTableControllerDidStartLoadNotification;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
Posted when the table controller finishes loading.
|
||||
*/
|
||||
extern NSString * const RKTableControllerDidFinishLoadNotification;
|
||||
|
||||
/**
|
||||
Posted when the table controller has loaded objects into the table view.
|
||||
*/
|
||||
extern NSString * const RKTableControllerDidLoadObjectsNotification;
|
||||
|
||||
/**
|
||||
Posted when the table controller has loaded an empty collection of objects into the table view.
|
||||
*/
|
||||
extern NSString * const RKTableControllerDidLoadEmptyNotification;
|
||||
|
||||
/**
|
||||
Posted when the table controller has loaded an error.
|
||||
*/
|
||||
extern NSString * const RKTableControllerDidLoadErrorNotification;
|
||||
|
||||
/**
|
||||
Posted when the table controller has transitioned from an offline to online state.
|
||||
*/
|
||||
extern NSString * const RKTableControllerDidBecomeOnline;
|
||||
|
||||
/**
|
||||
Posted when the table controller has transitioned from an online to an offline state.
|
||||
*/
|
||||
extern NSString * const RKTableControllerDidBecomeOffline;
|
||||
|
||||
@protocol RKAbstractTableControllerDelegate;
|
||||
|
||||
/**
|
||||
@enum RKTableControllerState
|
||||
|
||||
@constant RKTableControllerStateNormal Indicates that the table has
|
||||
loaded normally and is displaying cell content. It is not loading content,
|
||||
is not empty, has not loaded an error, and is not offline.
|
||||
|
||||
@constant RKTableControllerStateLoading Indicates that the table controller
|
||||
is loading content from a remote source.
|
||||
|
||||
@constant RKTableControllerStateEmpty Indicates that the table controller has
|
||||
retrieved an empty collection of objects.
|
||||
|
||||
@constant RKTableControllerStateError Indicates that the table controller has
|
||||
encountered an error while attempting to load.
|
||||
|
||||
@constant RKTableControllerStateOffline Indicates that the table controller is
|
||||
offline and cannot perform network access.
|
||||
|
||||
@constant RKTableControllerStateNotYetLoaded Indicates that the table controller is
|
||||
has not yet attempted a load and state is unknown.
|
||||
*/
|
||||
enum RKTableControllerState {
|
||||
RKTableControllerStateNormal = 0,
|
||||
RKTableControllerStateLoading = 1 << 1,
|
||||
RKTableControllerStateEmpty = 1 << 2,
|
||||
RKTableControllerStateError = 1 << 3,
|
||||
RKTableControllerStateOffline = 1 << 4,
|
||||
RKTableControllerStateNotYetLoaded = 0xFF000000
|
||||
};
|
||||
typedef NSUInteger RKTableControllerState;
|
||||
|
||||
/**
|
||||
RKAbstractTableController is an abstract base class for concrete table controller classes.
|
||||
A table controller object acts as both the delegate and data source for a UITableView
|
||||
object and leverages the RestKit object mapping engine to transform local domain models
|
||||
into UITableViewCell representations. Concrete implementations are provided for the
|
||||
display of static table views and Core Data backed fetched results controller basied
|
||||
table views.
|
||||
*/
|
||||
@interface RKAbstractTableController : NSObject <UITableViewDataSource, UITableViewDelegate>
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Configuring the Table Controller
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
@property (nonatomic, assign) id<RKTableControllerDelegate> delegate;
|
||||
@property (nonatomic, readonly) UIViewController* viewController;
|
||||
@property (nonatomic, readonly) UITableView* tableView;
|
||||
@property (nonatomic, readonly) NSMutableArray* sections;
|
||||
@property (nonatomic, assign) id<RKAbstractTableControllerDelegate> delegate;
|
||||
@property (nonatomic, readonly) UIViewController *viewController;
|
||||
@property (nonatomic, readonly) UITableView *tableView;
|
||||
@property (nonatomic, assign) UITableViewRowAnimation defaultRowAnimation;
|
||||
|
||||
@property (nonatomic, assign) BOOL pullToRefreshEnabled;
|
||||
@@ -82,38 +124,39 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
@property (nonatomic, assign) BOOL canMoveRows;
|
||||
@property (nonatomic, assign) BOOL autoResizesForKeyboard;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Instantiation
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
+ (id)tableControllerWithTableView:(UITableView*)tableView
|
||||
forViewController:(UIViewController*)viewController;
|
||||
+ (id)tableControllerWithTableView:(UITableView *)tableView
|
||||
forViewController:(UIViewController *)viewController;
|
||||
|
||||
+ (id)tableControllerForTableViewController:(UITableViewController*)tableViewController;
|
||||
+ (id)tableControllerForTableViewController:(UITableViewController *)tableViewController;
|
||||
|
||||
- (id)initWithTableView:(UITableView*)tableView
|
||||
viewController:(UIViewController*)viewController;
|
||||
- (id)initWithTableView:(UITableView *)tableView
|
||||
viewController:(UIViewController *)viewController;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Object to Table View Cell Mappings
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
@property (nonatomic, retain) RKTableViewCellMappings* cellMappings;
|
||||
@property (nonatomic, retain) RKTableViewCellMappings *cellMappings;
|
||||
|
||||
- (void)mapObjectsWithClass:(Class)objectClass toTableCellsWithMapping:(RKTableViewCellMapping*)cellMapping;
|
||||
- (void)mapObjectsWithClassName:(NSString *)objectClassName toTableCellsWithMapping:(RKTableViewCellMapping*)cellMapping;
|
||||
- (void)mapObjectsWithClass:(Class)objectClass toTableCellsWithMapping:(RKTableViewCellMapping *)cellMapping;
|
||||
- (void)mapObjectsWithClassName:(NSString *)objectClassName toTableCellsWithMapping:(RKTableViewCellMapping *)cellMapping;
|
||||
- (id)objectForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (RKTableViewCellMapping*)cellMappingForObjectAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (RKTableViewCellMapping *)cellMappingForObjectAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
Return the index path of the object within the table
|
||||
*/
|
||||
- (NSIndexPath *)indexPathForObject:(id)object;
|
||||
- (UITableViewCell *)cellForObject:(id)object;
|
||||
- (void)reloadRowForObject:(id)object withRowAnimation:(UITableViewRowAnimation)rowAnimation;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Header and Footer Rows
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
- (void)addHeaderRowForItem:(RKTableItem *)tableItem;
|
||||
- (void)addFooterRowForItem:(RKTableItem *)tableItem;
|
||||
@@ -122,12 +165,12 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
- (void)removeAllHeaderRows;
|
||||
- (void)removeAllFooterRows;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name RESTful Table Loading
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
The object manager instance this table view model is associated with.
|
||||
The object manager instance this table controller is associated with.
|
||||
|
||||
This instance is used for creating object loaders when loading Network
|
||||
tables and provides the managed object store used for Core Data tables.
|
||||
@@ -144,42 +187,102 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
- (void)cancelLoad;
|
||||
- (BOOL)isAutoRefreshNeeded;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
/// @name Model State Views
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Inspecting Table State
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
The current state of the table controller. Note that the controller may be in more
|
||||
than one state (e.g. loading | empty).
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) RKTableControllerState state;
|
||||
|
||||
/**
|
||||
An error object that was encountered as the result of an attempt to load
|
||||
the table. Will return a value when the table is in the error state,
|
||||
otherwise nil.
|
||||
*/
|
||||
@property (nonatomic, readonly, retain) NSError *error;
|
||||
|
||||
/**
|
||||
Returns a Boolean value indicating if the table controller is currently
|
||||
loading content.
|
||||
*/
|
||||
- (BOOL)isLoading;
|
||||
|
||||
/**
|
||||
Returns a Boolean value indicating if the table controller has attempted
|
||||
a load and transitioned into any state.
|
||||
*/
|
||||
- (BOOL)isLoaded;
|
||||
|
||||
/**
|
||||
Returns a Boolean value indicating if the table controller has loaded an
|
||||
empty set of content.
|
||||
|
||||
When YES and there is not an empty item configured, the table controller
|
||||
will optionally display an empty image overlayed on top of the table view.
|
||||
|
||||
**NOTE**: It is possible for an empty table controller to display cells
|
||||
witin the managed table view in the event an empty item or header/footer
|
||||
rows are configured.
|
||||
|
||||
@see imageForEmpty
|
||||
*/
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
/**
|
||||
Returns a Boolean value indicating if the table controller is online
|
||||
and network operations may be performed.
|
||||
*/
|
||||
- (BOOL)isOnline;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isError;
|
||||
@property (nonatomic, readonly, retain) NSError* error;
|
||||
/**
|
||||
Returns a Boolean value indicating if the table controller is offline.
|
||||
|
||||
When YES, the table controller will optionally display an offline image
|
||||
overlayed on top of the table view.
|
||||
|
||||
@see imageForOffline
|
||||
*/
|
||||
- (BOOL)isOffline;
|
||||
|
||||
/**
|
||||
Returns a Boolean value indicating if the table controller encountered
|
||||
an error while attempting to load.
|
||||
|
||||
When YES, the table controller will optionally display an error image
|
||||
overlayed on top of the table view.
|
||||
|
||||
@see imageForError
|
||||
*/
|
||||
- (BOOL)isError;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Model State Views
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
An image to overlay onto the table when the table view
|
||||
does not have any row data to display. It will be centered
|
||||
within the table view
|
||||
within the table view.
|
||||
*/
|
||||
// TODO: Should be emptyImage
|
||||
@property (nonatomic, retain) UIImage* imageForEmpty;
|
||||
@property (nonatomic, retain) UIImage *imageForEmpty;
|
||||
|
||||
/**
|
||||
An image to overlay onto the table when a load operation
|
||||
has encountered an error. It will be centered
|
||||
within the table view.
|
||||
*/
|
||||
// TODO: Should be errorImage
|
||||
@property (nonatomic, retain) UIImage* imageForError;
|
||||
@property (nonatomic, retain) UIImage *imageForError;
|
||||
|
||||
/**
|
||||
An image to overlay onto the table with when the user does
|
||||
not have connectivity to the Internet
|
||||
not have connectivity to the Internet.
|
||||
|
||||
@see RKReachabilityObserver
|
||||
*/
|
||||
// TODO: Should be offlineImage
|
||||
@property (nonatomic, retain) UIImage* imageForOffline;
|
||||
@property (nonatomic, retain) UIImage *imageForOffline;
|
||||
|
||||
/**
|
||||
A UIView to add to the table overlay during loading. It
|
||||
@@ -187,7 +290,19 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
|
||||
The loading view is always presented non-modally.
|
||||
*/
|
||||
@property (nonatomic, retain) UIView* loadingView;
|
||||
@property (nonatomic, retain) UIView *loadingView;
|
||||
|
||||
/**
|
||||
Returns the image, if any, configured for display when the table controller
|
||||
is in the given state.
|
||||
|
||||
**NOTE** This method accepts a single state value.
|
||||
|
||||
@param state The table controller state
|
||||
@return The image for the specified state, else nil. Always returns nil for
|
||||
RKTableControllerStateNormal, RKTableControllerStateLoading and RKTableControllerStateLoading.
|
||||
*/
|
||||
- (UIImage *)imageForState:(RKTableControllerState)state;
|
||||
|
||||
/**
|
||||
A rectangle configuring the dimensions for the overlay view that is
|
||||
@@ -200,6 +315,11 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
*/
|
||||
@property (nonatomic, assign) CGRect overlayFrame;
|
||||
|
||||
/**
|
||||
The image currently displayed within the overlay view.
|
||||
*/
|
||||
@property (nonatomic, readonly) UIImage *overlayImage;
|
||||
|
||||
/**
|
||||
When YES, the image view added to the table overlay for displaying table
|
||||
state (i.e. for offline, error and empty) will be displayed modally
|
||||
@@ -213,54 +333,47 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
@property (nonatomic, assign) BOOL variableHeightRows;
|
||||
@property (nonatomic, assign) BOOL showsHeaderRowsWhenEmpty;
|
||||
@property (nonatomic, assign) BOOL showsFooterRowsWhenEmpty;
|
||||
@property (nonatomic, retain) RKTableItem* emptyItem;
|
||||
@property (nonatomic, retain) RKTableItem *emptyItem;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Managing Sections
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/** The number of sections in the model. */
|
||||
/**
|
||||
The number of sections in the table.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger sectionCount;
|
||||
|
||||
/** The number of rows across all sections in the model. */
|
||||
/**
|
||||
The number of rows across all sections in the model.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger rowCount;
|
||||
|
||||
/** Returns the section at the specified index.
|
||||
* @param index Must be less than the total number of sections. */
|
||||
- (RKTableSection *)sectionAtIndex:(NSUInteger)index;
|
||||
|
||||
/** Returns the first section with the specified header title.
|
||||
* @param title The header title. */
|
||||
- (RKTableSection *)sectionWithHeaderTitle:(NSString *)title;
|
||||
|
||||
/**
|
||||
Returns the number of rows in the section at the given index.
|
||||
|
||||
@param index The index of the section to return the row count for.
|
||||
@returns The
|
||||
@returns The number of rows contained within the section with the given index.
|
||||
@raises NSInvalidArgumentException Raised if index is greater than or
|
||||
equal to the total number of sections in the table.
|
||||
equal to the total number of sections in the table.
|
||||
*/
|
||||
- (NSUInteger)numberOfRowsInSectionAtIndex:(NSUInteger)index;
|
||||
- (NSUInteger)numberOfRowsInSection:(NSUInteger)index;
|
||||
|
||||
/** Returns the index of the specified section.
|
||||
* @param section Must be a valid non nil RKTableViewSection.
|
||||
* @return If section is not found, method returns NSNotFound. */
|
||||
- (NSUInteger)indexForSection:(RKTableSection *)section;
|
||||
|
||||
/** Returns the UITableViewCell created by applying the specified
|
||||
* mapping operation to the object identified by indexPath.
|
||||
* @param indexPath The indexPath in the tableView for which a cell
|
||||
* is needed. */
|
||||
/**
|
||||
Returns the UITableViewCell created by applying the specified
|
||||
mapping operation to the object identified by indexPath.
|
||||
|
||||
@param indexPath The indexPath in the tableView for which a cell is needed.
|
||||
*/
|
||||
- (UITableViewCell *)cellForObjectAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Managing Swipe View
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
@property (nonatomic, assign) BOOL cellSwipeViewsEnabled;
|
||||
@property (nonatomic, retain) UIView* cellSwipeView;
|
||||
@property (nonatomic, readonly) UITableViewCell* swipeCell;
|
||||
@property (nonatomic, retain) UIView *cellSwipeView;
|
||||
@property (nonatomic, readonly) UITableViewCell *swipeCell;
|
||||
@property (nonatomic, readonly) id swipeObject;
|
||||
@property (nonatomic, readonly) BOOL animatingCellSwipe;
|
||||
@property (nonatomic, readonly) UISwipeGestureRecognizerDirection swipeDirection;
|
||||
@@ -270,46 +383,45 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
|
||||
@end
|
||||
|
||||
@protocol RKTableControllerDelegate <NSObject>
|
||||
@protocol RKAbstractTableControllerDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
// Network
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willLoadTableWithObjectLoader:(RKObjectLoader*)objectLoader;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didLoadTableWithObjectLoader:(RKObjectLoader*)objectLoader;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willLoadTableWithObjectLoader:(RKObjectLoader *)objectLoader;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didLoadTableWithObjectLoader:(RKObjectLoader *)objectLoader;
|
||||
|
||||
// Basic States
|
||||
- (void)tableControllerDidStartLoad:(RKAbstractTableController *)tableController;
|
||||
|
||||
/** Sent when the table view has transitioned out of the loading state regardless of outcome **/
|
||||
/**
|
||||
Sent when the table view has transitioned out of the loading state regardless of outcome
|
||||
*/
|
||||
- (void)tableControllerDidFinishLoad:(RKAbstractTableController *)tableController;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didFailLoadWithError:(NSError*)error;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didFailLoadWithError:(NSError *)error;
|
||||
- (void)tableControllerDidCancelLoad:(RKAbstractTableController *)tableController;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didLoadObjects:(NSArray*)objects inSection:(NSUInteger)sectionIndex;
|
||||
|
||||
/** Sent to the delegate when the controller is really and truly finished loading/updating, whether from the network or from Core Data, or from static data, ... this happens in didFinishLoading
|
||||
**/
|
||||
/**
|
||||
Sent to the delegate when the controller is really and truly finished loading/updating, whether from the network or from Core Data,
|
||||
or from static data, ... this happens in didFinishLoading
|
||||
*/
|
||||
- (void)tableControllerDidFinalizeLoad:(RKAbstractTableController *)tableController;
|
||||
|
||||
/**
|
||||
Sent to the delegate when the content of the table view has become empty
|
||||
*/
|
||||
- (void)tableControllerDidBecomeEmpty:(RKAbstractTableController *)tableController; // didLoadEmpty???
|
||||
- (void)tableControllerDidBecomeEmpty:(RKAbstractTableController *)tableController;
|
||||
|
||||
/**
|
||||
Sent to the delegate when the table view model has transitioned from offline to online
|
||||
Sent to the delegate when the table controller has transitioned from offline to online
|
||||
*/
|
||||
- (void)tableControllerDidBecomeOnline:(RKAbstractTableController *)tableController;
|
||||
|
||||
/**
|
||||
Sent to the delegate when the table view model has transitioned from online to offline
|
||||
Sent to the delegate when the table controller has transitioned from online to offline
|
||||
*/
|
||||
- (void)tableControllerDidBecomeOffline:(RKAbstractTableController *)tableController;
|
||||
|
||||
// Sections
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didInsertSection:(RKTableSection *)section atIndex:(NSUInteger)sectionIndex;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didRemoveSection:(RKTableSection *)section atIndex:(NSUInteger)sectionIndex;
|
||||
|
||||
// Objects
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didInsertObject:(id)object atIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didUpdateObject:(id)object atIndexPath:(NSIndexPath *)indexPath;
|
||||
@@ -320,19 +432,13 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didEndEditing:(id)object atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
// Swipe Views
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willAddSwipeView:(UIView*)swipeView toCell:(UITableViewCell *)cell forObject:(id)object;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willRemoveSwipeView:(UIView*)swipeView fromCell:(UITableViewCell *)cell forObject:(id)object;
|
||||
|
||||
// BELOW NOT YET IMPLEMENTED
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willAddSwipeView:(UIView *)swipeView toCell:(UITableViewCell *)cell forObject:(id)object;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willRemoveSwipeView:(UIView *)swipeView fromCell:(UITableViewCell *)cell forObject:(id)object;
|
||||
|
||||
// Cells
|
||||
- (void)tableController:(RKAbstractTableController *)tableController willDisplayCell:(UITableViewCell *)cell forObject:(id)object atIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)tableController:(RKAbstractTableController *)tableController didSelectCell:(UITableViewCell *)cell forObject:(id)object atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
// Objects
|
||||
- (void)tableControllerDidBeginUpdates:(RKAbstractTableController *)tableController;
|
||||
- (void)tableControllerDidEndUpdates:(RKAbstractTableController *)tableController;
|
||||
|
||||
@end
|
||||
|
||||
#endif // TARGET_OS_IPHONE
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,25 +18,29 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "RKRefreshGestureRecognizer.h"
|
||||
|
||||
/*
|
||||
A private continuation class for subclass implementations of RKAbstractTableController
|
||||
*/
|
||||
@interface RKAbstractTableController () <RKObjectLoaderDelegate, RKRefreshTriggerProtocol>
|
||||
|
||||
@property (nonatomic, readwrite, assign) UITableView* tableView;
|
||||
@property (nonatomic, readwrite, assign) UIViewController* viewController;
|
||||
@property (nonatomic, readwrite, retain) RKObjectLoader* objectLoader;
|
||||
@property (nonatomic, readwrite, assign) BOOL loading;
|
||||
@property (nonatomic, readwrite, assign) BOOL loaded;
|
||||
@property (nonatomic, readwrite, assign) BOOL empty;
|
||||
@property (nonatomic, readwrite, assign) BOOL online;
|
||||
@property (nonatomic, readwrite, retain) NSError* error;
|
||||
@property (nonatomic, readwrite, retain) NSMutableArray* headerItems;
|
||||
@property (nonatomic, readwrite, retain) NSMutableArray* footerItems;
|
||||
|
||||
@property (nonatomic, readwrite, assign) UITableView *tableView;
|
||||
@property (nonatomic, readwrite, assign) UIViewController *viewController;
|
||||
@property (nonatomic, assign, readwrite) RKTableControllerState state;
|
||||
@property (nonatomic, readwrite, retain) RKObjectLoader *objectLoader;
|
||||
@property (nonatomic, readwrite, retain) NSError *error;
|
||||
@property (nonatomic, readwrite, retain) NSMutableArray *headerItems;
|
||||
@property (nonatomic, readwrite, retain) NSMutableArray *footerItems;
|
||||
@property (nonatomic, readonly) UIView *tableOverlayView;
|
||||
@property (nonatomic, readonly) UIImageView *stateOverlayImageView;
|
||||
@property (nonatomic, readonly) RKCache *cache;
|
||||
@property (nonatomic, retain) UIView *pullToRefreshHeaderView;
|
||||
|
||||
#pragma mark - Subclass Load Event Hooks
|
||||
|
||||
- (void)didStartLoad;
|
||||
|
||||
/**
|
||||
Must be invoked when the table controller has finished loading.
|
||||
@@ -45,7 +49,7 @@
|
||||
and cleaning up the table overlay view.
|
||||
*/
|
||||
- (void)didFinishLoad;
|
||||
- (void)updateOfflineImageForOnlineState:(BOOL)isOnline;
|
||||
- (void)didFailLoadWithError:(NSError *)error;
|
||||
|
||||
#pragma mark - Table View Overlay
|
||||
|
||||
@@ -61,5 +65,14 @@
|
||||
- (void)pullToRefreshStateChanged:(UIGestureRecognizer *)gesture;
|
||||
- (void)resetPullToRefreshRecognizer;
|
||||
|
||||
/**
|
||||
Returns a Boolean value indicating if the table controller
|
||||
should be considered empty and transitioned into the empty state.
|
||||
Used by the abstract table controller to trigger state transitions.
|
||||
|
||||
**NOTE**: This is an abstract method that MUST be implemented with
|
||||
a subclass.
|
||||
*/
|
||||
- (BOOL)isConsideredEmpty;
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
|
||||
typedef UIView *(^RKFetchedResultsTableViewViewForHeaderInSectionBlock)(NSUInteger sectionIndex, NSString *sectionTitle);
|
||||
|
||||
@class RKFetchedResultsTableController;
|
||||
@protocol RKFetchedResultsTableControllerDelegate <RKAbstractTableControllerDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
// Sections
|
||||
- (void)tableController:(RKFetchedResultsTableController *)tableController didInsertSectionAtIndex:(NSUInteger)sectionIndex;
|
||||
- (void)tableController:(RKFetchedResultsTableController *)tableController didDeleteSectionAtIndex:(NSUInteger)sectionIndex;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
Instances of RKFetchedResultsTableController provide an interface for driving a UITableView
|
||||
*/
|
||||
@@ -33,7 +44,8 @@ typedef UIView *(^RKFetchedResultsTableViewViewForHeaderInSectionBlock)(NSUInteg
|
||||
BOOL _isEmptyBeforeAnimation;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (nonatomic, assign) id<RKFetchedResultsTableControllerDelegate> delegate;
|
||||
@property (nonatomic, retain, readonly) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (nonatomic, copy) NSString *resourcePath;
|
||||
@property (nonatomic, retain) NSFetchRequest *fetchRequest;
|
||||
@property (nonatomic, assign) CGFloat heightForHeaderInSection;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import "RKFetchedResultsTableController.h"
|
||||
#import "RKAbstractTableController_Internals.h"
|
||||
#import "RKManagedObjectStore.h"
|
||||
@@ -31,12 +32,15 @@
|
||||
#define RKLogComponent lcl_cRestKitUI
|
||||
|
||||
@interface RKFetchedResultsTableController ()
|
||||
- (void)performFetch;
|
||||
@property (nonatomic, retain, readwrite) NSFetchedResultsController *fetchedResultsController;
|
||||
|
||||
- (BOOL)performFetch:(NSError **)error;
|
||||
- (void)updateSortedArray;
|
||||
@end
|
||||
|
||||
@implementation RKFetchedResultsTableController
|
||||
|
||||
@dynamic delegate;
|
||||
@synthesize fetchedResultsController = _fetchedResultsController;
|
||||
@synthesize resourcePath = _resourcePath;
|
||||
@synthesize heightForHeaderInSection = _heightForHeaderInSection;
|
||||
@@ -75,18 +79,31 @@
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)performFetch {
|
||||
- (BOOL)performFetch:(NSError **)error {
|
||||
// TODO: We could be doing a KVO on the predicate/sortDescriptors/sectionKeyPath and intelligently deleting the cache
|
||||
[NSFetchedResultsController deleteCacheWithName:_fetchedResultsController.cacheName];
|
||||
|
||||
NSError* error;
|
||||
BOOL success = [_fetchedResultsController performFetch:&error];
|
||||
BOOL success = [_fetchedResultsController performFetch:error];
|
||||
if (!success) {
|
||||
self.error = error;
|
||||
RKLogError(@"performFetch failed with error: %@", [error localizedDescription]);
|
||||
RKLogError(@"performFetch failed with error: %@", [*error localizedDescription]);
|
||||
return NO;
|
||||
} else {
|
||||
RKLogTrace(@"performFetch completed successfully");
|
||||
for (NSUInteger index = 0; index < [self sectionCount]; index++) {
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didInsertSectionAtIndex:)]) {
|
||||
[self.delegate tableController:self didInsertSectionAtIndex:index];
|
||||
}
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didInsertObject:atIndexPath:)]) {
|
||||
for (NSUInteger row = 0; row < [self numberOfRowsInSection:index]; row++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:index];
|
||||
id object = [self objectForRowAtIndexPath:indexPath];
|
||||
[self.delegate tableController:self didInsertObject:object atIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateSortedArray {
|
||||
@@ -217,8 +234,7 @@
|
||||
} else {
|
||||
fetchRequest = _fetchRequest;
|
||||
}
|
||||
NSAssert(fetchRequest != nil, @"Attempted to load RKFetchedResultsTableController with nil fetchRequest for resourcePath %@, fetchRequest %@",
|
||||
_resourcePath, _fetchRequest);
|
||||
NSAssert(fetchRequest != nil, @"Attempted to load RKFetchedResultsTableController with nil fetchRequest for resourcePath %@, fetchRequest %@", _resourcePath, _fetchRequest);
|
||||
|
||||
if (_predicate) {
|
||||
[fetchRequest setPredicate:_predicate];
|
||||
@@ -226,21 +242,21 @@
|
||||
if (_sortDescriptors) {
|
||||
[fetchRequest setSortDescriptors:_sortDescriptors];
|
||||
}
|
||||
|
||||
[_fetchedResultsController setDelegate:nil];
|
||||
[_fetchedResultsController release];
|
||||
_fetchedResultsController = nil;
|
||||
|
||||
_fetchedResultsController =
|
||||
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[NSManagedObjectContext contextForCurrentThread]
|
||||
sectionNameKeyPath:_sectionNameKeyPath
|
||||
cacheName:_cacheName];
|
||||
|
||||
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[NSManagedObjectContext contextForCurrentThread]
|
||||
sectionNameKeyPath:_sectionNameKeyPath
|
||||
cacheName:_cacheName];
|
||||
_fetchedResultsController.delegate = self;
|
||||
|
||||
[self performFetch];
|
||||
|
||||
// Perform the load
|
||||
NSError *error;
|
||||
[self didStartLoad];
|
||||
BOOL success = [self performFetch:&error];
|
||||
if (! success) {
|
||||
[self didFailLoadWithError:error];
|
||||
}
|
||||
[self updateSortedArray];
|
||||
|
||||
[self.tableView reloadData];
|
||||
[self didFinishLoad];
|
||||
|
||||
@@ -470,7 +486,7 @@
|
||||
return self.emptyItem;
|
||||
|
||||
} else if ([self isHeaderIndexPath:indexPath]) {
|
||||
NSUInteger row = (self.empty && self.emptyItem) ? (indexPath.row - 1) : indexPath.row;
|
||||
NSUInteger row = ([self isEmpty] && self.emptyItem) ? (indexPath.row - 1) : indexPath.row;
|
||||
return [self.headerItems objectAtIndex:row];
|
||||
|
||||
} else if ([self isFooterIndexPath:indexPath]) {
|
||||
@@ -499,7 +515,7 @@
|
||||
|
||||
#pragma mark - KVO & Model States
|
||||
|
||||
- (BOOL)isEmpty {
|
||||
- (BOOL)isConsideredEmpty {
|
||||
NSUInteger fetchedObjectsCount = [[_fetchedResultsController fetchedObjects] count];
|
||||
BOOL isEmpty = (fetchedObjectsCount == 0);
|
||||
RKLogTrace(@"Determined isEmpty = %@. fetchedObjects count = %d", isEmpty ? @"YES" : @"NO", fetchedObjectsCount);
|
||||
@@ -511,7 +527,7 @@
|
||||
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller {
|
||||
RKLogTrace(@"Beginning updates for fetchedResultsController (%@). Current section count = %d (resource path: %@)", controller, [[controller sections] count], _resourcePath);
|
||||
|
||||
if(_sortSelector) return;
|
||||
if (_sortSelector) return;
|
||||
|
||||
[self.tableView beginUpdates];
|
||||
_isEmptyBeforeAnimation = [self isEmpty];
|
||||
@@ -522,17 +538,25 @@
|
||||
atIndex:(NSUInteger)sectionIndex
|
||||
forChangeType:(NSFetchedResultsChangeType)type {
|
||||
|
||||
if(_sortSelector) return;
|
||||
if (_sortSelector) return;
|
||||
|
||||
switch (type) {
|
||||
case NSFetchedResultsChangeInsert:
|
||||
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didInsertSectionAtIndex:)]) {
|
||||
[self.delegate tableController:self didInsertSectionAtIndex:sectionIndex];
|
||||
}
|
||||
break;
|
||||
|
||||
case NSFetchedResultsChangeDelete:
|
||||
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didDeleteSectionAtIndex:)]) {
|
||||
[self.delegate tableController:self didDeleteSectionAtIndex:sectionIndex];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -547,7 +571,7 @@
|
||||
forChangeType:(NSFetchedResultsChangeType)type
|
||||
newIndexPath:(NSIndexPath *)newIndexPath {
|
||||
|
||||
if(_sortSelector) return;
|
||||
if (_sortSelector) return;
|
||||
|
||||
NSIndexPath* adjIndexPath = [self indexPathForFetchedResultsIndexPath:indexPath];
|
||||
NSIndexPath* adjNewIndexPath = [self indexPathForFetchedResultsIndexPath:newIndexPath];
|
||||
@@ -594,13 +618,27 @@
|
||||
|
||||
[self updateSortedArray];
|
||||
|
||||
if(_sortSelector) {
|
||||
if (_sortSelector) {
|
||||
[self.tableView reloadData];
|
||||
} else {
|
||||
[self.tableView endUpdates];
|
||||
}
|
||||
|
||||
|
||||
[self didFinishLoad];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource methods
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView*)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSAssert(theTableView == self.tableView, @"tableView:cellForRowAtIndexPath: invoked with inappropriate tableView: %@", theTableView);
|
||||
UITableViewCell* cell = [self cellForObjectAtIndexPath:indexPath];
|
||||
|
||||
RKLogTrace(@"%@ cellForRowAtIndexPath:%@ = %@", self, indexPath, cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfRowsInSection:(NSUInteger)index {
|
||||
return [self tableView:self.tableView numberOfRowsInSection:index];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,19 +29,26 @@
|
||||
#import "RKObjectMapping.h"
|
||||
#import "RKObjectLoader.h"
|
||||
|
||||
@protocol RKTableControllerDelegate <RKAbstractTableControllerDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)tableController:(RKTableController *)tableController didLoadObjects:(NSArray *)objects inSection:(RKTableSection *)section;
|
||||
|
||||
@end
|
||||
|
||||
@interface RKTableController : RKAbstractTableController
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
@property (nonatomic, assign) id<RKTableControllerDelegate> delegate;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Static Tables
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
- (void)loadObjects:(NSArray *)objects;
|
||||
- (void)loadObjects:(NSArray *)objects inSection:(NSUInteger)sectionIndex;
|
||||
- (void)loadEmpty;
|
||||
|
||||
// Move to superclass???
|
||||
- (void)reloadRowForObject:(id)object withRowAnimation:(UITableViewRowAnimation)rowAnimation;
|
||||
|
||||
/**
|
||||
Load an array of RKTableItems into table cells of the specified class. A table cell
|
||||
mapping will be constructed on your behalf and yielded to the block for configuration.
|
||||
@@ -78,12 +85,16 @@
|
||||
*/
|
||||
- (void)loadTableItems:(NSArray *)tableItems inSection:(NSUInteger)sectionIndex;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/** @name Network Tables */
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
- (void)loadTableFromResourcePath:(NSString *)resourcePath;
|
||||
- (void)loadTableFromResourcePath:(NSString *)resourcePath usingBlock:(void (^)(RKObjectLoader *objectLoader))block;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/** @name Forms */
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
The form that the table has been loaded with (if any)
|
||||
@@ -98,15 +109,37 @@
|
||||
*/
|
||||
- (void)loadForm:(RKForm *)form;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Managing Sections
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
@property (nonatomic, readonly) NSMutableArray *sections;
|
||||
|
||||
/**
|
||||
The key path on the loaded objects used to determine the section they belong to.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *sectionNameKeyPath;
|
||||
|
||||
/**
|
||||
Returns the section at the specified index.
|
||||
@param index Must be less than the total number of sections.
|
||||
*/
|
||||
- (RKTableSection *)sectionAtIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Returns the first section with the specified header title.
|
||||
@param title The header title.
|
||||
*/
|
||||
- (RKTableSection *)sectionWithHeaderTitle:(NSString *)title;
|
||||
|
||||
/**
|
||||
Returns the index of the specified section.
|
||||
|
||||
@param section Must be a valid non nil RKTableViewSection.
|
||||
@return The index of the given section if contained within the receiver, otherwise NSNotFound.
|
||||
*/
|
||||
- (NSUInteger)indexForSection:(RKTableSection *)section;
|
||||
|
||||
// Coalesces a series of table view updates performed within the block into
|
||||
// a single animation using beginUpdates: and endUpdates: on the table view
|
||||
// TODO: Move to super-class?
|
||||
|
||||
@@ -23,27 +23,35 @@
|
||||
#import "RKLog.h"
|
||||
#import "RKFormSection.h"
|
||||
#import "NSArray+RKAdditions.h"
|
||||
#import "RKObjectMappingOperation.h"
|
||||
|
||||
// Define logging component
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent lcl_cRestKitUI
|
||||
|
||||
@interface RKTableController ()
|
||||
@property (nonatomic, readwrite) NSMutableArray *sections;
|
||||
@end
|
||||
|
||||
@implementation RKTableController
|
||||
|
||||
@dynamic delegate;
|
||||
@synthesize form = _form;
|
||||
@synthesize sectionNameKeyPath = _sectionNameKeyPath;
|
||||
@synthesize sections = _sections;
|
||||
|
||||
#pragma mark - Instantiation
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_sections = [NSMutableArray new];
|
||||
[self addObserver:self
|
||||
forKeyPath:@"sections"
|
||||
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
|
||||
context:nil];
|
||||
|
||||
RKTableSection* section = [RKTableSection section];
|
||||
RKTableSection *section = [RKTableSection section];
|
||||
[self addSection:section];
|
||||
}
|
||||
|
||||
@@ -54,7 +62,8 @@
|
||||
[self removeObserver:self forKeyPath:@"sections"];
|
||||
[_form release];
|
||||
[_sectionNameKeyPath release];
|
||||
|
||||
[_sections release];
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -77,7 +86,7 @@
|
||||
[self.sections removeObjectsAtIndexes:indexes];
|
||||
}
|
||||
|
||||
- (void)replaceObjectsAtIndexes:(NSIndexSet *)indexes withObjects:(NSArray *)objects {
|
||||
- (void)replaceSectionsAtIndexes:(NSIndexSet *)indexes withObjects:(NSArray *)objects {
|
||||
[self.sections replaceObjectsAtIndexes:indexes withObjects:objects];
|
||||
}
|
||||
|
||||
@@ -89,11 +98,6 @@
|
||||
}
|
||||
|
||||
[[self sectionsProxy] addObject:section];
|
||||
|
||||
// TODO: move into KVO?
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didInsertSection:atIndex:)]) {
|
||||
[self.delegate tableController:self didInsertSection:section atIndex:[self.sections indexOfObject:section]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeSection:(RKTableSection *)section {
|
||||
@@ -103,10 +107,6 @@
|
||||
reason:@"Tables must always have at least one section"
|
||||
userInfo:nil];
|
||||
}
|
||||
NSUInteger index = [self.sections indexOfObject:section];
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didRemoveSection:atIndex:)]) {
|
||||
[self.delegate tableController:self didRemoveSection:section atIndex:index];
|
||||
}
|
||||
[[self sectionsProxy] removeObject:section];
|
||||
}
|
||||
|
||||
@@ -114,10 +114,6 @@
|
||||
NSAssert(section, @"Cannot insert a nil section");
|
||||
section.tableController = self;
|
||||
[[self sectionsProxy] insertObject:section atIndex:index];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didInsertSection:atIndex:)]) {
|
||||
[self.delegate tableController:self didInsertSection:section atIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeSectionAtIndex:(NSUInteger)index {
|
||||
@@ -126,21 +122,10 @@
|
||||
reason:@"Tables must always have at least one section"
|
||||
userInfo:nil];
|
||||
}
|
||||
RKTableSection* section = [self.sections objectAtIndex:index];
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didRemoveSection:atIndex:)]) {
|
||||
[self.delegate tableController:self didRemoveSection:section atIndex:index];
|
||||
}
|
||||
[[self sectionsProxy] removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeAllSections:(BOOL)recreateFirstSection {
|
||||
NSUInteger sectionCount = [self.sections count];
|
||||
for (NSUInteger index = 0; index < sectionCount; index++) {
|
||||
RKTableSection* section = [self.sections objectAtIndex:index];
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didRemoveSection:atIndex:)]) {
|
||||
[self.delegate tableController:self didRemoveSection:section atIndex:index];
|
||||
}
|
||||
}
|
||||
[[self sectionsProxy] removeAllObjects];
|
||||
|
||||
if (recreateFirstSection) {
|
||||
@@ -194,7 +179,11 @@
|
||||
}
|
||||
|
||||
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:self.defaultRowAnimation];
|
||||
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(tableController:didLoadObjects:inSection:)]) {
|
||||
[self.delegate tableController:self didLoadObjects:objects inSection:section];
|
||||
}
|
||||
|
||||
// The load is finalized via network callbacks for
|
||||
// dynamic table controllers
|
||||
if (nil == self.objectLoader) {
|
||||
@@ -325,6 +314,9 @@
|
||||
|
||||
if (self.sectionNameKeyPath) {
|
||||
NSArray *sectionedObjects = [objects sectionsGroupedByKeyPath:self.sectionNameKeyPath];
|
||||
if ([sectionedObjects count] == 0) {
|
||||
[self removeAllSections];
|
||||
}
|
||||
for (NSArray *sectionOfObjects in sectionedObjects) {
|
||||
NSUInteger sectionIndex = [sectionedObjects indexOfObject:sectionOfObjects];
|
||||
if (sectionIndex >= [self sectionCount]) {
|
||||
@@ -337,13 +329,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadRowForObject:(id)object withRowAnimation:(UITableViewRowAnimation)rowAnimation {
|
||||
NSIndexPath *indexPath = [self indexPathForObject:object];
|
||||
if (indexPath) {
|
||||
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:rowAnimation];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
if ([keyPath isEqualToString:@"sections"]) {
|
||||
@@ -376,4 +361,153 @@
|
||||
// TODO: KVO should be used for managing the row level manipulations on the table view as well...
|
||||
}
|
||||
|
||||
#pragma mark - Managing Sections
|
||||
|
||||
- (NSUInteger)sectionCount {
|
||||
return [_sections count];
|
||||
}
|
||||
|
||||
- (NSUInteger)rowCount {
|
||||
return [[_sections valueForKeyPath:@"@sum.rowCount"] intValue];
|
||||
}
|
||||
|
||||
- (RKTableSection *)sectionAtIndex:(NSUInteger)index {
|
||||
return [_sections objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSUInteger)indexForSection:(RKTableSection *)section {
|
||||
NSAssert(section, @"Cannot return index for a nil section");
|
||||
return [_sections indexOfObject:section];
|
||||
}
|
||||
|
||||
- (RKTableSection *)sectionWithHeaderTitle:(NSString *)title {
|
||||
for (RKTableSection* section in _sections) {
|
||||
if ([section.headerTitle isEqualToString:title]) {
|
||||
return section;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfRowsInSection:(NSUInteger)index {
|
||||
return [self sectionAtIndex:index].rowCount;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)cellForObjectAtIndexPath:(NSIndexPath *)indexPath {
|
||||
RKTableSection* section = [self sectionAtIndex:indexPath.section];
|
||||
id mappableObject = [section objectAtIndex:indexPath.row];
|
||||
RKTableViewCellMapping* cellMapping = [self.cellMappings cellMappingForObject:mappableObject];
|
||||
NSAssert(cellMapping, @"Cannot build a tableView cell for object %@: No cell mapping defined for objects of type '%@'", mappableObject, NSStringFromClass([mappableObject class]));
|
||||
|
||||
UITableViewCell* cell = [cellMapping mappableObjectForData:self.tableView];
|
||||
NSAssert(cell, @"Cell mapping failed to dequeue or allocate a tableViewCell for object: %@", mappableObject);
|
||||
|
||||
// Map the object state into the cell
|
||||
RKObjectMappingOperation* mappingOperation = [[RKObjectMappingOperation alloc] initWithSourceObject:mappableObject destinationObject:cell mapping:cellMapping];
|
||||
NSError* error = nil;
|
||||
BOOL success = [mappingOperation performMapping:&error];
|
||||
[mappingOperation release];
|
||||
// NOTE: If there is no mapping work performed, but no error is generated then
|
||||
// we consider the operation a success. It is common for table cells to not contain
|
||||
// any dynamically mappable content (i.e. header/footer rows, banners, etc.)
|
||||
if (success == NO && error != nil) {
|
||||
RKLogError(@"Failed to generate table cell for object: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - Cell Mappings
|
||||
|
||||
- (id)objectForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSAssert(indexPath, @"Cannot lookup object with a nil indexPath");
|
||||
RKTableSection* section = [self sectionAtIndex:indexPath.section];
|
||||
return [section objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource methods
|
||||
|
||||
- (NSString*)tableView:(UITableView*)theTableView titleForHeaderInSection:(NSInteger)section {
|
||||
NSAssert(theTableView == self.tableView, @"tableView:titleForHeaderInSection: invoked with inappropriate tableView: %@", theTableView);
|
||||
return [[_sections objectAtIndex:section] headerTitle];
|
||||
}
|
||||
|
||||
- (NSString*)tableView:(UITableView*)theTableView titleForFooterInSection:(NSInteger)section {
|
||||
NSAssert(theTableView == self.tableView, @"tableView:titleForFooterInSection: invoked with inappropriate tableView: %@", theTableView);
|
||||
return [[_sections objectAtIndex:section] footerTitle];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView*)theTableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSAssert(theTableView == self.tableView, @"tableView:canEditRowAtIndexPath: invoked with inappropriate tableView: %@", theTableView);
|
||||
return self.canEditRows;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView*)theTableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSAssert(theTableView == self.tableView, @"tableView:canMoveRowAtIndexPath: invoked with inappropriate tableView: %@", theTableView);
|
||||
return self.canMoveRows;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView*)theTableView {
|
||||
NSAssert(theTableView == self.tableView, @"numberOfSectionsInTableView: invoked with inappropriate tableView: %@", theTableView);
|
||||
RKLogTrace(@"%@ numberOfSectionsInTableView = %d", self, self.sectionCount);
|
||||
return self.sectionCount;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section {
|
||||
NSAssert(theTableView == self.tableView, @"tableView:numberOfRowsInSection: invoked with inappropriate tableView: %@", theTableView);
|
||||
RKLogTrace(@"%@ numberOfRowsInSection:%d = %d", self, section, self.sectionCount);
|
||||
return [[_sections objectAtIndex:section] rowCount];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForObject:(id)object {
|
||||
NSUInteger sectionIndex = 0;
|
||||
for (RKTableSection *section in self.sections) {
|
||||
NSUInteger rowIndex = 0;
|
||||
for (id rowObject in section.objects) {
|
||||
if ([rowObject isEqual:object]) {
|
||||
return [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
|
||||
}
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
sectionIndex++;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)theTableView heightForHeaderInSection:(NSInteger)sectionIndex {
|
||||
NSAssert(theTableView == self.tableView, @"heightForHeaderInSection: invoked with inappropriate tableView: %@", theTableView);
|
||||
RKTableSection *section = [self sectionAtIndex:sectionIndex];
|
||||
return section.headerHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)theTableView heightForFooterInSection:(NSInteger)sectionIndex {
|
||||
NSAssert(theTableView == self.tableView, @"heightForFooterInSection: invoked with inappropriate tableView: %@", theTableView);
|
||||
RKTableSection *section = [self sectionAtIndex:sectionIndex];
|
||||
return section.footerHeight;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)theTableView viewForHeaderInSection:(NSInteger)sectionIndex {
|
||||
NSAssert(theTableView == self.tableView, @"viewForHeaderInSection: invoked with inappropriate tableView: %@", theTableView);
|
||||
RKTableSection *section = [self sectionAtIndex:sectionIndex];
|
||||
return section.headerView;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)theTableView viewForFooterInSection:(NSInteger)sectionIndex {
|
||||
NSAssert(theTableView == self.tableView, @"viewForFooterInSection: invoked with inappropriate tableView: %@", theTableView);
|
||||
RKTableSection *section = [self sectionAtIndex:sectionIndex];
|
||||
return section.footerView;
|
||||
}
|
||||
|
||||
- (BOOL)isConsideredEmpty {
|
||||
NSUInteger nonRowItemsCount = [self.headerItems count] + [self.footerItems count];
|
||||
nonRowItemsCount += self.emptyItem ? 1 : 0;
|
||||
BOOL isEmpty = (self.rowCount - nonRowItemsCount) == 0;
|
||||
RKLogTrace(@"Determined isConsideredEmpty = %@. self.rowCount = %d with %d nonRowItems in the table", isEmpty ? @"YES" : @"NO", self.rowCount, nonRowItemsCount);
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user