Browse files

Reworked table controller state into a bit mask to coalesce state cha…

…nge into a single observation. fixes #753

* Added RKLogIntegerAsBinary() helper for logging bit masks
* Implemented RKTableControllerDidLoadObjectsNotification for static and fetched results table controllers
* Cleaned up state definitions within table controller
* Documentation cleanups
  • Loading branch information...
1 parent 98c8780 commit 992bfb96cc382924e86aaa588f6c5c6fe5e2fdaf @blakewatters blakewatters committed May 23, 2012
View
6 Code/Support/RKLog.h
@@ -205,3 +205,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);
View
10 Code/Support/RKLog.m
@@ -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);
+}
View
341 Code/UI/RKAbstractTableController.h
@@ -21,99 +21,141 @@
#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 */
+///-----------------------------------------------------------------------------
+/// @name Constants
+///-----------------------------------------------------------------------------
-/** Posted when the table view model starts loading */
-extern NSString* const RKTableControllerDidStartLoadNotification;
+/**
+ Posted when the table controller starts loading.
+ */
+extern NSString * const RKTableControllerDidStartLoadNotification;
-/** Posted when the table view model finishes loading */
-extern NSString* const RKTableControllerDidFinishLoadNotification;
+/**
+ Posted when the table controller 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 controller 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 controller 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 controller 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 controller has transitioned from an offline to online state.
+ */
+extern NSString * const RKTableControllerDidBecomeOnline;
-/** Posted when the table view model has transitioned from online to offline */
-extern NSString* const RKTableControllerDidBecomeOffline;
+/**
+ Posted when the table controller has transitioned from an online to an offline state.
+ */
+extern NSString * const RKTableControllerDidBecomeOffline;
+
+@protocol RKAbstractTableControllerDelegate;
-@protocol RKTableControllerDelegate;
+/**
+ @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;
/**
- 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.
- */
-@interface RKAbstractTableController : NSObject <UITableViewDataSource, UITableViewDelegate> {
- @protected
- UIView *_tableOverlayView;
- UIImageView *_stateOverlayImageView;
- UIView *_pullToRefreshHeaderView;
- RKCache *_cache;
-}
-
-/////////////////////////////////////////////////////////////////////////
+ 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;
@property (nonatomic, assign) BOOL canEditRows;
@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;
-/////////////////////////////////////////////////////////////////////////
+///-----------------------------------------------------------------------------
/// @name Header and Footer Rows
-/////////////////////////////////////////////////////////////////////////
+///-----------------------------------------------------------------------------
- (void)addHeaderRowForItem:(RKTableItem *)tableItem;
- (void)addFooterRowForItem:(RKTableItem *)tableItem;
@@ -122,12 +164,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,50 +186,122 @@ 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
will be positioned directly in the center of the table view.
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
@@ -201,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
and prevent any interaction with the table.
@@ -213,54 +332,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,45 +382,50 @@ 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;
+- (void)tableController:(RKAbstractTableController *)tableController didLoadObjects:(NSArray *)objects inSection:(NSUInteger)sectionIndex; // NOT IMPLEMENTED
-/** 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;
+// TODO: Can these even be implemented???
+- (void)tableController:(RKAbstractTableController *)tableController didInsertSectionAtIndex:(NSUInteger)sectionIndex;
+- (void)tableController:(RKAbstractTableController *)tableController didRemoveSectionAtIndex:(NSUInteger)sectionIndex;
// Objects
- (void)tableController:(RKAbstractTableController *)tableController didInsertObject:(id)object atIndexPath:(NSIndexPath *)indexPath;
@@ -320,8 +437,8 @@ 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;
+- (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
View
579 Code/UI/RKAbstractTableController.m
@@ -26,6 +26,7 @@
#import "RKReachabilityObserver.h"
#import "UIView+FindFirstResponder.h"
#import "RKRefreshGestureRecognizer.h"
+#import "RKTableSection.h"
// Define logging component
#undef RKLogComponent
@@ -37,22 +38,21 @@
*/
#define BOUNCE_PIXELS 5.0
-NSString* const RKTableControllerDidStartLoadNotification = @"RKTableControllerDidStartLoadNotification";
-NSString* const RKTableControllerDidFinishLoadNotification = @"RKTableControllerDidFinishLoadNotification";
-NSString* const RKTableControllerDidLoadObjectsNotification = @"RKTableControllerDidLoadObjectsNotification";
-NSString* const RKTableControllerDidLoadEmptyNotification = @"RKTableControllerDidLoadEmptyNotification";
-NSString* const RKTableControllerDidLoadErrorNotification = @"RKTableControllerDidLoadErrorNotification";
-NSString* const RKTableControllerDidBecomeOnline = @"RKTableControllerDidBecomeOnline";
-NSString* const RKTableControllerDidBecomeOffline = @"RKTableControllerDidBecomeOffline";
+NSString * const RKTableControllerDidStartLoadNotification = @"RKTableControllerDidStartLoadNotification";
+NSString * const RKTableControllerDidFinishLoadNotification = @"RKTableControllerDidFinishLoadNotification";
+NSString * const RKTableControllerDidLoadObjectsNotification = @"RKTableControllerDidLoadObjectsNotification";
+NSString * const RKTableControllerDidLoadEmptyNotification = @"RKTableControllerDidLoadEmptyNotification";
+NSString * const RKTableControllerDidLoadErrorNotification = @"RKTableControllerDidLoadErrorNotification";
+NSString * const RKTableControllerDidBecomeOnline = @"RKTableControllerDidBecomeOnline";
+NSString * const RKTableControllerDidBecomeOffline = @"RKTableControllerDidBecomeOffline";
-static NSString* lastUpdatedDateDictionaryKey = @"lastUpdatedDateDictionaryKey";
+static NSString * lastUpdatedDateDictionaryKey = @"lastUpdatedDateDictionaryKey";
@implementation RKAbstractTableController
@synthesize delegate = _delegate;
@synthesize viewController = _viewController;
@synthesize tableView = _tableView;
-@synthesize sections = _sections;
@synthesize defaultRowAnimation = _defaultRowAnimation;
@synthesize objectLoader = _objectLoader;
@@ -61,10 +61,7 @@ @implementation RKAbstractTableController
@synthesize autoRefreshFromNetwork = _autoRefreshFromNetwork;
@synthesize autoRefreshRate = _autoRefreshRate;
-@synthesize empty = _empty;
-@synthesize loading = _loading;
-@synthesize loaded = _loaded;
-@synthesize online = _online;
+@synthesize state = _state;
@synthesize error = _error;
@synthesize imageForEmpty = _imageForEmpty;
@@ -95,20 +92,21 @@ @implementation RKAbstractTableController
@synthesize tableOverlayView = _tableOverlayView;
@synthesize stateOverlayImageView = _stateOverlayImageView;
@synthesize cache = _cache;
+@synthesize pullToRefreshHeaderView = _pullToRefreshHeaderView;
#pragma mark - Instantiation
-+ (id)tableControllerWithTableView:(UITableView*)tableView
- forViewController:(UIViewController*)viewController {
++ (id)tableControllerWithTableView:(UITableView *)tableView
+ forViewController:(UIViewController *)viewController {
return [[[self alloc] initWithTableView:tableView viewController:viewController] autorelease];
}
-+ (id)tableControllerForTableViewController:(UITableViewController*)tableViewController {
++ (id)tableControllerForTableViewController:(UITableViewController *)tableViewController {
return [self tableControllerWithTableView:tableViewController.tableView
forViewController:tableViewController];
}
-- (id)initWithTableView:(UITableView*)theTableView viewController:(UIViewController*)theViewController {
+- (id)initWithTableView:(UITableView *)theTableView viewController:(UIViewController *)theViewController {
NSAssert(theTableView, @"Cannot initialize a table view model with a nil tableView");
NSAssert(theViewController, @"Cannot initialize a table view model with a nil viewController");
self = [self init];
@@ -134,7 +132,7 @@ - (id)init {
userInfo:nil];
}
- _sections = [NSMutableArray new];
+ self.state = RKTableControllerStateNotYetLoaded;
self.objectManager = [RKObjectManager sharedManager];
_cellMappings = [RKTableViewCellMappings new];
@@ -149,25 +147,13 @@ - (id)init {
// Setup key-value observing
[self addObserver:self
- forKeyPath:@"loading"
- options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
- context:nil];
- [self addObserver:self
- forKeyPath:@"loaded"
- options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
- context:nil];
- [self addObserver:self
- forKeyPath:@"empty"
+ forKeyPath:@"state"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
[self addObserver:self
forKeyPath:@"error"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
- [self addObserver:self
- forKeyPath:@"online"
- options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
- context:nil];
}
return self;
}
@@ -187,20 +173,16 @@ - (void)dealloc {
_tableOverlayView = nil;
// Remove observers
- [self removeObserver:self forKeyPath:@"loading"];
- [self removeObserver:self forKeyPath:@"loaded"];
- [self removeObserver:self forKeyPath:@"empty"];
+ [self removeObserver:self forKeyPath:@"state"];
[self removeObserver:self forKeyPath:@"error"];
- [self removeObserver:self forKeyPath:@"online"];
[[NSNotificationCenter defaultCenter] removeObserver:self];
// TODO: WTF? Get UI crashes when enabled...
// [_objectManager.requestQueue abortRequestsWithDelegate:self];
_objectLoader.delegate = nil;
[_objectLoader release];
_objectLoader = nil;
-
- [_sections release];
+
[_cellMappings release];
[_headerItems release];
[_footerItems release];
@@ -254,7 +236,11 @@ - (void)setObjectManager:(RKObjectManager *)objectManager {
// Initialize online/offline state (if it is known)
if (objectManager.networkStatus != RKObjectManagerNetworkStatusUnknown) {
- self.online = objectManager.isOnline;
+ if (objectManager.isOnline) {
+ self.state &= ~RKTableControllerStateOffline;
+ } else {
+ self.state |= RKTableControllerStateOffline;
+ }
}
}
}
@@ -282,7 +268,7 @@ - (void)setAutoRefreshFromNetwork:(BOOL)autoRefreshFromNetwork {
if (_autoRefreshFromNetwork != autoRefreshFromNetwork) {
_autoRefreshFromNetwork = autoRefreshFromNetwork;
if (_autoRefreshFromNetwork) {
- NSString* cachePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
+ NSString *cachePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
stringByAppendingPathComponent:@"RKAbstractTableControllerCache"];
_cache = [[RKCache alloc] initWithPath:cachePath subDirectories:nil];
} else {
@@ -295,130 +281,110 @@ - (void)setAutoRefreshFromNetwork:(BOOL)autoRefreshFromNetwork {
}
}
-- (void)objectManagerConnectivityDidChange:(NSNotification *)notification {
- RKLogTrace(@"%@ received network status change notification: %@", self, [notification name]);
- self.online = self.objectManager.isOnline;
-}
-
-#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];
+- (void)setLoading:(BOOL)loading {
+ if (loading) {
+ self.state |= RKTableControllerStateLoading;
+ } else {
+ self.state &= ~RKTableControllerStateLoading;
+ }
}
-- (NSUInteger)indexForSection:(RKTableSection *)section {
- NSAssert(section, @"Cannot return index for a nil section");
- return [_sections indexOfObject:section];
+// NOTE: The loaded flag is handled specially. When loaded becomes NO,
+// we clear all other flags. In practice this should not happen outside of init.
+- (void)setLoaded:(BOOL)loaded {
+ if (loaded) {
+ self.state &= ~RKTableControllerStateNotYetLoaded;
+ } else {
+ self.state = RKTableControllerStateNotYetLoaded;
+ }
}
-- (RKTableSection *)sectionWithHeaderTitle:(NSString *)title {
- for (RKTableSection* section in _sections) {
- if ([section.headerTitle isEqualToString:title]) {
- return section;
- }
+- (void)setEmpty:(BOOL)empty {
+ if (empty) {
+ self.state |= RKTableControllerStateEmpty;
+ } else {
+ self.state &= ~RKTableControllerStateEmpty;
}
-
- return nil;
}
-- (NSUInteger)numberOfRowsInSectionAtIndex:(NSUInteger)index {
- return [self sectionAtIndex:index].rowCount;
+- (void)setOffline:(BOOL)offline {
+ if (offline) {
+ self.state |= RKTableControllerStateOffline;
+ } else {
+ self.state &= ~RKTableControllerStateOffline;
+ }
}
-- (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;
+- (void)setErrorState:(BOOL)error {
+ if (error) {
+ self.state |= RKTableControllerStateError;
+ } else {
+ self.state &= ~RKTableControllerStateError;
}
+}
- return cell;
+- (void)objectManagerConnectivityDidChange:(NSNotification *)notification {
+ RKLogTrace(@"%@ received network status change notification: %@", self, [notification name]);
+ [self setOffline:!self.objectManager.isOnline];
}
-#pragma mark - UITableViewDataSource methods
+#pragma mark - Abstract Methods
-- (NSInteger)numberOfSectionsInTableView:(UITableView*)theTableView {
- NSAssert(theTableView == self.tableView, @"numberOfSectionsInTableView: invoked with inappropriate tableView: %@", theTableView);
- RKLogTrace(@"%@ numberOfSectionsInTableView = %d", self, self.sectionCount);
- return self.sectionCount;
+- (BOOL)isConsideredEmpty {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
}
-- (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];
+- (NSUInteger)sectionCount {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
}
-- (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)rowCount {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
}
-- (NSString*)tableView:(UITableView*)theTableView titleForHeaderInSection:(NSInteger)section {
- NSAssert(theTableView == self.tableView, @"tableView:titleForHeaderInSection: invoked with inappropriate tableView: %@", theTableView);
- return [[_sections objectAtIndex:section] headerTitle];
+- (id)objectForRowAtIndexPath:(NSIndexPath *)indexPath {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
}
-- (NSString*)tableView:(UITableView*)theTableView titleForFooterInSection:(NSInteger)section {
- NSAssert(theTableView == self.tableView, @"tableView:titleForFooterInSection: invoked with inappropriate tableView: %@", theTableView);
- return [[_sections objectAtIndex:section] footerTitle];
+- (NSIndexPath *)indexPathForObject:(id)object {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
}
-- (BOOL)tableView:(UITableView*)theTableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
- NSAssert(theTableView == self.tableView, @"tableView:canEditRowAtIndexPath: invoked with inappropriate tableView: %@", theTableView);
- return _canEditRows;
+- (NSUInteger)numberOfRowsInSection:(NSUInteger)index {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
}
-- (BOOL)tableView:(UITableView*)theTableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
- NSAssert(theTableView == self.tableView, @"tableView:canMoveRowAtIndexPath: invoked with inappropriate tableView: %@", theTableView);
- return _canMoveRows;
+- (UITableViewCell *)cellForObjectAtIndexPath:(NSIndexPath *)indexPath {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
+ userInfo:nil];
}
#pragma mark - Cell Mappings
-- (void)mapObjectsWithClass:(Class)objectClass toTableCellsWithMapping:(RKTableViewCellMapping*)cellMapping {
+- (void)mapObjectsWithClass:(Class)objectClass toTableCellsWithMapping:(RKTableViewCellMapping *)cellMapping {
// TODO: Should we raise an exception/throw a warning if you are doing class mapping for a type
// that implements a cellMapping instance method? Maybe a class declaration overrides
[_cellMappings setCellMapping:cellMapping forClass:objectClass];
}
-- (void)mapObjectsWithClassName:(NSString *)objectClassName toTableCellsWithMapping:(RKTableViewCellMapping*)cellMapping {
+- (void)mapObjectsWithClassName:(NSString *)objectClassName toTableCellsWithMapping:(RKTableViewCellMapping *)cellMapping {
[self mapObjectsWithClass:NSClassFromString(objectClassName) toTableCellsWithMapping:cellMapping];
}
-- (id)objectForRowAtIndexPath:(NSIndexPath *)indexPath {
- NSAssert(indexPath, @"Cannot lookup object with a nil indexPath");
- RKTableSection* section = [self sectionAtIndex:indexPath.section];
- return [section objectAtIndex:indexPath.row];
-}
-
-- (RKTableViewCellMapping*)cellMappingForObjectAtIndexPath:(NSIndexPath *)indexPath {
+- (RKTableViewCellMapping *)cellMappingForObjectAtIndexPath:(NSIndexPath *)indexPath {
NSAssert(indexPath, @"Cannot lookup cell mapping for object with a nil indexPath");
id object = [self objectForRowAtIndexPath:indexPath];
return [self.cellMappings cellMappingForObject:object];
@@ -429,41 +395,24 @@ - (UITableViewCell *)cellForObject:(id)object {
return indexPath ? [self cellForObjectAtIndexPath:indexPath] : nil;
}
-- (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;
-}
-
#pragma mark - Header and Footer Rows
-- (void)addHeaderRowForItem:(RKTableItem*)tableItem {
+- (void)addHeaderRowForItem:(RKTableItem *)tableItem {
[_headerItems addObject:tableItem];
}
-- (void)addFooterRowForItem:(RKTableItem*)tableItem {
+- (void)addFooterRowForItem:(RKTableItem *)tableItem {
[_footerItems addObject:tableItem];
}
- (void)addHeaderRowWithMapping:(RKTableViewCellMapping *)cellMapping {
- RKTableItem* tableItem = [RKTableItem tableItem];
+ RKTableItem *tableItem = [RKTableItem tableItem];
tableItem.cellMapping = cellMapping;
[self addHeaderRowForItem:tableItem];
}
- (void)addFooterRowWithMapping:(RKTableViewCellMapping *)cellMapping {
- RKTableItem* tableItem = [RKTableItem tableItem];
+ RKTableItem *tableItem = [RKTableItem tableItem];
tableItem.cellMapping = cellMapping;
[self addFooterRowForItem:tableItem];
}
@@ -476,17 +425,32 @@ - (void)removeAllFooterRows {
[_footerItems removeAllObjects];
}
+#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;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ [NSException raise:@"Must be implemented in a subclass!" format:@"sectionCount must be implemented with a subclass"];
+ return 0;
+}
+
#pragma mark - UITableViewDelegate methods
-- (void)tableView:(UITableView*)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+- (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSAssert(theTableView == self.tableView, @"tableView:didSelectRowAtIndexPath: invoked with inappropriate tableView: %@", theTableView);
RKLogTrace(@"%@: Row at indexPath %@ selected for tableView %@", self, indexPath, theTableView);
id object = [self objectForRowAtIndexPath:indexPath];
// NOTE: Do NOT use cellForObjectAtIndexPath here. See https://gist.github.com/eafbb641d37bb7137759
- UITableViewCell* cell = [theTableView cellForRowAtIndexPath:indexPath];
- RKTableViewCellMapping* cellMapping = [_cellMappings cellMappingForObject:object];
+ UITableViewCell *cell = [theTableView cellForRowAtIndexPath:indexPath];
+ RKTableViewCellMapping *cellMapping = [_cellMappings cellMappingForObject:object];
// NOTE: Handle deselection first as the onSelectCell processing may result in the tableView
// being reloaded and our instances invalidated
@@ -508,7 +472,7 @@ - (void)tableView:(UITableView *)theTableView willDisplayCell:(UITableViewCell *
NSAssert(theTableView == self.tableView, @"tableView:didSelectRowAtIndexPath: invoked with inappropriate tableView: %@", theTableView);
cell.hidden = NO;
id mappableObject = [self objectForRowAtIndexPath:indexPath];
- RKTableViewCellMapping* cellMapping = [self.cellMappings cellMappingForObject:mappableObject];
+ RKTableViewCellMapping *cellMapping = [self.cellMappings cellMappingForObject:mappableObject];
if (cellMapping.onCellWillAppearForObjectAtIndexPath) {
cellMapping.onCellWillAppearForObjectAtIndexPath(cell, mappableObject, indexPath);
}
@@ -540,7 +504,7 @@ - (void)tableView:(UITableView *)theTableView willDisplayCell:(UITableViewCell *
- (CGFloat)tableView:(UITableView *)theTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.variableHeightRows) {
- RKTableViewCellMapping* cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
+ RKTableViewCellMapping *cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
if (cellMapping.heightOfCellForObjectAtIndexPath) {
id object = [self objectForRowAtIndexPath:indexPath];
@@ -557,55 +521,31 @@ - (CGFloat)tableView:(UITableView *)theTableView heightForRowAtIndexPath:(NSInde
return self.tableView.rowHeight;
}
-- (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;
-}
-
-- (void)tableView:(UITableView*)theTableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
- RKTableViewCellMapping* cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
+- (void)tableView:(UITableView *)theTableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
+ RKTableViewCellMapping *cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
if (cellMapping.onTapAccessoryButtonForObjectAtIndexPath) {
RKLogTrace(@"Found a block for tableView:accessoryButtonTappedForRowWithIndexPath: Executing...");
- UITableViewCell* cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];
+ UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];
id object = [self objectForRowAtIndexPath:indexPath];
cellMapping.onTapAccessoryButtonForObjectAtIndexPath(cell, object, indexPath);
}
}
-- (NSString*)tableView:(UITableView*)theTableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
- RKTableViewCellMapping* cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
+- (NSString *)tableView:(UITableView *)theTableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath {
+ RKTableViewCellMapping *cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
if (cellMapping.titleForDeleteButtonForObjectAtIndexPath) {
RKLogTrace(@"Found a block for tableView:titleForDeleteConfirmationButtonForRowAtIndexPath: Executing...");
- UITableViewCell* cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];
+ UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];
id object = [self objectForRowAtIndexPath:indexPath];
return cellMapping.titleForDeleteButtonForObjectAtIndexPath(cell, object, indexPath);
}
return NSLocalizedString(@"Delete", nil);
}
-- (UITableViewCellEditingStyle)tableView:(UITableView*)theTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
+- (UITableViewCellEditingStyle)tableView:(UITableView *)theTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if (_canEditRows) {
- RKTableViewCellMapping* cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
- UITableViewCell* cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];
+ RKTableViewCellMapping *cellMapping = [self cellMappingForObjectAtIndexPath:indexPath];
+ UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];
if (cellMapping.editingStyleForObjectAtIndexPath) {
RKLogTrace(@"Found a block for tableView:editingStyleForRowAtIndexPath: Executing...");
id object = [self objectForRowAtIndexPath:indexPath];
@@ -616,34 +556,34 @@ - (UITableViewCellEditingStyle)tableView:(UITableView*)theTableView editingStyle
return UITableViewCellEditingStyleNone;
}
-- (void)tableView:(UITableView*)theTableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
+- (void)tableView:(UITableView *)theTableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
if ([self.delegate respondsToSelector:@selector(tableController:didEndEditing:atIndexPath:)]) {
id object = [self objectForRowAtIndexPath:indexPath];
[self.delegate tableController:self didEndEditing:object atIndexPath:indexPath];
}
}
-- (void)tableView:(UITableView*)theTableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath {
+- (void)tableView:(UITableView *)theTableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath {
if ([self.delegate respondsToSelector:@selector(tableController:willBeginEditing:atIndexPath:)]) {
id object = [self objectForRowAtIndexPath:indexPath];
[self.delegate tableController:self willBeginEditing:object atIndexPath:indexPath];
}
}
-- (NSIndexPath *)tableView:(UITableView*)theTableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
+- (NSIndexPath *)tableView:(UITableView *)theTableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
if (_canMoveRows) {
- RKTableViewCellMapping* cellMapping = [self cellMappingForObjectAtIndexPath:sourceIndexPath];
+ RKTableViewCellMapping *cellMapping = [self cellMappingForObjectAtIndexPath:sourceIndexPath];
if (cellMapping.targetIndexPathForMove) {
RKLogTrace(@"Found a block for tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath: Executing...");
- UITableViewCell* cell = [self tableView:self.tableView cellForRowAtIndexPath:sourceIndexPath];
+ UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:sourceIndexPath];
id object = [self objectForRowAtIndexPath:sourceIndexPath];
return cellMapping.targetIndexPathForMove(cell, object, sourceIndexPath, proposedDestinationIndexPath);
}
}
return proposedDestinationIndexPath;
}
-- (NSIndexPath *)tableView:(UITableView*)theTableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+- (NSIndexPath *)tableView:(UITableView *)theTableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self removeSwipeView:YES];
return indexPath;
}
@@ -654,18 +594,18 @@ - (void)cancelLoad {
[self.objectLoader cancel];
}
-- (NSDate*)lastUpdatedDate {
+- (NSDate *)lastUpdatedDate {
if (! self.objectLoader) {
return nil;
}
if (_autoRefreshFromNetwork) {
NSAssert(_cache, @"Found a nil cache when trying to read our last loaded time");
- NSDictionary* lastUpdatedDates = [_cache dictionaryForCacheKey:lastUpdatedDateDictionaryKey];
+ NSDictionary *lastUpdatedDates = [_cache dictionaryForCacheKey:lastUpdatedDateDictionaryKey];
RKLogTrace(@"Last updated dates dictionary retrieved from tableController cache: %@", lastUpdatedDates);
if (lastUpdatedDates) {
- NSString* absoluteURLString = [self.objectLoader.URL absoluteString];
- NSNumber* lastUpdatedTimeIntervalSince1970 = (NSNumber*)[lastUpdatedDates objectForKey:absoluteURLString];
+ NSString *absoluteURLString = [self.objectLoader.URL absoluteString];
+ NSNumber *lastUpdatedTimeIntervalSince1970 = (NSNumber *)[lastUpdatedDates objectForKey:absoluteURLString];
if (absoluteURLString && lastUpdatedTimeIntervalSince1970) {
return [NSDate dateWithTimeIntervalSince1970:[lastUpdatedTimeIntervalSince1970 doubleValue]];
}
@@ -678,7 +618,7 @@ - (BOOL)isAutoRefreshNeeded {
BOOL isAutoRefreshNeeded = NO;
if (_autoRefreshFromNetwork) {
isAutoRefreshNeeded = YES;
- NSDate* lastUpdatedDate = [self lastUpdatedDate];
+ NSDate *lastUpdatedDate = [self lastUpdatedDate];
RKLogTrace(@"Last updated: %@", lastUpdatedDate);
if (lastUpdatedDate) {
RKLogTrace(@"-timeIntervalSinceNow=%f, autoRefreshRate=%f",
@@ -691,12 +631,12 @@ - (BOOL)isAutoRefreshNeeded {
#pragma mark - RKRequestDelegate & RKObjectLoaderDelegate methods
-- (void)requestDidStartLoad:(RKRequest*)request {
+- (void)requestDidStartLoad:(RKRequest *)request {
RKLogTrace(@"tableController %@ started loading.", self);
self.loading = YES;
}
-- (void)requestDidCancelLoad:(RKRequest*)request {
+- (void)requestDidCancelLoad:(RKRequest *)request {
RKLogTrace(@"tableController %@ cancelled loading.", self);
self.loading = NO;
@@ -705,7 +645,7 @@ - (void)requestDidCancelLoad:(RKRequest*)request {
}
}
-- (void)requestDidTimeout:(RKRequest*)request {
+- (void)requestDidTimeout:(RKRequest *)request {
RKLogTrace(@"tableController %@ timed out while loading.", self);
self.loading = NO;
}
@@ -716,13 +656,13 @@ - (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
// Updated the lastUpdatedDate dictionary using the URL of the request
if (self.autoRefreshFromNetwork) {
NSAssert(_cache, @"Found a nil cache when trying to save our last loaded time");
- NSMutableDictionary* lastUpdatedDates = [[_cache dictionaryForCacheKey:lastUpdatedDateDictionaryKey] mutableCopy];
+ NSMutableDictionary *lastUpdatedDates = [[_cache dictionaryForCacheKey:lastUpdatedDateDictionaryKey] mutableCopy];
if (lastUpdatedDates) {
[_cache invalidateEntry:lastUpdatedDateDictionaryKey];
} else {
lastUpdatedDates = [[NSMutableDictionary alloc] init];
}
- NSNumber* timeIntervalSince1970 = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]];
+ NSNumber *timeIntervalSince1970 = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]];
RKLogTrace(@"Setting timeIntervalSince1970=%@ for URL %@", timeIntervalSince1970, [request.URL absoluteString]);
[lastUpdatedDates setObject:timeIntervalSince1970
forKey:[request.URL absoluteString]];
@@ -747,22 +687,47 @@ - (void)objectLoaderDidFinishLoading:(RKObjectLoader *)objectLoader {
}
- (void)didFinishLoad {
- self.empty = [self isEmpty];
+ self.empty = [self isConsideredEmpty];
self.loading = [self.objectLoader isLoading]; // Mutate loading state after we have adjusted empty
self.loaded = YES;
- // Setup offline image state based on current online/offline state
- [self updateOfflineImageForOnlineState:[self isOnline]];
-
- [self resetOverlayView];
-
if (self.delegate && [_delegate respondsToSelector:@selector(tableControllerDidFinalizeLoad:)]) {
[_delegate performSelector:@selector(tableControllerDidFinalizeLoad:) withObject:self];
}
}
#pragma mark - Table Overlay Views
+- (UIImage *)imageForState:(RKTableControllerState)state {
+ switch (state) {
+ case RKTableControllerStateNormal:
+ case RKTableControllerStateLoading:
+ case RKTableControllerStateNotYetLoaded:
+ break;
+
+ case RKTableControllerStateEmpty:
+ return self.imageForEmpty;
+ break;
+
+ case RKTableControllerStateError:
+ return self.imageForError;
+ break;
+
+ case RKTableControllerStateOffline:
+ return self.imageForOffline;
+ break;
+
+ default:
+ break;
+ }
+
+ return nil;
+}
+
+- (UIImage *)overlayImage {
+ return _stateOverlayImageView.image;
+}
+
// Adds an overlay view above the table
- (void)addToOverlayView:(UIView *)view modally:(BOOL)modally {
if (! _tableOverlayView) {
@@ -835,31 +800,31 @@ - (void)removeImageOverlay {
[self resetOverlayView];
}
-- (void)setImageForEmpty:(UIImage*)imageForEmpty {
+- (void)setImageForEmpty:(UIImage *)imageForEmpty {
[imageForEmpty retain];
BOOL imageRemoved = [self removeImageFromOverlay:_imageForEmpty];
[_imageForEmpty release];
_imageForEmpty = imageForEmpty;
if (imageRemoved) [self showImageInOverlay:_imageForEmpty];
}
-- (void)setImageForError:(UIImage*)imageForError {
+- (void)setImageForError:(UIImage *)imageForError {
[imageForError retain];
BOOL imageRemoved = [self removeImageFromOverlay:_imageForError];
[_imageForError release];
_imageForError = imageForError;
if (imageRemoved) [self showImageInOverlay:_imageForError];
}
-- (void)setImageForOffline:(UIImage*)imageForOffline {
+- (void)setImageForOffline:(UIImage *)imageForOffline {
[imageForOffline retain];
BOOL imageRemoved = [self removeImageFromOverlay:_imageForOffline];
[_imageForOffline release];
_imageForOffline = imageForOffline;
if (imageRemoved) [self showImageInOverlay:_imageForOffline];
}
-- (void)setLoadingView:(UIView*)loadingView {
+- (void)setLoadingView:(UIView *)loadingView {
[loadingView retain];
BOOL viewRemoved = (_loadingView.superview != nil);
[_loadingView removeFromSuperview];
@@ -869,41 +834,34 @@ - (void)setLoadingView:(UIView*)loadingView {
if (viewRemoved) [self addToOverlayView:_loadingView modally:NO];
}
-#pragma mark - KVO & Model States
+#pragma mark - KVO & Table States
- (BOOL)isLoading {
- return self.loading;
+ return (self.state & RKTableControllerStateLoading) != 0;
}
- (BOOL)isLoaded {
- return self.loaded;
+ return (self.state & RKTableControllerStateNotYetLoaded) == 0;
+// return self.state != RKTableControllerStateNotYetLoaded;
}
+- (BOOL)isOffline {
+ return (self.state & RKTableControllerStateOffline) != 0;
+}
- (BOOL)isOnline {
- return self.online;
+ return ![self isOffline];
}
- (BOOL)isError {
- return _error != nil;
+ return (self.state & RKTableControllerStateError) != 0;
}
- (BOOL)isEmpty {
- NSUInteger nonRowItemsCount = [_headerItems count] + [_footerItems count];
- nonRowItemsCount += _emptyItem ? 1 : 0;
- BOOL isEmpty = (self.rowCount - nonRowItemsCount) == 0;
- RKLogTrace(@"Determined isEmpty = %@. self.rowCount = %d with %d nonRowItems in the table", isEmpty ? @"YES" : @"NO", self.rowCount, nonRowItemsCount);
- return isEmpty;
+ return (self.state & RKTableControllerStateEmpty) != 0;
}
-- (void)isLoadingDidChangeTo:(BOOL)isLoading {
- if (isLoading) {
- // Remove any current state to allow drawing of the loading view
- [self removeImageOverlay];
-
- // Clear the error state
- self.error = nil;
- self.empty = NO;
-
+- (void)isLoadingDidChange {
+ if ([self isLoading]) {
if ([self.delegate respondsToSelector:@selector(tableControllerDidStartLoad:)]) {
[self.delegate tableControllerDidStartLoad:self];
}
@@ -929,65 +887,40 @@ - (void)isLoadingDidChangeTo:(BOOL)isLoading {
}
// We don't want any image overlays applied until loading is finished
- _stateOverlayImageView.hidden = isLoading;
+ _stateOverlayImageView.hidden = [self isLoading];
}
-- (void)isLoadedDidChangeTo:(BOOL)isLoaded {
- if (isLoaded) {
+- (void)isLoadedDidChange {
+ if ([self isLoaded]) {
RKLogDebug(@"%@: is now loaded.", self);
} else {
RKLogDebug(@"%@: is NOT loaded.", self);
}
}
-- (void)errorDidChangeTo:(BOOL)isError {
- if (isError) {
+- (void)isErrorDidChange {
+ if ([self isError]) {
if ([self.delegate respondsToSelector:@selector(tableController:didFailLoadWithError:)]) {
[self.delegate tableController:self didFailLoadWithError:self.error];
}
- NSDictionary* userInfo = [NSDictionary dictionaryWithObject:self.error forKey:RKErrorNotificationErrorKey];
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:self.error forKey:RKErrorNotificationErrorKey];
[[NSNotificationCenter defaultCenter] postNotificationName:RKTableControllerDidLoadErrorNotification object:self userInfo:userInfo];
-
- if (self.imageForError) {
- [self showImageInOverlay:self.imageForError];
- }
- } else {
- [self removeImageFromOverlay:self.imageForError];
}
}
-- (void)isEmptyDidChangeTo:(BOOL)isEmpty {
- if (isEmpty) {
- // TODO: maybe this should be didLoadEmpty?
+- (void)isEmptyDidChange {
+ if ([self isEmpty]) {
if ([self.delegate respondsToSelector:@selector(tableControllerDidBecomeEmpty:)]) {
[self.delegate tableControllerDidBecomeEmpty:self];
}
[[NSNotificationCenter defaultCenter] postNotificationName:RKTableControllerDidLoadEmptyNotification object:self];
-
- if (self.imageForEmpty) {
- [self showImageInOverlay:self.imageForEmpty];
- }
- } else {
- if (self.imageForEmpty) {
- [self removeImageFromOverlay:self.imageForEmpty];
- }
}
}
-- (void)updateOfflineImageForOnlineState:(BOOL)isOnline {
- if (isOnline) {
- [self removeImageFromOverlay:self.imageForOffline];
- } else {
- if (self.imageForOffline) {
- [self showImageInOverlay:self.imageForOffline];
- }
- }
-}
-
-- (void)isOnlineDidChangeTo:(BOOL)isOnline {
- if (isOnline) {
+- (void)isOnlineDidChange {
+ if ([self isOnline]) {
// We just transitioned to online
if ([self.delegate respondsToSelector:@selector(tableControllerDidBecomeOnline:)]) {
[self.delegate tableControllerDidBecomeOnline:self];
@@ -1001,37 +934,59 @@ - (void)isOnlineDidChangeTo:(BOOL)isOnline {
}
[[NSNotificationCenter defaultCenter] postNotificationName:RKTableControllerDidBecomeOffline object:self];
+ }
+}
+
+- (void)updateTableViewForStateChange:(NSDictionary *)change {
+ RKTableControllerState oldState = [[change valueForKey:NSKeyValueChangeOldKey] integerValue];
+ RKTableControllerState newState = [[change valueForKey:NSKeyValueChangeNewKey] integerValue];
+
+ // Determine state transitions
+ BOOL loadedChanged = ((oldState ^ newState) & RKTableControllerStateNotYetLoaded);
+ BOOL emptyChanged = ((oldState ^ newState) & RKTableControllerStateEmpty);
+ BOOL offlineChanged = ((oldState ^ newState) & RKTableControllerStateOffline);
+ BOOL loadingChanged = ((oldState ^ newState) & RKTableControllerStateLoading);
+ BOOL errorChanged = ((oldState ^ newState) & RKTableControllerStateError);
+
+ if (loadedChanged) [self isLoadedDidChange];
+ if (emptyChanged) [self isEmptyDidChange];
+ if (offlineChanged) [self isOnlineDidChange];
+ if (errorChanged) [self isErrorDidChange];
+ if (loadingChanged) [self isLoadingDidChange];
+
+ // Clear the image from the overlay
+ _stateOverlayImageView.image = nil;
+
+ // Determine the appropriate overlay image to display (if any)
+ if (self.state == RKTableControllerStateNormal) {
+ [self removeImageOverlay];
+ } else {
+ if ([self isLoading]) {
+ // During a load we don't adjust the overlay
+ return;
+ }
+
+ // Though the table can be in more than one state, we only
+ // want to display a single overlay image.
+ if ([self isOffline] && self.imageForOffline) {
+ [self showImageInOverlay:self.imageForOffline];
+ } else if ([self isError] && self.imageForError) {
+ [self showImageInOverlay:self.imageForError];
+ } else if ([self isEmpty] && self.imageForEmpty) {
+ [self showImageInOverlay:self.imageForEmpty];
+ }
}
-
- [self updateOfflineImageForOnlineState:isOnline];
+
+ // Remove the overlay if no longer in use
+ [self resetOverlayView];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
- BOOL newValue = NO;
- BOOL oldValue = NO;
- if ([keyPath isEqualToString:@"loading"]) {
- newValue = [[change valueForKey:NSKeyValueChangeNewKey] boolValue];
- oldValue = [[change valueForKey:NSKeyValueChangeOldKey] boolValue];
- if (newValue != oldValue) [self isLoadingDidChangeTo:newValue];
- } else if ([keyPath isEqualToString:@"loaded"]) {
- newValue = [[change valueForKey:NSKeyValueChangeNewKey] boolValue];
- oldValue = [[change valueForKey:NSKeyValueChangeOldKey] boolValue];
- if (newValue != oldValue) [self isLoadedDidChangeTo:newValue];
+ if ([keyPath isEqualToString:@"state"]) {
+ [self updateTableViewForStateChange:change];
} else if ([keyPath isEqualToString:@"error"]) {
- newValue = (! [[change valueForKey:NSKeyValueChangeNewKey] isEqual:[NSNull null]]);
- oldValue = (! [[change valueForKey:NSKeyValueChangeOldKey] isEqual:[NSNull null]]);
- if (newValue != oldValue) [self errorDidChangeTo:newValue];
- } else if ([keyPath isEqualToString:@"empty"]) {
- newValue = [[change valueForKey:NSKeyValueChangeNewKey] boolValue];
- oldValue = [[change valueForKey:NSKeyValueChangeOldKey] boolValue];
- if (newValue != oldValue) [self isEmptyDidChangeTo:newValue];
- } else if ([keyPath isEqualToString:@"online"]) {
- newValue = [[change valueForKey:NSKeyValueChangeNewKey] boolValue];
- oldValue = [[change valueForKey:NSKeyValueChangeOldKey] boolValue];
- if (newValue != oldValue) [self isOnlineDidChangeTo:newValue];
+ [self setErrorState:(self.error != nil)];
}
-
- RKLogTrace(@"Key-value observation triggered for keyPath '%@'. Old value = %d, new value = %d", keyPath, oldValue, newValue);
}
#pragma mark - Pull to Refresh
@@ -1074,39 +1029,39 @@ - (void)pullToRefreshStateChanged:(UIGestureRecognizer *)gesture {
}
- (void)resetPullToRefreshRecognizer {
- RKRefreshGestureRecognizer* recognizer = [self pullToRefreshGestureRecognizer];
+ RKRefreshGestureRecognizer *recognizer = [self pullToRefreshGestureRecognizer];
if (recognizer)
[recognizer setRefreshState:RKRefreshIdle];
}
-- (BOOL)pullToRefreshDataSourceIsLoading:(UIGestureRecognizer*)gesture {
+- (BOOL)pullToRefreshDataSourceIsLoading:(UIGestureRecognizer *)gesture {
// If we have already been loaded and we are loading again, a refresh is taking place...
return [self isLoaded] && [self isLoading] && [self isOnline];
}
-- (NSDate*)pullToRefreshDataSourceLastUpdated:(UIGestureRecognizer*)gesture {
- NSDate* dataSourceLastUpdated = [self lastUpdatedDate];
+- (NSDate *)pullToRefreshDataSourceLastUpdated:(UIGestureRecognizer *)gesture {
+ NSDate *dataSourceLastUpdated = [self lastUpdatedDate];
return dataSourceLastUpdated ? dataSourceLastUpdated : [NSDate date];
}
#pragma mark - Cell Swipe Menu Methods
- (void)setupSwipeGestureRecognizers {
// Setup a right swipe gesture recognizer
- UISwipeGestureRecognizer* rightSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRight:)];
+ UISwipeGestureRecognizer *rightSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRight:)];
rightSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self.tableView addGestureRecognizer:rightSwipeGestureRecognizer];
[rightSwipeGestureRecognizer release];
// Setup a left swipe gesture recognizer
- UISwipeGestureRecognizer* leftSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeLeft:)];
+ UISwipeGestureRecognizer *leftSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeLeft:)];
leftSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[self.tableView addGestureRecognizer:leftSwipeGestureRecognizer];
[leftSwipeGestureRecognizer release];
}
- (void)removeSwipeGestureRecognizers {
- for (UIGestureRecognizer* recognizer in self.tableView.gestureRecognizers) {
+ for (UIGestureRecognizer *recognizer in self.tableView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UISwipeGestureRecognizer class]]) {
[self.tableView removeGestureRecognizer:recognizer];
}
@@ -1129,11 +1084,11 @@ - (void)setCellSwipeViewsEnabled:(BOOL)cellSwipeViewsEnabled {
_cellSwipeViewsEnabled = cellSwipeViewsEnabled;
}
-- (void)swipe:(UISwipeGestureRecognizer*)recognizer direction:(UISwipeGestureRecognizerDirection)direction {
+- (void)swipe:(UISwipeGestureRecognizer *)recognizer direction:(UISwipeGestureRecognizerDirection)direction {
if (_cellSwipeViewsEnabled && recognizer && recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint location = [recognizer locationInView:self.tableView];
- NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:location];
- UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
+ NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
+ UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
id object = [self objectForRowAtIndexPath:indexPath];
if (cell.frame.origin.x != 0) {
@@ -1149,11 +1104,11 @@ - (void)swipe:(UISwipeGestureRecognizer*)recognizer direction:(UISwipeGestureRec
}
}
-- (void)swipeLeft:(UISwipeGestureRecognizer*)recognizer {
+- (void)swipeLeft:(UISwipeGestureRecognizer *)recognizer {
[self swipe:recognizer direction:UISwipeGestureRecognizerDirectionLeft];
}
-- (void)swipeRight:(UISwipeGestureRecognizer*)recognizer {
+- (void)swipeRight:(UISwipeGestureRecognizer *)recognizer {
[self swipe:recognizer direction:UISwipeGestureRecognizerDirectionRight];
}
@@ -1192,7 +1147,7 @@ - (void)addSwipeViewTo:(UITableViewCell *)cell withObject:(id)object direction:(
}
}
-- (void)animationDidStopAddingSwipeView:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
+- (void)animationDidStopAddingSwipeView:(NSString *)animationID finished:(NSNumber *)finished context:(void*)context {
_animatingCellSwipe = NO;
}
@@ -1230,7 +1185,7 @@ - (void)removeSwipeView:(BOOL)animated {
}
}
-- (void)animationDidStopOne:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
+- (void)animationDidStopOne:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
if (_swipeDirection == UISwipeGestureRecognizerDirectionRight) {
@@ -1244,7 +1199,7 @@ - (void)animationDidStopOne:(NSString*)animationID finished:(NSNumber*)finished
[UIView commitAnimations];
}
-- (void)animationDidStopTwo:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
+- (void)animationDidStopTwo:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[UIView commitAnimations];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
@@ -1259,7 +1214,7 @@ - (void)animationDidStopTwo:(NSString*)animationID finished:(NSNumber*)finished
[UIView commitAnimations];
}
-- (void)animationDidStopThree:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
+- (void)animationDidStopThree:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
_animatingCellSwipe = NO;
[_swipeCell release];
_swipeCell = nil;
@@ -1268,7 +1223,7 @@ - (void)animationDidStopThree:(NSString*)animationID finished:(NSNumber*)finishe
#pragma mark UIScrollViewDelegate methods
-- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
+- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[self removeSwipeView:YES];
}
@@ -1279,23 +1234,23 @@ - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
#pragma mark - Keyboard Notification methods
-- (void)resizeTableViewForKeyboard:(NSNotification*)notification {
+- (void)resizeTableViewForKeyboard:(NSNotification *)notification {
NSAssert(_autoResizesForKeyboard, @"Errantly receiving keyboard notifications while autoResizesForKeyboard=NO");
- NSDictionary* userInfo = [notification userInfo];
+ NSDictionary *userInfo = [notification userInfo];
CGRect keyboardEndFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat heightForViewShift = keyboardEndFrame.size.height;
RKLogTrace(@"keyboardEndFrame.size.height=%f, heightForViewShift=%f",
keyboardEndFrame.size.height, heightForViewShift);
CGFloat bottomBarOffset = 0.0;
- UINavigationController* navigationController = self.viewController.navigationController;
+ UINavigationController *navigationController = self.viewController.navigationController;
if (navigationController && navigationController.toolbar && !navigationController.toolbarHidden) {
bottomBarOffset += navigationController.toolbar.frame.size.height;
RKLogTrace(@"Found a visible toolbar. Reducing size of heightForViewShift by=%f", bottomBarOffset);
}
- UITabBarController* tabBarController = self.viewController.tabBarController;
+ UITabBarController *tabBarController = self.viewController.tabBarController;
if (tabBarController && tabBarController.tabBar && !self.viewController.hidesBottomBarWhenPushed) {
bottomBarOffset += tabBarController.tabBar.frame.size.height;
RKLogTrace(@"Found a visible tabBar. Reducing size of heightForViewShift by=%f", bottomBarOffset);
@@ -1314,7 +1269,7 @@ - (void)resizeTableViewForKeyboard:(NSNotification*)notification {
nonKeyboardRect.origin.x, nonKeyboardRect.origin.y,
nonKeyboardRect.size.width, nonKeyboardRect.size.height);
- UIView* firstResponder = [self.tableView findFirstResponder];
+ UIView *firstResponder = [self.tableView findFirstResponder];
if (firstResponder) {
CGRect firstResponderFrame = firstResponder.frame;
RKLogTrace(@"Found firstResponder=%@ at (%f, %f, %f, %f)", firstResponder,
@@ -1345,7 +1300,7 @@ - (void)resizeTableViewForKeyboard:(NSNotification*)notification {
}
}
-- (void)loadTableWithObjectLoader:(RKObjectLoader*)theObjectLoader {
+- (void)loadTableWithObjectLoader:(RKObjectLoader *)theObjectLoader {
NSAssert(theObjectLoader, @"Cannot perform a network load without an object loader");
if (! [self.objectLoader isEqual:theObjectLoader]) {
if (self.objectLoader) {
View
42 Code/UI/RKAbstractTableController_Internals.h
@@ -18,25 +18,25 @@
// 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;
/**
Must be invoked when the table controller has finished loading.
@@ -45,7 +45,6 @@
and cleaning up the table overlay view.
*/
- (void)didFinishLoad;
-- (void)updateOfflineImageForOnlineState:(BOOL)isOnline;
#pragma mark - Table View Overlay
@@ -61,5 +60,22 @@
- (void)pullToRefreshStateChanged:(UIGestureRecognizer *)gesture;
- (void)resetPullToRefreshRecognizer;
+#pragma mark - State Mutators
+
+- (void)setLoading:(BOOL)loading;
+- (void)setLoaded:(BOOL)loaded;
+- (void)setEmpty:(BOOL)empty;
+- (void)setOffline:(BOOL)offline;
+- (void)setErrorState:(BOOL)error;
+
+/**
+ 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
View
2 Code/UI/RKFetchedResultsTableController.h
@@ -33,7 +33,7 @@ typedef UIView *(^RKFetchedResultsTableViewViewForHeaderInSectionBlock)(NSUInteg
BOOL _isEmptyBeforeAnimation;
}
-@property (nonatomic, readonly) NSFetchedResultsController *fetchedResultsController;
+@property (nonatomic, retain, readonly) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, copy) NSString *resourcePath;
@property (nonatomic, retain) NSFetchRequest *fetchRequest;
@property (nonatomic, assign) CGFloat heightForHeaderInSection;
View
34 Code/UI/RKFetchedResultsTableController.m
@@ -31,6 +31,8 @@
#define RKLogComponent lcl_cRestKitUI
@interface RKFetchedResultsTableController ()
+@property (nonatomic, retain, readwrite) NSFetchedResultsController *fetchedResultsController;
+
- (void)performFetch;
- (void)updateSortedArray;
@end
@@ -86,6 +88,7 @@ - (void)performFetch {
RKLogError(@"performFetch failed with error: %@", [error localizedDescription]);
} else {
RKLogTrace(@"performFetch completed successfully");
+ [[NSNotificationCenter defaultCenter] postNotificationName:RKTableControllerDidLoadObjectsNotification object:self];
}
}
@@ -226,17 +229,15 @@ - (void)loadTable {
if (_sortDescriptors) {
[fetchRequest setSortDescriptors:_sortDescriptors];
}
-
- [_fetchedResultsController setDelegate:nil];
- [_fetchedResultsController release];
- _fetchedResultsController = nil;
-
- _fetchedResultsController =
+
+ self.fetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[NSManagedObjectContext contextForCurrentThread]
sectionNameKeyPath:_sectionNameKeyPath
cacheName:_cacheName];
+ [_fetchedResultsController release];
_fetchedResultsController.delegate = self;
+
[self performFetch];
[self updateSortedArray];
@@ -470,7 +471,7 @@ - (id)objectForRowAtIndexPath:(NSIndexPath *)indexPath {
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 +500,7 @@ - (void)loadTableFromNetwork {
#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);
@@ -599,8 +600,23 @@ - (void)controllerDidChangeContent:(NSFetchedResultsController*)controller {
} else {
[self.tableView endUpdates];
}
-
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:RKTableControllerDidLoadObjectsNotification object:self];
[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
View
47 Code/UI/RKTableController.h
@@ -29,11 +29,22 @@
#import "RKObjectMapping.h"
#import "RKObjectLoader.h"
+@protocol RKTableControllerDelegate <RKAbstractTableControllerDelegate>
+
+@optional
+
+- (void)tableController:(RKAbstractTableController *)tableController didInsertSection:(RKTableSection *)section atIndex:(NSUInteger)sectionIndex;
+- (void)tableController:(RKAbstractTableController *)tableController didRemoveSection:(RKTableSection *)section atIndex:(NSUInteger)sectionIndex;
+
+@end
+
@interface RKTableController : RKAbstractTableController
-/////////////////////////////////////////////////////////////////////////
+@property (nonatomic, assign) id<RKTableControllerDelegate> delegate;
+
+///-----------------------------------------------------------------------------
/// @name Static Tables
-/////////////////////////////////////////////////////////////////////////
+///-----------------------------------------------------------------------------
- (void)loadObjects:(NSArray *)objects;
- (void)loadObjects:(NSArray *)objects inSection:(NSUInteger)sectionIndex;
@@ -78,12 +89,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 +113,39 @@
*/
- (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.
+ */
+// MOVED
+- (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.
+ */
+// MOVED
+- (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?
View
166 Code/UI/RKTableController.m
@@ -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 @@ - (void)dealloc {
[self removeObserver:self forKeyPath:@"sections"];
[_form release];
[_sectionNameKeyPath release];
-
+ [_sections release];
+
[super dealloc];
}
@@ -194,7 +203,9 @@ - (void)loadObjects:(NSArray *)objects inSection:(NSUInteger)sectionIndex {
}
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:self.defaultRowAnimation];
-
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:RKTableControllerDidLoadObjectsNotification object:self];
+
// The load is finalized via network callbacks for
// dynamic table controllers
if (nil == self.objectLoader) {
@@ -376,4 +387,153 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
// 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 {