Fixed issues with incorrect retrieval of indexPath for header, footer, and empty items on the fetched results table controller. fixes #798

This commit is contained in:
Blake Watters
2012-06-06 18:18:44 -04:00
parent c297fbee68
commit 1c504022a6
12 changed files with 444 additions and 42 deletions

View File

@@ -21,6 +21,7 @@
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#import "RKTableCellBlockTypes.h"
#import "RKTableViewCellMappings.h"
#import "RKTableItem.h"
#import "RKObjectManager.h"
@@ -258,6 +259,20 @@ typedef NSUInteger RKTableControllerState;
*/
- (BOOL)isError;
///-----------------------------------------------------------------------------
/// @name Block Callbacks
///-----------------------------------------------------------------------------
// TODO: Audit and expand the library of callbacks...
// TODO: Docs AND tests...
@property (nonatomic, copy) RKTableCellForObjectAtIndexPathBlock onSelectCellForObjectAtIndexPath;
@property (nonatomic, copy) RKTableCellForObjectAtIndexPathBlock onPrepareCellForObjectAtIndexPath; // TODO: May want to eliminate...
@property (nonatomic, copy) RKTableCellForObjectAtIndexPathBlock onWillDisplayCellForObjectAtIndexPath;
- (void)setOnSelectCellForObjectAtIndexPath:(RKTableCellForObjectAtIndexPathBlock)onSelectCellForObjectAtIndexPath;
- (void)setOnPrepareCellForObjectAtIndexPath:(RKTableCellForObjectAtIndexPathBlock)onPrepareCellForObjectAtIndexPath;
- (void)setOnWillDisplayCellForObjectAtIndexPath:(RKTableCellForObjectAtIndexPathBlock)onWillDisplayCellForObjectAtIndexPath;
///-----------------------------------------------------------------------------
/// @name Model State Views
///-----------------------------------------------------------------------------

View File

@@ -59,6 +59,5 @@ typedef UIView *(^RKFetchedResultsTableViewViewForHeaderInSectionBlock)(NSUInteg
- (void)setObjectMappingForClass:(Class)objectClass;
- (void)loadTable;
- (void)loadTableFromNetwork;
- (NSIndexPath *)indexPathForObject:(id)object;
@end

View File

@@ -125,9 +125,14 @@
}
}
- (NSUInteger)headerSectionIndex
{
return 0;
}
- (BOOL)isHeaderSection:(NSUInteger)section
{
return (section == 0);
return (section == [self headerSectionIndex]);
}
- (BOOL)isHeaderRow:(NSUInteger)row
@@ -142,9 +147,14 @@
return isHeaderRow;
}
- (NSUInteger)footerSectionIndex
{
return ([self sectionCount] - 1);
}
- (BOOL)isFooterSection:(NSUInteger)section
{
return (section == ([self sectionCount] - 1));
return (section == [self footerSectionIndex]);
}
- (BOOL)isFooterRow:(NSUInteger)row
@@ -352,13 +362,18 @@
- (UITableViewCell *)cellForObjectAtIndexPath:(NSIndexPath *)indexPath
{
NSAssert(indexPath, @"Cannot retrieve cell for nil indexPath");
id mappableObject = [self objectForRowAtIndexPath:indexPath];
NSAssert(mappableObject, @"Cannot build a tableView cell without an object");
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];
// Attempt to get existing cell
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (! cell) {
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
@@ -382,13 +397,38 @@
{
if ([object isKindOfClass:[NSManagedObject class]]) {
return [self indexPathForFetchedResultsIndexPath:[_fetchedResultsController indexPathForObject:object]];
} else if ([object isKindOfClass:[RKTableItem class]]) {
if ([object isEqual:self.emptyItem]) {
return ([self isEmpty]) ? [self emptyItemIndexPath] : nil;
} else if ([self.headerItems containsObject:object]) {
// Figure out the row number for the object
NSUInteger objectIndex = [self.headerItems indexOfObject:object];
NSUInteger row = ([self isEmpty] && self.emptyItem) ? (objectIndex + 1) : objectIndex;
return [NSIndexPath indexPathForRow:row inSection:[self headerSectionIndex]];
} else if ([self.footerItems containsObject:object]) {
NSUInteger footerSectionIndex = [self sectionCount] - 1;
id <NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:footerSectionIndex];
NSUInteger numberOfFetchedResults = sectionInfo.numberOfObjects;
NSUInteger objectIndex = [self.footerItems indexOfObject:object];
NSUInteger row = numberOfFetchedResults + objectIndex;
row += ([self isEmpty] && self.emptyItem) ? 1 : 0;
if ([self isHeaderSection:footerSectionIndex]) {
row += [self.headerItems count];
}
return [NSIndexPath indexPathForRow:row inSection:footerSectionIndex];
}
} else {
RKLogWarning(@"Asked for indexPath of unsupported object type '%@': %@", [object class], object);
}
return nil;
}
- (UITableViewCell *)cellForObject:(id)object
{
return [self cellForObjectAtIndexPath:[self indexPathForObject:object]];
NSIndexPath *indexPath = [self indexPathForObject:object];
NSAssert(indexPath, @"Failed to find indexPath for object: %@", object);
return [self cellForObjectAtIndexPath:indexPath];
}
#pragma mark - UITableViewDataSource methods
@@ -678,15 +718,6 @@
#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];

View File

@@ -0,0 +1,15 @@
//
// RKTableCellBlockTypes.h
// RestKit
//
// Created by Blake Watters on 6/6/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
typedef NSIndexPath *(^RKTableTargetIndexPathForMoveBlock)(UITableViewCell *cell, id object, NSIndexPath *sourceIndexPath, NSIndexPath *destIndexPath);
typedef UITableViewCellEditingStyle(^RKTableCellEditingStyleForObjectAtIndexPathBlock)(UITableViewCell *cell, id object, NSIndexPath *indexPath);
typedef NSString *(^RKTableStringForObjectAtIndexPathBlock)(UITableViewCell *cell, id object, NSIndexPath *indexPath);
typedef CGFloat(^RKTableHeightOfCellForObjectAtIndexPathBlock)(id object, NSIndexPath *indexPath);
typedef void(^RKTableVoidBlock)();
typedef void(^RKTableCellBlock)(UITableViewCell *cell);
typedef void(^RKTableCellForObjectAtIndexPathBlock)(UITableViewCell *cell, id object, NSIndexPath *indexPath);

View File

@@ -431,6 +431,7 @@
- (UITableViewCell *)cellForObjectAtIndexPath:(NSIndexPath *)indexPath
{
NSAssert(indexPath, @"Cannot retrieve cell for nil indexPath");
RKTableSection *section = [self sectionAtIndex:indexPath.section];
id mappableObject = [section objectAtIndex:indexPath.row];
RKTableViewCellMapping *cellMapping = [self.cellMappings cellMappingForObject:mappableObject];

View File

@@ -20,17 +20,7 @@
#import <UIKit/UIKit.h>
#import "RKObjectMapping.h"
/** @name Cell Mapping Block Callbacks **/
typedef void(^RKTableViewCellForObjectAtIndexPathBlock)(UITableViewCell *cell, id object, NSIndexPath *indexPath);
typedef CGFloat(^RKTableViewHeightOfCellForObjectAtIndexPathBlock)(id object, NSIndexPath *indexPath);
typedef void(^RKTableViewAccessoryButtonTappedForObjectAtIndexPathBlock)(UITableViewCell *cell, id object, NSIndexPath *indexPath);
typedef NSString *(^RKTableViewTitleForDeleteButtonForObjectAtIndexPathBlock)(UITableViewCell *cell, id object, NSIndexPath *indexPath);
typedef UITableViewCellEditingStyle(^RKTableViewEditingStyleForObjectAtIndexPathBlock)(UITableViewCell *cell, id object, NSIndexPath *indexPath);
typedef NSIndexPath *(^RKTableViewTargetIndexPathForMoveBlock)(UITableViewCell *cell, id object, NSIndexPath *sourceIndexPath, NSIndexPath *destIndexPath);
typedef void(^RKTableViewAnonymousBlock)();
typedef void(^RKTableViewCellBlock)(UITableViewCell *cell);
#import "RKTableCellBlockTypes.h"
/**
Defines a RestKit object mapping suitable for mapping generic
@@ -46,10 +36,7 @@ typedef void(^RKTableViewCellBlock)(UITableViewCell *cell);
@see RKTableController
*/
@interface RKTableViewCellMapping : RKObjectMapping {
@protected
NSMutableArray *_prepareCellBlocks;
}
@interface RKTableViewCellMapping : RKObjectMapping
/**
The UITableViewCell subclass that this mapping will target. This
@@ -141,7 +128,7 @@ typedef void(^RKTableViewCellBlock)(UITableViewCell *cell);
is invoked with a reference to both the UITableViewCell that was touched and the
object the cell is representing.
*/
@property (nonatomic, copy) RKTableViewCellForObjectAtIndexPathBlock onSelectCellForObjectAtIndexPath;
@property (nonatomic, copy) RKTableCellForObjectAtIndexPathBlock onSelectCellForObjectAtIndexPath;
/**
Invoked when the user has touched a cell configured with this mapping. The block is invoked
@@ -150,7 +137,7 @@ typedef void(^RKTableViewCellBlock)(UITableViewCell *cell);
@see onSelectCellForObjectAtIndexPath
*/
@property (nonatomic, copy) RKTableViewAnonymousBlock onSelectCell;
@property (nonatomic, copy) RKTableVoidBlock onSelectCell;
/**
A block to invoke when a table view cell created with this mapping is going to appear in the table.
@@ -159,37 +146,38 @@ typedef void(^RKTableViewCellBlock)(UITableViewCell *cell);
This is a good moment to perform any customization to the cell before it becomes visible in the table view.
*/
@property (nonatomic, copy) RKTableViewCellForObjectAtIndexPathBlock onCellWillAppearForObjectAtIndexPath;
@property (nonatomic, copy) RKTableCellForObjectAtIndexPathBlock onCellWillAppearForObjectAtIndexPath;
/**
A block to invoke when the table view is measuring the height of the UITableViewCell.
The block will be invoked with the UITableViewCell, an id reference to the mapped object being
represented in the cell, and the NSIndexPath for the row position the cell will be appearing at.
*/
@property (nonatomic, copy) RKTableViewHeightOfCellForObjectAtIndexPathBlock heightOfCellForObjectAtIndexPath;
@property (nonatomic, copy) RKTableHeightOfCellForObjectAtIndexPathBlock heightOfCellForObjectAtIndexPath;
/**
A block to invoke when the accessory button for a given cell is tapped by the user.
The block will be invoked with the UITableViewCell, an id reference to the mapped object being
represented in the cell, and the NSIndexPath for the row position the cell will be appearing at.
*/
@property (nonatomic, copy) RKTableViewAccessoryButtonTappedForObjectAtIndexPathBlock onTapAccessoryButtonForObjectAtIndexPath;
@property (nonatomic, copy) RKTableCellForObjectAtIndexPathBlock onTapAccessoryButtonForObjectAtIndexPath;
/**
A block to invoke when the table view is determining the title for the delete confirmation button.
The block will be invoked with the UITableViewCell, an id reference to the mapped object being
represented in the cell, and the NSIndexPath for the row position the cell will be appearing at.
*/
@property (nonatomic, copy) RKTableViewTitleForDeleteButtonForObjectAtIndexPathBlock titleForDeleteButtonForObjectAtIndexPath;
@property (nonatomic, copy) RKTableStringForObjectAtIndexPathBlock titleForDeleteButtonForObjectAtIndexPath;
/**
A block to invoke when the table view is determining the editing style for a given row.
The block will be invoked with the UITableViewCell, an id reference to the mapped object being
represented in the cell, and the NSIndexPath for the row position the cell will be appearing at.
*/
@property (nonatomic, copy) RKTableViewEditingStyleForObjectAtIndexPathBlock editingStyleForObjectAtIndexPath;
@property (nonatomic, copy) RKTableCellEditingStyleForObjectAtIndexPathBlock editingStyleForObjectAtIndexPath;
@property (nonatomic, copy) RKTableViewTargetIndexPathForMoveBlock targetIndexPathForMove;
// TODO: Docs...
@property (nonatomic, copy) RKTableTargetIndexPathForMoveBlock targetIndexPathForMove;
/**
Returns a new auto-released mapping targeting UITableViewCell

View File

@@ -82,6 +82,10 @@ typedef void(^RKControlBlockActionBlock)(id sender);
@end
@interface RKTableViewCellMapping ()
@property (nonatomic, retain) NSMutableArray *prepareCellBlocks;
@end
@implementation RKTableViewCellMapping
@synthesize reuseIdentifier = _reuseIdentifier;
@@ -98,7 +102,8 @@ typedef void(^RKControlBlockActionBlock)(id sender);
@synthesize targetIndexPathForMove = _targetIndexPathForMove;
@synthesize rowHeight = _rowHeight;
@synthesize deselectsRowOnSelection = _deselectsRowOnSelection;
@synthesize managesCellAttributes;
@synthesize managesCellAttributes = _managesCellAttributes;
@synthesize prepareCellBlocks = _prepareCellBlocks;
+ (id)cellMapping
{
@@ -204,9 +209,12 @@ typedef void(^RKControlBlockActionBlock)(id sender);
NSAssert([tableView isKindOfClass:[UITableView class]], @"Expected to be invoked with a tableView as the data. Got %@", tableView);
RKLogTrace(@"About to dequeue reusable cell using self.reuseIdentifier=%@", self.reuseIdentifier);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.reuseIdentifier];
if (! cell) {
if (cell) {
RKLogTrace(@"Dequeued existing cell object for reuse identifier '%@': %@", self.reuseIdentifier, cell);
} else {
cell = [[[self.objectClass alloc] initWithStyle:self.style
reuseIdentifier:self.reuseIdentifier] autorelease];
RKLogTrace(@"Failed to dequeue existing cell object for reuse identifier '%@', instantiated new cell: %@", self.reuseIdentifier, cell);
}
if (self.managesCellAttributes) {

View File

@@ -673,6 +673,8 @@
25B6EA0414CF943E00B1E881 /* RKMutableBlockDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 25B6E9FC14CF943E00B1E881 /* RKMutableBlockDictionary.m */; };
25B6EA0614CF946400B1E881 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25B6EA0514CF946300B1E881 /* QuartzCore.framework */; };
25B6EA0814CF947E00B1E881 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25B6EA0714CF947D00B1E881 /* CoreGraphics.framework */; };
25C93DF2157FB6370089259B /* RKTableCellBlockTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 25C93DF1157FB6370089259B /* RKTableCellBlockTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
25C93DF3157FB6370089259B /* RKTableCellBlockTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 25C93DF1157FB6370089259B /* RKTableCellBlockTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
25C954A715542A47005C9E08 /* RKTestConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 25C954A415542A47005C9E08 /* RKTestConstants.m */; };
25C954A815542A47005C9E08 /* RKTestConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 25C954A415542A47005C9E08 /* RKTestConstants.m */; };
25CA7A8F14EC570200888FF8 /* RKObjectMappingDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 25CA7A8E14EC570100888FF8 /* RKObjectMappingDefinition.m */; };
@@ -1194,6 +1196,7 @@
25B6E9FC14CF943E00B1E881 /* RKMutableBlockDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKMutableBlockDictionary.m; sourceTree = "<group>"; };
25B6EA0514CF946300B1E881 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
25B6EA0714CF947D00B1E881 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
25C93DF1157FB6370089259B /* RKTableCellBlockTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKTableCellBlockTypes.h; sourceTree = "<group>"; };
25C954A415542A47005C9E08 /* RKTestConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKTestConstants.m; path = Testing/RKTestConstants.m; sourceTree = "<group>"; };
25CA7A8E14EC570100888FF8 /* RKObjectMappingDefinition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKObjectMappingDefinition.m; sourceTree = "<group>"; };
25CAAA9315254E7800CAE5D7 /* ArrayOfHumans.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ArrayOfHumans.json; sourceTree = "<group>"; };
@@ -2049,6 +2052,7 @@
25EC1A2B14F6FDAC00C3CF3F /* RKObjectManager+RKTableController.m */,
25EC1B3714F84B5C00C3CF3F /* UIImage+RKAdditions.h */,
25EC1B3814F84B5C00C3CF3F /* UIImage+RKAdditions.m */,
25C93DF1157FB6370089259B /* RKTableCellBlockTypes.h */,
);
path = UI;
sourceTree = "<group>";
@@ -2310,6 +2314,7 @@
258113C815781848009835EB /* RKObjectMappingProvider+CoreData.h in Headers */,
25545959155F0527007D7625 /* RKBenchmark.h in Headers */,
25E4DAB4156DA97F00A5C84B /* RKTableControllerTestDelegate.h in Headers */,
25C93DF2157FB6370089259B /* RKTableCellBlockTypes.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2436,6 +2441,7 @@
252028FD1577AE0B00076FB4 /* RKRouter.h in Headers */,
252029041577AE1800076FB4 /* RKRoute.h in Headers */,
258113C915781848009835EB /* RKObjectMappingProvider+CoreData.h in Headers */,
25C93DF3157FB6370089259B /* RKTableCellBlockTypes.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -68,6 +68,7 @@
2552087E14E5B0B500EFD81F /* RKTestEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 2552087D14E5B0B500EFD81F /* RKTestEnvironment.m */; };
25AE61DC15ADECAE00B319C8 /* OCClassMockRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 25AE61D915ADECAE00B319C8 /* OCClassMockRecorder.m */; };
25AE61DD15ADECAE00B319C8 /* OCMockClassObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 25AE61DB15ADECAE00B319C8 /* OCMockClassObject.m */; };
25C93E03157FE7C40089259B /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 25C93E02157FE7C40089259B /* Storyboard.storyboard */; };
25FABEA914E3698100E609E7 /* blake.png in Resources */ = {isa = PBXBuildFile; fileRef = 25FABE8114E3698100E609E7 /* blake.png */; };
25FABEAA14E3698100E609E7 /* ArrayOfNestedDictionaries.json in Resources */ = {isa = PBXBuildFile; fileRef = 25FABE8314E3698100E609E7 /* ArrayOfNestedDictionaries.json */; };
25FABEAB14E3698100E609E7 /* ArrayOfResults.json in Resources */ = {isa = PBXBuildFile; fileRef = 25FABE8414E3698100E609E7 /* ArrayOfResults.json */; };
@@ -272,6 +273,7 @@
25AE61D915ADECAE00B319C8 /* OCClassMockRecorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCClassMockRecorder.m; sourceTree = "<group>"; };
25AE61DA15ADECAE00B319C8 /* OCMockClassObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMockClassObject.h; sourceTree = "<group>"; };
25AE61DB15ADECAE00B319C8 /* OCMockClassObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMockClassObject.m; sourceTree = "<group>"; };
25C93E02157FE7C40089259B /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = "<group>"; };
25FABE8114E3698100E609E7 /* blake.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = blake.png; sourceTree = "<group>"; };
25FABE8314E3698100E609E7 /* ArrayOfNestedDictionaries.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ArrayOfNestedDictionaries.json; sourceTree = "<group>"; };
25FABE8414E3698100E609E7 /* ArrayOfResults.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ArrayOfResults.json; sourceTree = "<group>"; };
@@ -392,6 +394,7 @@
253B487514E3415800B0483F /* RKATAppDelegate.m */,
255207EB14E5A50800EFD81F /* RKATTests.xcdatamodeld */,
253B486C14E3415800B0483F /* Supporting Files */,
25C93E02157FE7C40089259B /* Storyboard.storyboard */,
);
name = App;
path = RKApplicationTests/App;
@@ -787,6 +790,7 @@
buildActionMask = 2147483647;
files = (
253B487014E3415800B0483F /* InfoPlist.strings in Resources */,
25C93E03157FE7C40089259B /* Storyboard.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11E53" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="1yr-CK-aHs">
<dependencies>
<deployment defaultVersion="1296" identifier="iOS"/>
<development defaultVersion="4200" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/>
</dependencies>
<scenes>
<!--Table View Controller-->
<scene sceneID="b8o-v0-PyV">
<objects>
<placeholder placeholderIdentifier="IBFirstResponder" id="bOO-KL-fJE" userLabel="First Responder" sceneMemberID="firstResponder"/>
<tableViewController id="1yr-CK-aHs" sceneMemberID="viewController">
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="TLU-pZ-Qvj">
<rect key="frame" x="0.0" y="20" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="HeaderCell" textLabel="KhK-Gz-eSx" style="IBUITableViewCellStyleDefault" id="qmM-ji-v3M">
<rect key="frame" x="0.0" y="22" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="KhK-Gz-eSx">
<rect key="frame" x="10" y="0.0" width="300" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<color key="highlightedColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="ObjectCell" textLabel="TKX-Yg-pGU" detailTextLabel="xxa-4c-3ki" style="IBUITableViewCellStyleValue1" id="Re2-lX-ZFL">
<rect key="frame" x="0.0" y="66" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="TKX-Yg-pGU">
<rect key="frame" x="10" y="11" width="35" height="21"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<color key="highlightedColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</label>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xxa-4c-3ki">
<rect key="frame" x="266" y="11" width="44" height="21"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.2196078431372549" green="0.32941176470588235" blue="0.52941176470588236" alpha="1" colorSpace="calibratedRGB"/>
<color key="highlightedColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="FooterCell" textLabel="N6n-tS-fmm" style="IBUITableViewCellStyleDefault" id="kJX-kC-GHc">
<rect key="frame" x="0.0" y="110" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="N6n-tS-fmm">
<rect key="frame" x="10" y="0.0" width="300" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<color key="highlightedColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="1yr-CK-aHs" id="d2i-yI-C8u"/>
<outlet property="delegate" destination="1yr-CK-aHs" id="Igd-89-n3h"/>
</connections>
</tableView>
</tableViewController>
</objects>
<point key="canvasLocation" x="76" y="21"/>
</scene>
</scenes>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination"/>
</simulatedMetricsContainer>
</document>

View File

@@ -83,6 +83,10 @@
[objectManager.mappingProvider setObjectMapping:humanMapping forResourcePathPattern:@"/JSON/humans/all\\.json" withFetchRequestBlock:^NSFetchRequest *(NSString *resourcePath) {
return [RKHuman requestAllSortedBy:@"name" ascending:YES];
}];
[objectManager.mappingProvider setObjectMapping:humanMapping forResourcePathPattern:@"/JSON/humans/empty\\.json" withFetchRequestBlock:^NSFetchRequest *(NSString *resourcePath) {
return [RKHuman requestAllSortedBy:@"name" ascending:YES];
}];
}
- (void)bootstrapNakedObjectStoreAndCache
@@ -523,9 +527,9 @@
{
[self bootstrapStoreAndCache];
[self stubObjectManagerToOnline];
[[RKObjectManager sharedManager].router routeClass:[RKHuman class]
toResourcePath:@"/humans/:railsID"
forMethod:RKRequestMethodDELETE];
[[RKObjectManager sharedManager].router addRouteWithClass:[RKHuman class]
resourcePathPattern:@"/humans/:railsID"
method:RKRequestMethodDELETE];
RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
tableController.resourcePath = @"/JSON/humans/all.json";
@@ -1528,4 +1532,238 @@
[mockDelegate verify];
}
- (void)testRetrievalOfExistingCellsByObject
{
[self bootstrapStoreAndCache];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
RKFetchedResultsTableController *tableController;
tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
[cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
[tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
tableController.resourcePath = @"/JSON/humans/all.json";
RKTableItem *tableItem = [RKTableItem tableItemWithText:@"Test"];
tableItem.cellMapping.reuseIdentifier = @"HeaderCell";
[tableItem.cellMapping addDefaultMappings];
[tableController addHeaderRowForItem:tableItem];
[[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];
[RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidLoadObjectsNotification usingBlock:^{
[tableController loadTable];
}];
assertThatInt([tableController numberOfRowsInSection:0], is(equalToInteger(3)));
NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cellFromTableView = [tableViewController.tableView cellForRowAtIndexPath:headerIndexPath];
UITableViewCell *cellForObject = [tableController cellForObject:tableItem];
assertThat(cellForObject, is(equalTo(cellFromTableView)));
UITableViewCell *cellFromTableController = [tableController cellForObjectAtIndexPath:headerIndexPath];
assertThat(cellForObject, is(equalTo(cellFromTableController)));
assertThat(cellFromTableView, is(equalTo(cellFromTableController)));
}
- (void)testRetrievalOfManagedObjectIndexPath
{
[self bootstrapStoreAndCache];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
RKFetchedResultsTableController *tableController;
tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
[cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
[tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
tableController.resourcePath = @"/JSON/humans/all.json";
RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
[headerItem.cellMapping addDefaultMappings];
[tableController addHeaderRowForItem:headerItem];
RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
footerItem.cellMapping.reuseIdentifier = @"FooterCell";
[footerItem.cellMapping addDefaultMappings];
[tableController addFooterRowForItem:footerItem];
[[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];
[RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
[tableController loadTable];
}];
// Let the table update
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
assertThatInt([tableController rowCount], is(equalToInteger(4)));
RKHuman *human = [[RKHuman findAllSortedBy:@"name" ascending:YES] objectAtIndex:0];
NSIndexPath *indexPath = [tableController indexPathForObject:human];
assertThatInteger(indexPath.section, is(equalToInteger(0)));
assertThatInteger(indexPath.row, is(equalToInteger(1)));
}
- (void)testRetrievalOfHeaderItemIndexPath
{
[self bootstrapStoreAndCache];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
RKFetchedResultsTableController *tableController;
tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
[cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
[tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
tableController.resourcePath = @"/JSON/humans/all.json";
RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
[headerItem.cellMapping addDefaultMappings];
[tableController addHeaderRowForItem:headerItem];
RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
footerItem.cellMapping.reuseIdentifier = @"FooterCell";
[footerItem.cellMapping addDefaultMappings];
[tableController addFooterRowForItem:footerItem];
[[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];
[RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
[tableController loadTable];
}];
// Let the table update
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
assertThatInt([tableController rowCount], is(equalToInteger(4)));
NSIndexPath *indexPath = [tableController indexPathForObject:headerItem];
assertThatInteger(indexPath.section, is(equalToInteger(0)));
assertThatInteger(indexPath.row, is(equalToInteger(0)));
}
- (void)testRetrievalOfFooterItemIndexPath
{
[self bootstrapStoreAndCache];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
RKFetchedResultsTableController *tableController;
tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
[cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
[tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
tableController.resourcePath = @"/JSON/humans/all.json";
RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
[headerItem.cellMapping addDefaultMappings];
[tableController addHeaderRowForItem:headerItem];
RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
footerItem.cellMapping.reuseIdentifier = @"FooterCell";
[footerItem.cellMapping addDefaultMappings];
[tableController addFooterRowForItem:footerItem];
[[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];
[RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
[tableController loadTable];
}];
// Let the table update
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
assertThatInt([tableController rowCount], is(equalToInteger(4)));
NSIndexPath *indexPath = [tableController indexPathForObject:footerItem];
assertThatInteger(indexPath.section, is(equalToInteger(0)));
assertThatInteger(indexPath.row, is(equalToInteger(3)));
}
- (void)testRetrievalOfEmptyItemReturnsNilIndexPathWhenNotEmpty
{
[self bootstrapStoreAndCache];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
RKFetchedResultsTableController *tableController;
tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
[cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
[tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
tableController.resourcePath = @"/JSON/humans/all.json";
RKTableItem *emptyItem = [RKTableItem tableItemWithText:@"Empty!"];
emptyItem.cellMapping.reuseIdentifier = @"HeaderCell";
[emptyItem.cellMapping addDefaultMappings];
tableController.emptyItem = emptyItem;
RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
[headerItem.cellMapping addDefaultMappings];
[tableController addHeaderRowForItem:headerItem];
RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
footerItem.cellMapping.reuseIdentifier = @"FooterCell";
[footerItem.cellMapping addDefaultMappings];
[tableController addFooterRowForItem:footerItem];
[[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];
[RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
[tableController loadTable];
}];
// Let the table update
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
assertThatInt([tableController rowCount], is(equalToInteger(4)));
NSIndexPath *indexPath = [tableController indexPathForObject:emptyItem];
assertThat(indexPath, is(nilValue()));
}
- (void)testRetrievalOfEmptyItemReturnsIndexPathWhenEmpty
{
[self bootstrapStoreAndCache];
[RKHuman truncateAll];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
RKFetchedResultsTableController *tableController;
tableController = [[RKFetchedResultsTableController tableControllerForTableViewController:tableViewController] retain];
RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
[cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
[tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
tableController.resourcePath = @"/JSON/humans/empty.json";
tableController.showsFooterRowsWhenEmpty = NO;
tableController.showsHeaderRowsWhenEmpty = NO;
RKTableItem *emptyItem = [RKTableItem tableItemWithText:@"Empty!"];
emptyItem.cellMapping.reuseIdentifier = @"HeaderCell";
[emptyItem.cellMapping addDefaultMappings];
tableController.emptyItem = emptyItem;
RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
[headerItem.cellMapping addDefaultMappings];
[tableController addHeaderRowForItem:headerItem];
RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
footerItem.cellMapping.reuseIdentifier = @"FooterCell";
[footerItem.cellMapping addDefaultMappings];
[tableController addFooterRowForItem:footerItem];
[[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];
[RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
[tableController loadTable];
}];
// Let the table update
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
assertThatInt([tableController rowCount], is(equalToInteger(1)));
NSIndexPath *indexPath = [tableController indexPathForObject:emptyItem];
assertThatInteger(indexPath.section, is(equalToInteger(0)));
assertThatInteger(indexPath.row, is(equalToInteger(0)));
}
@end

View File

@@ -0,0 +1 @@
[]