mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-06-19 01:56:29 +08:00
Reworked table controller state into a bit mask to coalesce state change 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
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,28 +124,28 @@ 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
|
||||
@@ -111,9 +153,9 @@ extern NSString* const RKTableControllerDidBecomeOffline;
|
||||
- (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,42 +186,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 +289,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 +314,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 +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
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 @@
|
||||
RKLogError(@"performFetch failed with error: %@", [error localizedDescription]);
|
||||
} else {
|
||||
RKLogTrace(@"performFetch completed successfully");
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RKTableControllerDidLoadObjectsNotification object:self];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,17 +229,15 @@
|
||||
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 @@
|
||||
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 @@
|
||||
|
||||
#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 @@
|
||||
} 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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -194,7 +203,9 @@
|
||||
}
|
||||
|
||||
[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 @@
|
||||
// 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
|
||||
|
||||
@@ -1386,4 +1386,21 @@
|
||||
assertThat(tableController.stateOverlayImageView.image, is(notNilValue()));
|
||||
}
|
||||
|
||||
- (void)testPostANotificationWhenObjectsAreLoaded {
|
||||
[self bootstrapNakedObjectStoreAndCache];
|
||||
UITableView* tableView = [UITableView new];
|
||||
RKFetchedResultsTableControllerSpecViewController* viewController = [RKFetchedResultsTableControllerSpecViewController new];
|
||||
RKFetchedResultsTableController* tableController =
|
||||
[[RKFetchedResultsTableController alloc] initWithTableView:tableView
|
||||
viewController:viewController];
|
||||
tableController.resourcePath = @"/JSON/NakedEvents.json";
|
||||
[tableController setObjectMappingForClass:[RKEvent class]];
|
||||
|
||||
id observerMock = [OCMockObject observerMock];
|
||||
[[NSNotificationCenter defaultCenter] addMockObserver:observerMock name:RKTableControllerDidLoadObjectsNotification object:tableController];
|
||||
[[observerMock expect] notificationWithName:RKTableControllerDidLoadObjectsNotification object:tableController];
|
||||
[tableController loadTable];
|
||||
[observerMock verify];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface RKTableControllerTest : SenTestCase
|
||||
@interface RKTableControllerTest : RKTestCase
|
||||
|
||||
@end
|
||||
|
||||
@@ -151,6 +151,12 @@
|
||||
assertThat(tableController.tableView, is(equalTo(tableView)));
|
||||
}
|
||||
|
||||
- (void)testInitializesToUnloadedState {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(NO)));
|
||||
}
|
||||
|
||||
- (void)testAlwaysHaveAtLeastOneSection {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
assertThat(viewController.tableView, is(notNilValue()));
|
||||
@@ -671,8 +677,10 @@
|
||||
- (void)testAllowYouToTriggerAnEmptyLoad {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
RKLogIntegerAsBinary(tableController.state);
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(NO)));
|
||||
[tableController loadEmpty];
|
||||
RKLogIntegerAsBinary(tableController.state);
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
}
|
||||
@@ -726,6 +734,71 @@
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
}
|
||||
|
||||
- (void)testErrorIsClearedAfterSubsequentLoad {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
id mockObjectLoader = [OCMockObject niceMockForClass:[RKObjectLoader class]];
|
||||
NSError* error = [NSError errorWithDomain:@"Test" code:0 userInfo:nil];
|
||||
[tableController objectLoader:mockObjectLoader didFailWithError:error];
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isError], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
|
||||
[tableController objectLoader:mockObjectLoader didLoadObjects:[NSArray array]];
|
||||
assertThatBool([tableController isError], is(equalToBool(NO)));
|
||||
assertThat(tableController.error, is(nilValue()));
|
||||
}
|
||||
|
||||
- (void)testDisplayOfErrorImageTakesPresendenceOverEmpty {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
UIImage *imageForEmpty = [RKTestFixture imageWithContentsOfFixture:@"blake.png"];
|
||||
UIImage *imageForError = [imageForEmpty copy];
|
||||
tableController.imageForEmpty = imageForEmpty;
|
||||
tableController.imageForError = imageForError;
|
||||
|
||||
id mockObjectLoader = [OCMockObject niceMockForClass:[RKObjectLoader class]];
|
||||
NSError* error = [NSError errorWithDomain:@"Test" code:0 userInfo:nil];
|
||||
[tableController objectLoader:mockObjectLoader didFailWithError:error];
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isError], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
|
||||
UIImage *overlayImage = [tableController overlayImage];
|
||||
assertThat(overlayImage, isNot(nilValue()));
|
||||
assertThat(overlayImage, is(equalTo(imageForError)));
|
||||
}
|
||||
|
||||
- (void)testBitwiseLoadingTransition {
|
||||
RKTableControllerState oldState = RKTableControllerStateNotYetLoaded;
|
||||
RKTableControllerState newState = RKTableControllerStateLoading;
|
||||
|
||||
BOOL loadingTransitioned = ((oldState ^ newState) & RKTableControllerStateLoading);
|
||||
assertThatBool(loadingTransitioned, is(equalToBool(YES)));
|
||||
|
||||
oldState = RKTableControllerStateOffline | RKTableControllerStateEmpty;
|
||||
newState = RKTableControllerStateOffline | RKTableControllerStateEmpty | RKTableControllerStateLoading;
|
||||
loadingTransitioned = ((oldState ^ newState) & RKTableControllerStateLoading);
|
||||
assertThatBool(loadingTransitioned, is(equalToBool(YES)));
|
||||
|
||||
oldState = RKTableControllerStateNormal;
|
||||
newState = RKTableControllerStateLoading;
|
||||
loadingTransitioned = ((oldState ^ newState) & RKTableControllerStateLoading);
|
||||
assertThatBool(loadingTransitioned, is(equalToBool(YES)));
|
||||
|
||||
oldState = RKTableControllerStateOffline | RKTableControllerStateEmpty | RKTableControllerStateLoading;
|
||||
newState = RKTableControllerStateOffline | RKTableControllerStateLoading;
|
||||
loadingTransitioned = ((oldState ^ newState) & RKTableControllerStateLoading);
|
||||
assertThatBool(loadingTransitioned, is(equalToBool(NO)));
|
||||
|
||||
oldState = RKTableControllerStateNotYetLoaded;
|
||||
newState = RKTableControllerStateOffline;
|
||||
loadingTransitioned = ((oldState ^ newState) & RKTableControllerStateLoading);
|
||||
assertThatBool(loadingTransitioned, is(equalToBool(NO)));
|
||||
}
|
||||
|
||||
- (void)testSetTheModelToAnEmptyStateIfTheObjectLoaderReturnsAnEmptyCollection {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
@@ -1164,6 +1237,32 @@
|
||||
[observerMock verify];
|
||||
}
|
||||
|
||||
- (void)testPostANotificationWhenObjectsAreLoaded {
|
||||
RKObjectManager* objectManager = [RKTestFactory objectManager];
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
tableController.objectManager = objectManager;
|
||||
[tableController mapObjectsWithClass:[RKTestUser class] toTableCellsWithMapping:[RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping* mapping) {
|
||||
mapping.cellClass = [RKTestUserTableViewCell class];
|
||||
[mapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
|
||||
[mapping mapKeyPath:@"nickName" toAttribute:@"detailTextLabel.text"];
|
||||
}]];
|
||||
|
||||
id observerMock = [OCMockObject observerMock];
|
||||
[[NSNotificationCenter defaultCenter] addMockObserver:observerMock name:RKTableControllerDidLoadObjectsNotification object:tableController];
|
||||
[[observerMock expect] notificationWithName:RKTableControllerDidLoadObjectsNotification object:tableController];
|
||||
RKTestTableControllerDelegate* delegate = [RKTestTableControllerDelegate new];
|
||||
tableController.delegate = delegate;
|
||||
|
||||
[tableController loadTableFromResourcePath:@"/JSON/users.json" usingBlock:^(RKObjectLoader* objectLoader) {
|
||||
objectLoader.objectMapping = [RKObjectMapping mappingForClass:[RKTestUser class] usingBlock:^(RKObjectMapping* mapping) {
|
||||
[mapping mapAttributes:@"name", nil];
|
||||
}];
|
||||
}];
|
||||
[delegate waitForLoad];
|
||||
[observerMock verify];
|
||||
}
|
||||
|
||||
- (void)testPostANotificationWhenAnErrorOccurs {
|
||||
RKObjectManager* objectManager = [RKTestFactory objectManager];
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
@@ -1216,6 +1315,110 @@
|
||||
[observerMock verify];
|
||||
}
|
||||
|
||||
#pragma mark - State Transitions
|
||||
|
||||
- (void)testInitializesToNotYetLoadedState {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
assertThatBool(tableController.state == RKTableControllerStateNotYetLoaded, is(equalToBool(YES)));
|
||||
}
|
||||
|
||||
- (void)testInitialLoadSetsStateToLoading {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
id mockLoader = [OCMockObject mockForClass:[RKObjectLoader class]];
|
||||
[tableController requestDidStartLoad:mockLoader];
|
||||
assertThatBool([tableController isLoading], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(NO)));
|
||||
}
|
||||
|
||||
- (void)testSuccessfulLoadSetsStateToNormal {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
id mockLoader = [OCMockObject mockForClass:[RKObjectLoader class]];
|
||||
[tableController objectLoader:mockLoader didLoadObjects:[NSArray arrayWithObject:@"test"]];
|
||||
RKLogIntegerAsBinary(tableController.state);
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(YES)));
|
||||
assertThatInteger(tableController.state, is(equalToInteger(RKTableControllerStateNormal)));
|
||||
}
|
||||
|
||||
- (void)testErrorLoadsSetsStateToError {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
id mockLoader = [OCMockObject mockForClass:[RKObjectLoader class]];
|
||||
NSError *error = [NSError errorWithDomain:@"Test" code:1234 userInfo:nil];
|
||||
[tableController objectLoader:mockLoader didFailWithError:error];
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isError], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
}
|
||||
|
||||
- (void)testSecondaryLoadAfterErrorSetsStateToErrorAndLoading {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
id mockLoader = [OCMockObject mockForClass:[RKObjectLoader class]];
|
||||
NSError *error = [NSError errorWithDomain:@"Test" code:1234 userInfo:nil];
|
||||
[tableController objectLoader:mockLoader didFailWithError:error];
|
||||
assertThatBool([tableController isLoaded], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isError], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
[tableController requestDidStartLoad:mockLoader];
|
||||
assertThatBool([tableController isLoading], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isError], is(equalToBool(YES)));
|
||||
}
|
||||
|
||||
- (void)testEmptyLoadSetsStateToEmpty {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
[tableController loadEmpty];
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
}
|
||||
|
||||
- (void)testSecondaryLoadAfterEmptySetsStateToEmptyAndLoading {
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
[tableController loadEmpty];
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
id mockLoader = [OCMockObject mockForClass:[RKObjectLoader class]];
|
||||
[tableController requestDidStartLoad:mockLoader];
|
||||
assertThatBool([tableController isLoading], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
}
|
||||
|
||||
- (void)testTransitionToOfflineAfterLoadSetsStateToOfflineAndLoaded {
|
||||
RKObjectManager* objectManager = [RKTestFactory objectManager];
|
||||
id mockManager = [OCMockObject partialMockForObject:objectManager];
|
||||
BOOL isOnline = YES;
|
||||
[[[mockManager stub] andReturnValue:OCMOCK_VALUE(isOnline)] isOnline];
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
tableController.objectManager = mockManager;
|
||||
[tableController loadEmpty];
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isLoading], is(equalToBool(NO)));
|
||||
assertThatBool([tableController isOffline], is(equalToBool(NO)));
|
||||
id mockLoader = [OCMockObject mockForClass:[RKObjectLoader class]];
|
||||
[tableController requestDidStartLoad:mockLoader];
|
||||
assertThatBool([tableController isLoading], is(equalToBool(YES)));
|
||||
assertThatBool([tableController isEmpty], is(equalToBool(YES)));
|
||||
isOnline = NO;
|
||||
id mockManager2 = [OCMockObject partialMockForObject:objectManager];
|
||||
[[[mockManager2 stub] andReturnValue:OCMOCK_VALUE(isOnline)] isOnline];
|
||||
tableController.objectManager = mockManager2;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RKObjectManagerDidBecomeOfflineNotification object:tableController.objectManager];
|
||||
assertThatBool(tableController.isOffline, is(equalToBool(YES)));
|
||||
}
|
||||
|
||||
#pragma mark - State Views
|
||||
|
||||
- (void)testPermitYouToOverlayAnImageOnTheTable {
|
||||
@@ -2019,6 +2222,7 @@
|
||||
[[[mockManager expect] andReturnValue:OCMOCK_VALUE(online)] isOnline];
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
[tableController loadEmpty]; // Load to change the isLoaded state
|
||||
UIImage* image = [RKTestFixture imageWithContentsOfFixture:@"blake.png"];
|
||||
tableController.imageForOffline = image;
|
||||
|
||||
@@ -2096,7 +2300,6 @@
|
||||
}
|
||||
|
||||
- (void)testCallTheDelegateBeforeHidingTheSwipeView {
|
||||
// RKLogConfigureByName("RestKit/UI", RKLogLevelTrace);
|
||||
RKTableControllerTestTableViewController* viewController = [RKTableControllerTestTableViewController new];
|
||||
RKTableController* tableController = [RKTableController tableControllerForTableViewController:viewController];
|
||||
tableController.cellSwipeViewsEnabled = YES;
|
||||
|
||||
Reference in New Issue
Block a user