├── .gitignore ├── AWPagedArray.h ├── AWPagedArray.m ├── AWPagedArray.podspec ├── Demo ├── FluentResourcePaging-example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── FluentResourcePaging-example │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── ClassicPagingViewController.h │ ├── ClassicPagingViewController.m │ ├── DataLoadingOperation.h │ ├── DataLoadingOperation.m │ ├── DataProvider.h │ ├── DataProvider.m │ ├── DataReceiver.h │ ├── FluentPagingCollectionViewController.h │ ├── FluentPagingCollectionViewController.m │ ├── FluentPagingTableViewController.h │ ├── FluentPagingTableViewController.m │ ├── FluentResourcePaging-example-Info.plist │ ├── FluentResourcePaging-example-Prefix.pch │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json │ ├── LabelCollectionViewCell.h │ ├── LabelCollectionViewCell.m │ ├── MainStoryboard.storyboard │ ├── en.lproj │ └── InfoPlist.strings │ └── main.m ├── GitHub ├── PreloadFluentCollectionView.gif └── PreloadFluentPaging.gif ├── LICENSE ├── README.md └── Tests ├── AWPagedArray tests ├── AWPagedArray tests-Info.plist ├── AWPagedArray tests-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── AWPagedArrayTests.m └── AWPagedArrayTests.xcodeproj ├── project.pbxproj └── project.xcworkspace └── contents.xcworkspacedata /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods 23 | Pods -------------------------------------------------------------------------------- /AWPagedArray.h: -------------------------------------------------------------------------------- 1 | // 2 | // AWPagedArray.h 3 | // 4 | // Copyright (c) 2014 Alek Åström 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import 26 | 27 | @protocol AWPagedArrayDelegate; 28 | 29 | /** 30 | * This class acts as a proxy for NSArray, when data is loaded in batches, called "pages". 31 | * @discussion The recommendation is to use this class in conjunction with a data controller class which populates the paged array with pages of data, while casting the paged array back as an NSArray to its owner. 32 | * 33 | * This class is inspired by NSFetchRequest's batching mechanism which returns a custom NSArray subclass. 34 | * @see NSFetchRequest fetchBatchSize 35 | */ 36 | @interface AWPagedArray : NSProxy 37 | 38 | /** 39 | * The designated initializer for this class 40 | * Note that the parameters are part of immutable state 41 | */ 42 | - (instancetype)initWithCount:(NSUInteger)count objectsPerPage:(NSUInteger)objectsPerPage initialPageIndex:(NSInteger)initialPageIndex; 43 | 44 | /** 45 | * Convenience initializer with initialPageIndex = 1 46 | */ 47 | - (instancetype)initWithCount:(NSUInteger)count objectsPerPage:(NSUInteger)objectsPerPage; 48 | 49 | /** 50 | * Sets objects for a specific page in the array 51 | * @param objects The objects in the page 52 | * @param page The page which these objects should be set for, pages start with index 1 53 | * @throws AWPagedArrayObjectsPerPageMismatchException when page size mismatch the initialized objectsPerPage property 54 | * for any page other than the last. 55 | */ 56 | - (void)setObjects:(NSArray *)objects forPage:(NSUInteger)page; 57 | 58 | - (NSUInteger)pageForIndex:(NSUInteger)index; 59 | - (NSIndexSet *)indexSetForPage:(NSUInteger)page; 60 | 61 | @property (nonatomic, readonly) NSUInteger objectsPerPage; 62 | @property (nonatomic, readonly) NSUInteger numberOfPages; 63 | @property (nonatomic, readonly) NSInteger initialPageIndex; 64 | 65 | /** 66 | * Contains NSArray instances of pages, backing the data 67 | */ 68 | @property (nonatomic, readonly) NSDictionary *pages; 69 | 70 | @property (nonatomic, weak) id delegate; 71 | 72 | @end 73 | 74 | 75 | @protocol AWPagedArrayDelegate 76 | 77 | /** 78 | * Called when the an object is accessed by index 79 | * 80 | * @param pagedArray the paged array being accessed 81 | * @param index the index in the paged array 82 | * @param returnObject an id pointer to the object which will be returned to the receiver of the accessor being called. 83 | * 84 | * @discussion This delegate method is only called when the paged array is accessed by the objectAtIndex: method or by subscripting. 85 | * The returnObject pointer can be changed in order to change which object will be returned. 86 | */ 87 | - (void)pagedArray:(AWPagedArray *)pagedArray willAccessIndex:(NSUInteger)index returnObject:(__autoreleasing id *)returnObject; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /AWPagedArray.m: -------------------------------------------------------------------------------- 1 | // 2 | // AWPagedArray.m 3 | // 4 | // Copyright (c) 2014 Alek Åström 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import "AWPagedArray.h" 26 | 27 | @implementation AWPagedArray { 28 | NSUInteger _totalCount; 29 | NSUInteger _objectsPerPage; 30 | NSMutableDictionary *_pages; 31 | 32 | BOOL _needsUpdateProxiedArray; 33 | NSArray *_proxiedArray; 34 | } 35 | 36 | #pragma mark - Public methods 37 | #pragma mark Initialization 38 | - (instancetype)initWithCount:(NSUInteger)count objectsPerPage:(NSUInteger)objectsPerPage initialPageIndex:(NSInteger)initialPageIndex { 39 | 40 | _totalCount = count; 41 | _objectsPerPage = objectsPerPage; 42 | _pages = [[NSMutableDictionary alloc] initWithCapacity:[self numberOfPages]]; 43 | _initialPageIndex = initialPageIndex; 44 | 45 | return self; 46 | } 47 | - (instancetype)initWithCount:(NSUInteger)count objectsPerPage:(NSUInteger)objectsPerPage { 48 | return [self initWithCount:count objectsPerPage:objectsPerPage initialPageIndex:1]; 49 | } 50 | 51 | #pragma mark Accessing data 52 | - (void)setObjects:(NSArray *)objects forPage:(NSUInteger)page { 53 | 54 | // Make sure object count is correct 55 | NSAssert((objects.count == _objectsPerPage || page == [self _lastPageIndex]), @"Expected object count per page: %ld received: %ld", (unsigned long)_objectsPerPage, (unsigned long)objects.count); 56 | 57 | _pages[@(page)] = objects; 58 | _needsUpdateProxiedArray = YES; 59 | } 60 | - (NSUInteger)pageForIndex:(NSUInteger)index { 61 | return index/_objectsPerPage + _initialPageIndex; 62 | } 63 | - (NSIndexSet *)indexSetForPage:(NSUInteger)page { 64 | NSParameterAssert(page < _initialPageIndex+[self numberOfPages]); 65 | 66 | NSUInteger rangeLength = _objectsPerPage; 67 | if (page == [self _lastPageIndex]) { 68 | rangeLength = (_totalCount % _objectsPerPage) ?: _objectsPerPage; 69 | } 70 | return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange((page - _initialPageIndex) * _objectsPerPage, rangeLength)]; 71 | } 72 | - (NSDictionary *)pages { 73 | return _pages; 74 | } 75 | - (NSUInteger)numberOfPages { 76 | return ceil((CGFloat)_totalCount/_objectsPerPage); 77 | } 78 | 79 | #pragma mark - NSArray overrides 80 | - (id)objectAtIndex:(NSUInteger)index { 81 | 82 | id object = [[self _proxiedArray] objectAtIndex:index]; 83 | 84 | [self.delegate pagedArray:self 85 | willAccessIndex:index 86 | returnObject:&object]; 87 | 88 | return object; 89 | } 90 | - (id)objectAtIndexedSubscript:(NSUInteger)index { 91 | return [self objectAtIndex:index]; 92 | } 93 | 94 | #pragma mark - Proxying 95 | + (Class)class { 96 | return [NSArray class]; 97 | } 98 | - (void)forwardInvocation:(NSInvocation *)anInvocation { 99 | 100 | [anInvocation setTarget:[self _proxiedArray]]; 101 | [anInvocation invoke]; 102 | } 103 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 104 | return [[self _proxiedArray] methodSignatureForSelector:sel]; 105 | } 106 | + (BOOL)respondsToSelector:(SEL)aSelector { 107 | 108 | id proxy = [[[self class] alloc] init]; 109 | return [proxy respondsToSelector:aSelector]; 110 | } 111 | - (NSString *)description { 112 | return [[self _proxiedArray] description]; 113 | } 114 | 115 | #pragma mark - Private methods 116 | - (NSArray *)_proxiedArray { 117 | 118 | if (!_proxiedArray || _needsUpdateProxiedArray) { 119 | 120 | [self _generateProxiedArray]; 121 | _needsUpdateProxiedArray = NO; 122 | } 123 | 124 | return _proxiedArray; 125 | } 126 | - (void)_generateProxiedArray { 127 | 128 | NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:_totalCount]; 129 | 130 | for (NSInteger pageIndex = _initialPageIndex; pageIndex < [self numberOfPages]+_initialPageIndex; pageIndex++) { 131 | 132 | NSArray *page = _pages[@(pageIndex)]; 133 | if (!page) page = [self _placeholdersForPage:pageIndex]; 134 | 135 | [objects addObjectsFromArray:page]; 136 | } 137 | 138 | _proxiedArray = objects; 139 | } 140 | - (NSArray *)_placeholdersForPage:(NSUInteger)page { 141 | 142 | NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:_objectsPerPage]; 143 | 144 | NSUInteger pageLimit = [[self indexSetForPage:page] count]; 145 | for (NSUInteger i = 0; i < pageLimit; ++i) { 146 | [placeholders addObject:[NSNull null]]; 147 | } 148 | 149 | return placeholders; 150 | } 151 | - (NSInteger)_lastPageIndex { 152 | return [self numberOfPages]+_initialPageIndex-1; 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /AWPagedArray.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AWPagedArray' 3 | s.version = '0.3.0' 4 | s.summary = 'An Objective-C proxy class for creating paged arrays' 5 | s.description = <<-DESC 6 | AWPagedArray is an Objective-C class which acts as an NSArray proxy 7 | for easier paging mechanisms in UITableView's and UICollectionView's. 8 | DESC 9 | s.homepage = 'https://github.com/MrAlek/AWPagedArray' 10 | s.author = { 'Alek Åström' => 'hi@mralek.se' } 11 | s.license = { :type => 'MIT', :file => 'LICENSE' } 12 | s.source = { :git => "https://github.com/MrAlek/AWPagedArray.git", :tag => s.version.to_s } 13 | s.source_files = '*.{h,m}' 14 | s.requires_arc = true 15 | s.ios.deployment_target = "6.0" 16 | s.osx.deployment_target = "10.8" 17 | end 18 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 93262E97186E457C00B05C01 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93262E96186E457C00B05C01 /* Foundation.framework */; }; 11 | 93262E99186E457C00B05C01 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93262E98186E457C00B05C01 /* CoreGraphics.framework */; }; 12 | 93262E9B186E457C00B05C01 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93262E9A186E457C00B05C01 /* UIKit.framework */; }; 13 | 93262EA1186E457C00B05C01 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 93262E9F186E457C00B05C01 /* InfoPlist.strings */; }; 14 | 93262EA3186E457C00B05C01 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 93262EA2186E457C00B05C01 /* main.m */; }; 15 | 93262EA7186E457C00B05C01 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 93262EA6186E457C00B05C01 /* AppDelegate.m */; }; 16 | 93262EA9186E457C00B05C01 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 93262EA8186E457C00B05C01 /* Images.xcassets */; }; 17 | 93262EC6186E45CC00B05C01 /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 93262EC5186E45CC00B05C01 /* MainStoryboard.storyboard */; }; 18 | 93262EC9186E4CFE00B05C01 /* DataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 93262EC8186E4CFE00B05C01 /* DataProvider.m */; }; 19 | 93262ECC186E5B0700B05C01 /* ClassicPagingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93262ECB186E5B0700B05C01 /* ClassicPagingViewController.m */; }; 20 | 93262ECF186E731A00B05C01 /* FluentPagingTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93262ECE186E731A00B05C01 /* FluentPagingTableViewController.m */; }; 21 | 93848026186E75B80006006E /* FluentPagingCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93848025186E75B80006006E /* FluentPagingCollectionViewController.m */; }; 22 | 9384802C186E767E0006006E /* LabelCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9384802B186E767E0006006E /* LabelCollectionViewCell.m */; }; 23 | 93A4038A18E48FFC00640CAD /* AWPagedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 93A4038918E48FFC00640CAD /* AWPagedArray.m */; }; 24 | 93BEE49D1907C403008B4694 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 93BEE49C1907C403008B4694 /* LICENSE */; }; 25 | 93D9254818F84DA700A72AEF /* DataLoadingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D9254718F84DA700A72AEF /* DataLoadingOperation.m */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 93262E93186E457C00B05C01 /* FluentResourcePaging-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "FluentResourcePaging-example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 93262E96186E457C00B05C01 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 31 | 93262E98186E457C00B05C01 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 32 | 93262E9A186E457C00B05C01 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 33 | 93262E9E186E457C00B05C01 /* FluentResourcePaging-example-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FluentResourcePaging-example-Info.plist"; sourceTree = ""; }; 34 | 93262EA0186E457C00B05C01 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 35 | 93262EA2186E457C00B05C01 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 36 | 93262EA4186E457C00B05C01 /* FluentResourcePaging-example-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FluentResourcePaging-example-Prefix.pch"; sourceTree = ""; }; 37 | 93262EA5186E457C00B05C01 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 38 | 93262EA6186E457C00B05C01 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 39 | 93262EA8186E457C00B05C01 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 40 | 93262EAF186E457C00B05C01 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 41 | 93262EC5186E45CC00B05C01 /* MainStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MainStoryboard.storyboard; sourceTree = ""; }; 42 | 93262EC7186E4CFE00B05C01 /* DataProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataProvider.h; sourceTree = ""; }; 43 | 93262EC8186E4CFE00B05C01 /* DataProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataProvider.m; sourceTree = ""; }; 44 | 93262ECA186E5B0700B05C01 /* ClassicPagingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClassicPagingViewController.h; sourceTree = ""; }; 45 | 93262ECB186E5B0700B05C01 /* ClassicPagingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClassicPagingViewController.m; sourceTree = ""; }; 46 | 93262ECD186E731A00B05C01 /* FluentPagingTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FluentPagingTableViewController.h; sourceTree = ""; }; 47 | 93262ECE186E731A00B05C01 /* FluentPagingTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FluentPagingTableViewController.m; sourceTree = ""; }; 48 | 9345345218EF4F3000A23A35 /* DataReceiver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataReceiver.h; sourceTree = ""; }; 49 | 93848024186E75B80006006E /* FluentPagingCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FluentPagingCollectionViewController.h; sourceTree = ""; }; 50 | 93848025186E75B80006006E /* FluentPagingCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FluentPagingCollectionViewController.m; sourceTree = ""; }; 51 | 9384802A186E767E0006006E /* LabelCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LabelCollectionViewCell.h; sourceTree = ""; }; 52 | 9384802B186E767E0006006E /* LabelCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LabelCollectionViewCell.m; sourceTree = ""; }; 53 | 93A4038818E48FFC00640CAD /* AWPagedArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AWPagedArray.h; path = ../AWPagedArray.h; sourceTree = ""; }; 54 | 93A4038918E48FFC00640CAD /* AWPagedArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AWPagedArray.m; path = ../AWPagedArray.m; sourceTree = ""; }; 55 | 93BEE49C1907C403008B4694 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 56 | 93D9254618F84DA700A72AEF /* DataLoadingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataLoadingOperation.h; sourceTree = ""; }; 57 | 93D9254718F84DA700A72AEF /* DataLoadingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataLoadingOperation.m; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 93262E90186E457C00B05C01 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 93262E99186E457C00B05C01 /* CoreGraphics.framework in Frameworks */, 66 | 93262E9B186E457C00B05C01 /* UIKit.framework in Frameworks */, 67 | 93262E97186E457C00B05C01 /* Foundation.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 93262E8A186E457C00B05C01 = { 75 | isa = PBXGroup; 76 | children = ( 77 | 93A4038818E48FFC00640CAD /* AWPagedArray.h */, 78 | 93A4038918E48FFC00640CAD /* AWPagedArray.m */, 79 | 93BEE49C1907C403008B4694 /* LICENSE */, 80 | 93262E9C186E457C00B05C01 /* FluentResourcePaging-example */, 81 | 93262E95186E457C00B05C01 /* Frameworks */, 82 | 93262E94186E457C00B05C01 /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 93262E94186E457C00B05C01 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 93262E93186E457C00B05C01 /* FluentResourcePaging-example.app */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | 93262E95186E457C00B05C01 /* Frameworks */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 93262E96186E457C00B05C01 /* Foundation.framework */, 98 | 93262E98186E457C00B05C01 /* CoreGraphics.framework */, 99 | 93262E9A186E457C00B05C01 /* UIKit.framework */, 100 | 93262EAF186E457C00B05C01 /* XCTest.framework */, 101 | ); 102 | name = Frameworks; 103 | sourceTree = ""; 104 | }; 105 | 93262E9C186E457C00B05C01 /* FluentResourcePaging-example */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 9345345218EF4F3000A23A35 /* DataReceiver.h */, 109 | 93262EA5186E457C00B05C01 /* AppDelegate.h */, 110 | 93262EA6186E457C00B05C01 /* AppDelegate.m */, 111 | 93D9254618F84DA700A72AEF /* DataLoadingOperation.h */, 112 | 93D9254718F84DA700A72AEF /* DataLoadingOperation.m */, 113 | 93262EC7186E4CFE00B05C01 /* DataProvider.h */, 114 | 93262EC8186E4CFE00B05C01 /* DataProvider.m */, 115 | 93262ECA186E5B0700B05C01 /* ClassicPagingViewController.h */, 116 | 93262ECB186E5B0700B05C01 /* ClassicPagingViewController.m */, 117 | 93262ECD186E731A00B05C01 /* FluentPagingTableViewController.h */, 118 | 93262ECE186E731A00B05C01 /* FluentPagingTableViewController.m */, 119 | 93848024186E75B80006006E /* FluentPagingCollectionViewController.h */, 120 | 93848025186E75B80006006E /* FluentPagingCollectionViewController.m */, 121 | 9384802A186E767E0006006E /* LabelCollectionViewCell.h */, 122 | 9384802B186E767E0006006E /* LabelCollectionViewCell.m */, 123 | 93262EC5186E45CC00B05C01 /* MainStoryboard.storyboard */, 124 | 93262EA8186E457C00B05C01 /* Images.xcassets */, 125 | 93262E9D186E457C00B05C01 /* Supporting Files */, 126 | ); 127 | path = "FluentResourcePaging-example"; 128 | sourceTree = ""; 129 | }; 130 | 93262E9D186E457C00B05C01 /* Supporting Files */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 93262E9E186E457C00B05C01 /* FluentResourcePaging-example-Info.plist */, 134 | 93262E9F186E457C00B05C01 /* InfoPlist.strings */, 135 | 93262EA2186E457C00B05C01 /* main.m */, 136 | 93262EA4186E457C00B05C01 /* FluentResourcePaging-example-Prefix.pch */, 137 | ); 138 | name = "Supporting Files"; 139 | sourceTree = ""; 140 | }; 141 | /* End PBXGroup section */ 142 | 143 | /* Begin PBXNativeTarget section */ 144 | 93262E92186E457C00B05C01 /* FluentResourcePaging-example */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 93262EBF186E457C00B05C01 /* Build configuration list for PBXNativeTarget "FluentResourcePaging-example" */; 147 | buildPhases = ( 148 | 93262E8F186E457C00B05C01 /* Sources */, 149 | 93262E90186E457C00B05C01 /* Frameworks */, 150 | 93262E91186E457C00B05C01 /* Resources */, 151 | ); 152 | buildRules = ( 153 | ); 154 | dependencies = ( 155 | ); 156 | name = "FluentResourcePaging-example"; 157 | productName = "FluentResourcePaging-example"; 158 | productReference = 93262E93186E457C00B05C01 /* FluentResourcePaging-example.app */; 159 | productType = "com.apple.product-type.application"; 160 | }; 161 | /* End PBXNativeTarget section */ 162 | 163 | /* Begin PBXProject section */ 164 | 93262E8B186E457C00B05C01 /* Project object */ = { 165 | isa = PBXProject; 166 | attributes = { 167 | LastUpgradeCheck = 0510; 168 | ORGANIZATIONNAME = "Alek Åström"; 169 | }; 170 | buildConfigurationList = 93262E8E186E457C00B05C01 /* Build configuration list for PBXProject "FluentResourcePaging-example" */; 171 | compatibilityVersion = "Xcode 3.2"; 172 | developmentRegion = English; 173 | hasScannedForEncodings = 0; 174 | knownRegions = ( 175 | en, 176 | ); 177 | mainGroup = 93262E8A186E457C00B05C01; 178 | productRefGroup = 93262E94186E457C00B05C01 /* Products */; 179 | projectDirPath = ""; 180 | projectRoot = ""; 181 | targets = ( 182 | 93262E92186E457C00B05C01 /* FluentResourcePaging-example */, 183 | ); 184 | }; 185 | /* End PBXProject section */ 186 | 187 | /* Begin PBXResourcesBuildPhase section */ 188 | 93262E91186E457C00B05C01 /* Resources */ = { 189 | isa = PBXResourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 93262EC6186E45CC00B05C01 /* MainStoryboard.storyboard in Resources */, 193 | 93262EA1186E457C00B05C01 /* InfoPlist.strings in Resources */, 194 | 93BEE49D1907C403008B4694 /* LICENSE in Resources */, 195 | 93262EA9186E457C00B05C01 /* Images.xcassets in Resources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXResourcesBuildPhase section */ 200 | 201 | /* Begin PBXSourcesBuildPhase section */ 202 | 93262E8F186E457C00B05C01 /* Sources */ = { 203 | isa = PBXSourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | 93262EA7186E457C00B05C01 /* AppDelegate.m in Sources */, 207 | 93262EA3186E457C00B05C01 /* main.m in Sources */, 208 | 9384802C186E767E0006006E /* LabelCollectionViewCell.m in Sources */, 209 | 93848026186E75B80006006E /* FluentPagingCollectionViewController.m in Sources */, 210 | 93A4038A18E48FFC00640CAD /* AWPagedArray.m in Sources */, 211 | 93262EC9186E4CFE00B05C01 /* DataProvider.m in Sources */, 212 | 93D9254818F84DA700A72AEF /* DataLoadingOperation.m in Sources */, 213 | 93262ECC186E5B0700B05C01 /* ClassicPagingViewController.m in Sources */, 214 | 93262ECF186E731A00B05C01 /* FluentPagingTableViewController.m in Sources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXSourcesBuildPhase section */ 219 | 220 | /* Begin PBXVariantGroup section */ 221 | 93262E9F186E457C00B05C01 /* InfoPlist.strings */ = { 222 | isa = PBXVariantGroup; 223 | children = ( 224 | 93262EA0186E457C00B05C01 /* en */, 225 | ); 226 | name = InfoPlist.strings; 227 | sourceTree = ""; 228 | }; 229 | /* End PBXVariantGroup section */ 230 | 231 | /* Begin XCBuildConfiguration section */ 232 | 93262EBD186E457C00B05C01 /* Debug */ = { 233 | isa = XCBuildConfiguration; 234 | buildSettings = { 235 | ALWAYS_SEARCH_USER_PATHS = NO; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_ENABLE_MODULES = YES; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 249 | COPY_PHASE_STRIP = NO; 250 | GCC_C_LANGUAGE_STANDARD = gnu99; 251 | GCC_DYNAMIC_NO_PIC = NO; 252 | GCC_OPTIMIZATION_LEVEL = 0; 253 | GCC_PREPROCESSOR_DEFINITIONS = ( 254 | "DEBUG=1", 255 | "$(inherited)", 256 | ); 257 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 265 | ONLY_ACTIVE_ARCH = YES; 266 | SDKROOT = iphoneos; 267 | }; 268 | name = Debug; 269 | }; 270 | 93262EBE186E457C00B05C01 /* Release */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | ALWAYS_SEARCH_USER_PATHS = NO; 274 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 275 | CLANG_CXX_LIBRARY = "libc++"; 276 | CLANG_ENABLE_MODULES = YES; 277 | CLANG_ENABLE_OBJC_ARC = YES; 278 | CLANG_WARN_BOOL_CONVERSION = YES; 279 | CLANG_WARN_CONSTANT_CONVERSION = YES; 280 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INT_CONVERSION = YES; 284 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 287 | COPY_PHASE_STRIP = YES; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | GCC_C_LANGUAGE_STANDARD = gnu99; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 297 | SDKROOT = iphoneos; 298 | VALIDATE_PRODUCT = YES; 299 | }; 300 | name = Release; 301 | }; 302 | 93262EC0186E457C00B05C01 /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 306 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 307 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 308 | GCC_PREFIX_HEADER = "FluentResourcePaging-example/FluentResourcePaging-example-Prefix.pch"; 309 | INFOPLIST_FILE = "FluentResourcePaging-example/FluentResourcePaging-example-Info.plist"; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | WRAPPER_EXTENSION = app; 312 | }; 313 | name = Debug; 314 | }; 315 | 93262EC1186E457C00B05C01 /* Release */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 319 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 320 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 321 | GCC_PREFIX_HEADER = "FluentResourcePaging-example/FluentResourcePaging-example-Prefix.pch"; 322 | INFOPLIST_FILE = "FluentResourcePaging-example/FluentResourcePaging-example-Info.plist"; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | WRAPPER_EXTENSION = app; 325 | }; 326 | name = Release; 327 | }; 328 | /* End XCBuildConfiguration section */ 329 | 330 | /* Begin XCConfigurationList section */ 331 | 93262E8E186E457C00B05C01 /* Build configuration list for PBXProject "FluentResourcePaging-example" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 93262EBD186E457C00B05C01 /* Debug */, 335 | 93262EBE186E457C00B05C01 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | 93262EBF186E457C00B05C01 /* Build configuration list for PBXNativeTarget "FluentResourcePaging-example" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 93262EC0186E457C00B05C01 /* Debug */, 344 | 93262EC1186E457C00B05C01 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | /* End XCConfigurationList section */ 350 | }; 351 | rootObject = 93262E8B186E457C00B05C01 /* Project object */; 352 | } 353 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "DataReceiver.h" 11 | #import "DataProvider.h" 12 | 13 | @interface AppDelegate() 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | 21 | [self.window makeKeyAndVisible]; 22 | 23 | [[self _tabBarController] setDelegate:self]; 24 | [self _setDataProviderForViewController:[self _tabBarController].selectedViewController]; 25 | 26 | return YES; 27 | } 28 | 29 | - (void)_setDataProviderForViewController:(UIViewController *)viewController { 30 | 31 | if ([viewController isKindOfClass:[UINavigationController class]]) { 32 | id dataViewController = (id)[((UINavigationController *)viewController) topViewController]; 33 | if ([dataViewController conformsToProtocol:@protocol(DataReceiver) ]) { 34 | [dataViewController setDataProvider:[DataProvider new]]; 35 | } 36 | } 37 | } 38 | 39 | - (UITabBarController *)_tabBarController { 40 | return (UITabBarController *)self.window.rootViewController; 41 | } 42 | 43 | #pragma mark - Tab bar controller delegate 44 | - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { 45 | [self _setDataProviderForViewController:viewController]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/ClassicPagingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ClassicPagingViewController.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DataReceiver.h" 11 | 12 | @interface ClassicPagingViewController : UITableViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/ClassicPagingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ClassicPagingViewController.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import "ClassicPagingViewController.h" 10 | #import "DataProvider.h" 11 | 12 | typedef NS_ENUM(NSInteger, ClassicPagingViewControllerLoadingStyle) { 13 | ClassicPagingViewControllerLoadingStyleManual, 14 | ClassicPagingViewControllerLoadingStyleAutomatic 15 | }; 16 | 17 | const NSUInteger ClassicPagingTablePreloadMargin = 5; 18 | 19 | @interface ClassicPagingViewController () 20 | @property (nonatomic) ClassicPagingViewControllerLoadingStyle loadingStyle; 21 | @property (weak, nonatomic) IBOutlet UISwitch *preloadSwitch; 22 | @end 23 | 24 | @implementation ClassicPagingViewController { 25 | NSIndexPath *_loaderCellIndexPath; 26 | } 27 | @synthesize dataProvider = _dataProvider; 28 | 29 | #pragma mark - Accessors 30 | - (void)setDataProvider:(DataProvider *)dataProvider { 31 | 32 | if (dataProvider != _dataProvider) { 33 | 34 | _dataProvider = dataProvider; 35 | [_dataProvider loadDataForIndex:0]; 36 | _dataProvider.delegate = self; 37 | 38 | if ([self isViewLoaded]) { 39 | 40 | _dataProvider.shouldLoadAutomatically = self.preloadSwitch.on; 41 | _dataProvider.automaticPreloadMargin = self.preloadSwitch.on ? ClassicPagingTablePreloadMargin : 0; 42 | 43 | [self.tableView reloadData]; 44 | } 45 | } 46 | } 47 | - (void)setLoadingStyle:(ClassicPagingViewControllerLoadingStyle)loadingStyle { 48 | 49 | _loadingStyle = loadingStyle; 50 | self.preloadSwitch.enabled = (loadingStyle == ClassicPagingViewControllerLoadingStyleAutomatic); 51 | self.dataProvider.shouldLoadAutomatically = (loadingStyle == ClassicPagingViewControllerLoadingStyleAutomatic); 52 | 53 | [self.tableView reloadData]; 54 | } 55 | 56 | #pragma mark - User interaction 57 | - (IBAction)loadingStyleSegmentChanged:(UISegmentedControl *)sender { 58 | self.loadingStyle = sender.selectedSegmentIndex; 59 | } 60 | - (IBAction)preloadSwitchChanged:(UISwitch *)sender { 61 | self.dataProvider.automaticPreloadMargin = sender.on ? ClassicPagingTablePreloadMargin : 0; 62 | } 63 | 64 | #pragma mark - Table view 65 | #pragma mark data source 66 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 67 | return 1; 68 | } 69 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 70 | return MIN(self.dataProvider.loadedCount+1, self.dataProvider.dataObjects.count); 71 | } 72 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 73 | 74 | static NSString *DataCellIdentifier = @"data cell"; 75 | static NSString *LoadMoreCellIdentifier = @"load more cell"; 76 | static NSString *LoaderCellIdentifier = @"loader cell"; 77 | 78 | NSString *cellIdentifier; 79 | NSUInteger index = indexPath.row; 80 | 81 | if (index < self.dataProvider.loadedCount) { 82 | cellIdentifier = DataCellIdentifier; 83 | } else if ([self.dataProvider isLoadingDataAtIndex:index] || self.loadingStyle == ClassicPagingViewControllerLoadingStyleAutomatic) { 84 | cellIdentifier = LoaderCellIdentifier; 85 | _loaderCellIndexPath = indexPath; 86 | } else { 87 | cellIdentifier = LoadMoreCellIdentifier; 88 | } 89 | 90 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath]; 91 | 92 | if (cellIdentifier == DataCellIdentifier) { 93 | id data = self.dataProvider.dataObjects[indexPath.row]; 94 | 95 | if ([data isKindOfClass:[NSNumber class]]) { 96 | cell.textLabel.text = [data description]; 97 | } else { 98 | cell.textLabel.text = nil; 99 | } 100 | } 101 | 102 | return cell; 103 | } 104 | 105 | #pragma mark delegate 106 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 107 | 108 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 109 | 110 | if (indexPath.row == self.dataProvider.loadedCount) { 111 | [self.dataProvider loadDataForIndex:indexPath.row]; 112 | } 113 | } 114 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 115 | if (self.loadingStyle == ClassicPagingViewControllerLoadingStyleAutomatic && [indexPath isEqual:_loaderCellIndexPath]) { 116 | [self.dataProvider loadDataForIndex:indexPath.row]; 117 | } 118 | } 119 | 120 | #pragma mark - Data controller delegate 121 | - (void)dataProvider:(DataProvider *)dataProvider willLoadDataAtIndexes:(NSIndexSet *)indexes { 122 | 123 | if (self.loadingStyle == ClassicPagingViewControllerLoadingStyleManual) { 124 | [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:dataProvider.loadedCount inSection:0]] withRowAnimation:UITableViewRowAnimationFade]; 125 | } 126 | } 127 | - (void)dataProvider:(DataProvider *)dataProvider didLoadDataAtIndexes:(NSIndexSet *)indexes { 128 | 129 | [self.tableView beginUpdates]; 130 | [self.tableView reloadRowsAtIndexPaths:@[_loaderCellIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 131 | [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 132 | if (idx < dataProvider.dataObjects.count-1) { 133 | [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:idx+1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade]; 134 | } 135 | }]; 136 | [self.tableView endUpdates]; 137 | } 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/DataLoadingOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // DataLoadingOperation.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2014-04-11. 6 | // Copyright (c) 2014 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DataLoadingOperation : NSBlockOperation 12 | 13 | - (instancetype)initWithIndexes:(NSIndexSet *)indexes; 14 | 15 | @property (nonatomic, readonly) NSIndexSet *indexes; 16 | @property (nonatomic, readonly) NSArray *dataPage; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/DataLoadingOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // DataLoadingOperation.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2014-04-11. 6 | // Copyright (c) 2014 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import "DataLoadingOperation.h" 10 | 11 | const NSTimeInterval DataLoadingOperationDuration = 0.3; 12 | 13 | @implementation DataLoadingOperation 14 | 15 | - (instancetype)initWithIndexes:(NSIndexSet *)indexes{ 16 | 17 | self = [super init]; 18 | 19 | if (self) { 20 | 21 | _indexes = indexes; 22 | 23 | typeof(self) weakSelf = self; 24 | [self addExecutionBlock:^{ 25 | // Simulate fetching 26 | [NSThread sleepForTimeInterval:DataLoadingOperationDuration]; 27 | 28 | // Generate data 29 | NSMutableArray *dataPage = [NSMutableArray array]; 30 | [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 31 | [dataPage addObject:@(idx+1)]; 32 | }]; 33 | 34 | weakSelf->_dataPage = dataPage; 35 | }]; 36 | } 37 | 38 | return self; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/DataProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DataProvider.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | @class DataProvider; 12 | @protocol DataProviderDelegate 13 | 14 | @optional 15 | - (void)dataProvider:(DataProvider *)dataProvider willLoadDataAtIndexes:(NSIndexSet *)indexes; 16 | - (void)dataProvider:(DataProvider *)dataProvider didLoadDataAtIndexes:(NSIndexSet *)indexes; 17 | 18 | @end 19 | 20 | @interface DataProvider : NSObject 21 | 22 | - (instancetype)initWithPageSize:(NSUInteger)pageSize; 23 | 24 | @property (nonatomic, weak) id delegate; 25 | 26 | /** 27 | * The array returned contains NSNull values for data 28 | * objects not yet loaded. As data loads, the array updates 29 | * automatically to include the newly loaded objects. 30 | * 31 | * @see shouldLoadAutomatically 32 | */ 33 | @property (nonatomic, readonly) NSArray *dataObjects; 34 | 35 | @property (nonatomic, readonly) NSUInteger pageSize; 36 | @property (nonatomic, readonly) NSUInteger loadedCount; 37 | 38 | /** 39 | * When this property is set, new data is automatically loaded when 40 | * the dataObjects array returns an NSNull reference. 41 | * 42 | * @see dataObjects 43 | */ 44 | @property (nonatomic) BOOL shouldLoadAutomatically; 45 | @property (nonatomic) NSUInteger automaticPreloadMargin; 46 | 47 | - (BOOL)isLoadingDataAtIndex:(NSUInteger)index; 48 | - (void)loadDataForIndex:(NSUInteger)index; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/DataProvider.m: -------------------------------------------------------------------------------- 1 | // 2 | // DataProvider.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import "DataProvider.h" 10 | #import "AWPagedArray.h" 11 | #import "DataLoadingOperation.h" 12 | 13 | const NSUInteger DataProviderDefaultPageSize = 20; 14 | const NSUInteger DataProviderDataCount = 200; 15 | 16 | @interface DataProvider () @end 17 | 18 | @implementation DataProvider { 19 | AWPagedArray *_pagedArray; 20 | NSOperationQueue *_operationQueue; 21 | NSMutableDictionary *_dataLoadingOperations; 22 | } 23 | 24 | #pragma mark - Cleanup 25 | - (void)dealloc { 26 | [_operationQueue.operations makeObjectsPerformSelector:@selector(cancel)]; 27 | } 28 | 29 | #pragma mark - Initialization 30 | - (instancetype)init { 31 | return [self initWithPageSize:DataProviderDefaultPageSize]; 32 | } 33 | - (instancetype)initWithPageSize:(NSUInteger)pageSize { 34 | 35 | self = [super init]; 36 | if (self) { 37 | _pagedArray = [[AWPagedArray alloc] initWithCount:DataProviderDataCount objectsPerPage:pageSize]; 38 | _pagedArray.delegate = self; 39 | _dataLoadingOperations = [NSMutableDictionary dictionary]; 40 | _operationQueue = [NSOperationQueue new]; 41 | } 42 | return self; 43 | } 44 | 45 | #pragma mark - Accessors 46 | - (NSUInteger)loadedCount { 47 | return _pagedArray.pages.count*_pagedArray.objectsPerPage; 48 | } 49 | - (NSUInteger)pageSize { 50 | return _pagedArray.objectsPerPage; 51 | } 52 | - (NSArray *)dataObjects { 53 | return (NSArray *)_pagedArray; 54 | } 55 | 56 | #pragma mark - Other public methods 57 | - (BOOL)isLoadingDataAtIndex:(NSUInteger)index { 58 | return _dataLoadingOperations[@([_pagedArray pageForIndex:index])] != nil; 59 | } 60 | - (void)loadDataForIndex:(NSUInteger)index { 61 | [self _setShouldLoadDataForPage:[_pagedArray pageForIndex:index]]; 62 | } 63 | 64 | #pragma mark - Private methods 65 | - (void)_setShouldLoadDataForPage:(NSUInteger)page { 66 | 67 | if (!_pagedArray.pages[@(page)] && !_dataLoadingOperations[@(page)]) { 68 | // Don't load data if there already is a loading operation in progress 69 | [self _loadDataForPage:page]; 70 | } 71 | } 72 | - (void)_loadDataForPage:(NSUInteger)page { 73 | 74 | NSIndexSet *indexes = [_pagedArray indexSetForPage:page]; 75 | 76 | NSOperation *loadingOperation = [self _loadingOperationForPage:page indexes:indexes]; 77 | _dataLoadingOperations[@(page)] = loadingOperation; 78 | 79 | if ([self.delegate respondsToSelector:@selector(dataProvider:willLoadDataAtIndexes:)]) { 80 | [self.delegate dataProvider:self willLoadDataAtIndexes:indexes]; 81 | } 82 | [_operationQueue addOperation:loadingOperation]; 83 | } 84 | - (NSOperation *)_loadingOperationForPage:(NSUInteger)page indexes:(NSIndexSet *)indexes { 85 | 86 | DataLoadingOperation *operation = [[DataLoadingOperation alloc] initWithIndexes:indexes]; 87 | 88 | // Remember to not retain self in block since we store the operation 89 | __weak typeof(self) weakSelf = self; 90 | __weak typeof(operation) weakOperation = operation; 91 | operation.completionBlock = ^{ 92 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 93 | [weakSelf _dataOperation:weakOperation finishedLoadingForPage:page]; 94 | }]; 95 | }; 96 | 97 | return operation; 98 | } 99 | - (void)_preloadNextPageIfNeededForIndex:(NSUInteger)index { 100 | 101 | if (!self.shouldLoadAutomatically) { 102 | return; 103 | } 104 | 105 | NSUInteger currentPage = [_pagedArray pageForIndex:index]; 106 | NSUInteger preloadPage = [_pagedArray pageForIndex:index+self.automaticPreloadMargin]; 107 | 108 | if (preloadPage > currentPage && preloadPage <= _pagedArray.numberOfPages) { 109 | [self _setShouldLoadDataForPage:preloadPage]; 110 | } 111 | } 112 | - (void)_dataOperation:(DataLoadingOperation *)operation finishedLoadingForPage:(NSUInteger)page { 113 | 114 | [_dataLoadingOperations removeObjectForKey:@(page)]; 115 | [_pagedArray setObjects:operation.dataPage forPage:page]; 116 | 117 | if ([self.delegate respondsToSelector:@selector(dataProvider:didLoadDataAtIndexes:)]) { 118 | [self.delegate dataProvider:self didLoadDataAtIndexes:operation.indexes]; 119 | } 120 | } 121 | 122 | #pragma mark - Paged array delegate 123 | - (void)pagedArray:(AWPagedArray *)pagedArray willAccessIndex:(NSUInteger)index returnObject:(__autoreleasing id *)returnObject { 124 | 125 | if ([*returnObject isKindOfClass:[NSNull class]] && self.shouldLoadAutomatically) { 126 | [self _setShouldLoadDataForPage:[pagedArray pageForIndex:index]]; 127 | } else { 128 | [self _preloadNextPageIfNeededForIndex:index]; 129 | } 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/DataReceiver.h: -------------------------------------------------------------------------------- 1 | // 2 | // DataReceiver.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2014-04-04. 6 | // Copyright (c) 2014 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DataProvider; 12 | @protocol DataReceiver 13 | 14 | @property (nonatomic) DataProvider *dataProvider; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/FluentPagingCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FluentPagingCollectionViewController.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DataReceiver.h" 11 | 12 | @interface FluentPagingCollectionViewController : UICollectionViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/FluentPagingCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FluentPagingCollectionViewController.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import "FluentPagingCollectionViewController.h" 10 | #import "DataProvider.h" 11 | #import "LabelCollectionViewCell.h" 12 | 13 | const NSUInteger FluentPagingCollectionViewPreloadMargin = 10; 14 | 15 | @interface FluentPagingCollectionViewController () 16 | @property (weak, nonatomic) IBOutlet UISwitch *preloadSwitch; 17 | @end 18 | 19 | @implementation FluentPagingCollectionViewController 20 | @synthesize dataProvider = _dataProvider; 21 | 22 | #pragma mark - Accessors 23 | - (void)setDataProvider:(DataProvider *)dataProvider { 24 | 25 | if (dataProvider != _dataProvider) { 26 | _dataProvider = dataProvider; 27 | _dataProvider.delegate = self; 28 | _dataProvider.shouldLoadAutomatically = YES; 29 | _dataProvider.automaticPreloadMargin = self.preloadSwitch.on ? FluentPagingCollectionViewPreloadMargin : 0; 30 | 31 | if ([self isViewLoaded]) { 32 | [self.collectionView reloadData]; 33 | } 34 | } 35 | } 36 | 37 | #pragma mark - User interaction 38 | - (IBAction)preloadSwitchChanged:(UISwitch *)sender { 39 | self.dataProvider.automaticPreloadMargin = sender.on ? FluentPagingCollectionViewPreloadMargin : 0; 40 | } 41 | 42 | #pragma mark - Collection view data source 43 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 44 | return self.dataProvider.dataObjects.count; 45 | } 46 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 47 | 48 | static NSString *CellIdentifier = @"data cell"; 49 | 50 | LabelCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath]; 51 | 52 | id data = self.dataProvider.dataObjects[indexPath.row]; 53 | [self _configureCell:cell forDataObject:data animated:NO]; 54 | 55 | return cell; 56 | } 57 | 58 | #pragma mark - Data controller delegate 59 | - (void)dataProvider:(DataProvider *)dataProvider willLoadDataAtIndexes:(NSIndexSet *)indexes { 60 | 61 | } 62 | - (void)dataProvider:(DataProvider *)dataProvider didLoadDataAtIndexes:(NSIndexSet *)indexes { 63 | 64 | [indexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { 65 | 66 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; 67 | 68 | if ([self.collectionView.indexPathsForVisibleItems containsObject:indexPath]) { 69 | 70 | LabelCollectionViewCell *cell = (LabelCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; 71 | [self _configureCell:cell forDataObject:dataProvider.dataObjects[index] animated:YES]; 72 | } 73 | }]; 74 | } 75 | 76 | #pragma mark - Private methods 77 | - (void)_configureCell:(LabelCollectionViewCell *)cell forDataObject:(id)dataObject animated:(BOOL)animated { 78 | 79 | if ([dataObject isKindOfClass:[NSNumber class]]) { 80 | 81 | cell.label.text = [dataObject description]; 82 | 83 | if (animated) { 84 | cell.label.alpha = 0; 85 | [UIView animateWithDuration:0.3 animations:^{ 86 | cell.label.alpha = 1; 87 | }]; 88 | } 89 | } else { 90 | cell.label.text = nil; 91 | } 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/FluentPagingTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FluentPagingTableViewController.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DataReceiver.h" 11 | 12 | @interface FluentPagingTableViewController : UITableViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/FluentPagingTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FluentPagingTableViewController.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import "FluentPagingTableViewController.h" 10 | #import "DataProvider.h" 11 | 12 | const NSUInteger FluentPagingTablePreloadMargin = 5; 13 | 14 | @interface FluentPagingTableViewController () 15 | @property (weak, nonatomic) IBOutlet UISwitch *preloadSwitch; 16 | @end 17 | 18 | @implementation FluentPagingTableViewController 19 | @synthesize dataProvider = _dataProvider; 20 | 21 | #pragma mark - Accessors 22 | - (void)setDataProvider:(DataProvider *)dataProvider { 23 | 24 | if (dataProvider != _dataProvider) { 25 | _dataProvider = dataProvider; 26 | _dataProvider.delegate = self; 27 | _dataProvider.shouldLoadAutomatically = YES; 28 | _dataProvider.automaticPreloadMargin = self.preloadSwitch.on ? FluentPagingTablePreloadMargin : 0; 29 | 30 | if ([self isViewLoaded]) { 31 | [self.tableView reloadData]; 32 | } 33 | } 34 | } 35 | 36 | #pragma mark - User interaction 37 | - (IBAction)preloadSwitchChanged:(UISwitch *)sender { 38 | self.dataProvider.automaticPreloadMargin = sender.on ? FluentPagingTablePreloadMargin : 0; 39 | } 40 | 41 | #pragma mark - Table view data source 42 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 43 | return self.dataProvider.dataObjects.count; 44 | } 45 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 46 | 47 | static NSString *CellIdentifier = @"data cell"; 48 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 49 | 50 | id dataObject = self.dataProvider.dataObjects[indexPath.row]; 51 | [self _configureCell:cell forDataObject:dataObject]; 52 | 53 | return cell; 54 | } 55 | 56 | #pragma mark - Data controller delegate 57 | - (void)dataProvider:(DataProvider *)dataProvider didLoadDataAtIndexes:(NSIndexSet *)indexes { 58 | 59 | NSMutableArray *indexPathsToReload = [NSMutableArray array]; 60 | 61 | [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 62 | 63 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0]; 64 | 65 | if ([self.tableView.indexPathsForVisibleRows containsObject:indexPath]) { 66 | [indexPathsToReload addObject:indexPath]; 67 | } 68 | }]; 69 | 70 | if (indexPathsToReload.count > 0) { 71 | [self.tableView reloadRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationFade]; 72 | } 73 | } 74 | 75 | #pragma mark - Private methods 76 | - (void)_configureCell:(UITableViewCell *)cell forDataObject:(id)dataObject { 77 | 78 | if ([dataObject isKindOfClass:[NSNull class]]) { 79 | cell.textLabel.text = nil; 80 | } else { 81 | cell.textLabel.text = [dataObject description]; 82 | } 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/FluentResourcePaging-example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.appsandwonders.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | MainStoryboard 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/FluentResourcePaging-example-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/LabelCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // LabelCollectionViewCell.h 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LabelCollectionViewCell : UICollectionViewCell 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *label; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/LabelCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // LabelCollectionViewCell.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import "LabelCollectionViewCell.h" 10 | 11 | @implementation LabelCollectionViewCell 12 | @end 13 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/MainStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Demo/FluentResourcePaging-example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FluentResourcePaging-example 4 | // 5 | // Created by Alek Astrom on 2013-12-28. 6 | // Copyright (c) 2013 Alek Åström. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GitHub/PreloadFluentCollectionView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrAlek/AWPagedArray/06ae04f936150a8a10e3ab522cd5b2e1bc2e0506/GitHub/PreloadFluentCollectionView.gif -------------------------------------------------------------------------------- /GitHub/PreloadFluentPaging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrAlek/AWPagedArray/06ae04f936150a8a10e3ab522cd5b2e1bc2e0506/GitHub/PreloadFluentPaging.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Alek Åström 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWPagedArray 2 | 3 | `AWPagedArray` is an [`NSProxy`](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSProxy_Class/Reference/Reference.html) subclass which uses an `NSMutableDictionary` as its backbone to provide transparent paging through a standard `NSArray` API. 4 | 5 | ![AWPagedArray console output](https://dl.dropboxusercontent.com/u/330802/FluentResourcePaging/AWPagedArray%20console%20output.png) 6 | 7 | This means a data provider object can internally populate pages, while the receiver of data is agnostic of how the paging actually works. For objects not yet loaded, the proxy just returns `NSNull` values. 8 | 9 | The class is especially useful for `UITableView`s and `UICollectionView`s which contain paged data from external API's. 10 | 11 | ## Swift 12 | There is now a Swift equivalent to this data structure simply called [`PagedArray`](https://github.com/MrAlek/PagedArray). Check it out if you're looking to add some fluent paging to your Swift app! 13 | 14 | ## Installation 15 | 16 | ### CocoaPods 17 | [CocoaPods](http://cocoapods.org) is the recommended way to add AWPagedArray to your project. 18 | 19 | 1. Add a pod entry for AWPagedArray to your Podfile `pod 'AWPagedArray', '~> 0.1'` 20 | 2. Install the pod(s) by running `pod install`. 21 | 3. Include AWPagedArray wherever you need it with `#import "AWPagedArray.h"`. 22 | 23 | ### Source files 24 | 25 | Alternatively you can directly add the `AWPagedArray.h` and `AWPagedArray.m` source files to your project. 26 | 27 | 1. Download the [latest code version](https://github.com/MrAlek/AWPagedArray/archive/master.zip) or add the repository as a git submodule to your git-tracked project. 28 | 2. Open your project in Xcode, then drag and drop `AWPagedArray.h` and `AWPagedArray.m` onto your project (use the "Product Navigator view"). Make sure to select Copy items when asked if you extracted the code archive outside of your project. 29 | 3. Include AWPagedArray wherever you need it with `#import "AWPagedArray.h"`. 30 | 31 | ## Usage 32 | 33 | ```objective-c 34 | _pagedArray = [[AWPagedArray alloc] initWithCount:count objectsPerPage:pageSize]; 35 | _pagedArray.delegate = self; 36 | 37 | [_pagedArray setObjects:objects forPage:1]; 38 | ``` 39 | 40 | After instantiating the paged array, you set pages with the `setObjects:forPage:` method, while casting the paged array back as an `NSArray` to the data consumer (for example a `UITableViewController`). 41 | 42 | ```objective-c 43 | // DataProvider.h 44 | @property (nonatomic, readonly) NSArray *dataObjects; 45 | 46 | // DataProvider.m 47 | - (NSArray *)dataObjects { 48 | return (NSArray *)_pagedArray; 49 | } 50 | ``` 51 | 52 | Through the `AWPagedArrayDelegate` protocol, the data provider gets callbacks when data is access from the paged array. This way, the data provider can start loading pages as soon as an `NSNull` value is being accessed or preload the next page if the user starts to get close to an empty index. 53 | 54 | ```objective-c 55 | - (void)pagedArray:(AWPagedArray *)pagedArray 56 | willAccessIndex:(NSUInteger)index 57 | returnObject:(__autoreleasing id *)returnObject { 58 | 59 | if ([*returnObject isKindOfClass:[NSNull class]] && self.shouldLoadAutomatically) { 60 | [self setShouldLoadDataForPage:[pagedArray pageForIndex:index]]; 61 | } else { 62 | [self preloadNextPageIfNeededForIndex:index]; 63 | } 64 | } 65 | ``` 66 | 67 | Since the delegate is provided with a reference pointer to the return object, it can also dynamically change what gets returned to the consumer. For instance, replace the `NSNull` placeholder object with something else. 68 | 69 | ## Demo 70 | 71 | The included demo project shows an example implementation of a data provider using an `AWPagedArray`, populating a UITableViewController and a UICollectionViewController with the fluent pagination technique as described in [this blogpost](http://www.iosnomad.com/blog/2014/4/21/fluent-pagination). 72 | 73 | ![UITableView example](GitHub/PreloadFluentPaging.gif) 74 | ![UITableView example](GitHub/PreloadFluentCollectionView.gif) 75 | 76 | ## Tests 77 | 78 | AWPagedArray is covered with XCUnit tests which can be found in the `Tests` folder. 79 | 80 | There are currently `19` tests implemented. 81 | 82 | ## Licence 83 | 84 | This code is distributed under the terms and conditions of the [MIT license](LICENSE). 85 | -------------------------------------------------------------------------------- /Tests/AWPagedArray tests/AWPagedArray tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.appsandwonders.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/AWPagedArray tests/AWPagedArray tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /Tests/AWPagedArray tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Tests/AWPagedArrayTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AWPagedArrayTests.m 3 | // 4 | // Copyright (c) 2014 Alek Åström 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import 26 | #import "AWPagedArray.h" 27 | 28 | @interface AWPagedArrayTests : XCTestCase 29 | @end 30 | 31 | const NSUInteger MutablePagedArraySize = 50; 32 | const NSUInteger MutablePagedArrayObjectsPerPage = 6; 33 | const NSUInteger MutablePagedArrayInitialPageIndex = 1; 34 | 35 | @implementation AWPagedArrayTests { 36 | AWPagedArray *_pagedArray; 37 | NSMutableArray *_firstPage; 38 | NSMutableArray *_secondPage; 39 | } 40 | 41 | - (void)setUp { 42 | [super setUp]; 43 | 44 | _pagedArray = [[AWPagedArray alloc] initWithCount:MutablePagedArraySize objectsPerPage:MutablePagedArrayObjectsPerPage initialPageIndex:MutablePagedArrayInitialPageIndex]; 45 | 46 | _firstPage = [NSMutableArray array]; 47 | for (NSInteger i = 1; i <= MutablePagedArrayObjectsPerPage; i++) { 48 | [_firstPage addObject:@(i)]; 49 | } 50 | [_pagedArray setObjects:_firstPage forPage:MutablePagedArrayInitialPageIndex]; 51 | 52 | _secondPage = [NSMutableArray array]; 53 | for (NSInteger i = MutablePagedArrayObjectsPerPage+1; i <= MutablePagedArrayObjectsPerPage*2; i++) { 54 | [_secondPage addObject:@(i)]; 55 | } 56 | [_pagedArray setObjects:_secondPage forPage:MutablePagedArrayInitialPageIndex+1]; 57 | 58 | } 59 | - (NSArray *)array { 60 | return (NSArray *)_pagedArray; 61 | } 62 | 63 | #pragma mark - Tests 64 | - (void)testSizeIsCorrect { 65 | XCTAssertEqual([self array].count, MutablePagedArraySize, @"Paged array has wrong size"); 66 | } 67 | - (void)testSizeIsCorrectWithEvenPagePartitioning { 68 | 69 | AWPagedArray *pagedArray = [[AWPagedArray alloc] initWithCount:10 objectsPerPage:1 initialPageIndex:5]; 70 | XCTAssertEqual([(NSArray *)pagedArray count], 10, @"Paged array has wrong size"); 71 | } 72 | - (void)testObjectsPerPageIsCorrect { 73 | XCTAssertEqual(_pagedArray.objectsPerPage, MutablePagedArrayObjectsPerPage, @"Paged array has wrong objects per page count"); 74 | } 75 | - (void)testReturnsRightObject { 76 | XCTAssertEqualObjects([self array][0], _firstPage[0], @"Returns wrong object!"); 77 | } 78 | - (void)testThrowsExceptionWhenSettingPageWithWrongSize { 79 | 80 | XCTAssertThrowsSpecificNamed([_pagedArray setObjects:@[@1] forPage:1], NSException, NSInternalInconsistencyException, @"Paged array throws wrong exception"); 81 | } 82 | - (void)testDoesNotThrowExceptionWhenSettingLastPageWithOddSize { 83 | 84 | NSInteger lastPage = MutablePagedArrayInitialPageIndex+[_pagedArray numberOfPages]-1; 85 | 86 | XCTAssertNoThrow([_pagedArray setObjects:@[@(1)] forPage:lastPage], @"Paged array throws exception on last page!"); 87 | } 88 | - (void)testFastEnumeration { 89 | 90 | NSMutableArray *objects = [NSMutableArray array]; 91 | for (id object in [self array]) { 92 | if (![object isKindOfClass:[NSNull class]]) { 93 | [objects addObject:object]; 94 | } 95 | } 96 | 97 | NSArray *testObjects = [_firstPage arrayByAddingObjectsFromArray:_secondPage]; 98 | 99 | XCTAssertEqualObjects(objects, testObjects, @"Fast enumeration outputs wrong objects"); 100 | } 101 | - (void)testFastEnumerationUpdatesAfterSettingNewPage { 102 | 103 | NSMutableArray *beforeObjects = [NSMutableArray array]; 104 | for (id object in [self array]) { 105 | [beforeObjects addObject:object]; 106 | } 107 | 108 | [_pagedArray setObjects:_firstPage forPage:MutablePagedArrayInitialPageIndex+2]; 109 | 110 | NSMutableArray *afterObjects = [NSMutableArray array]; 111 | for (id object in [self array]) { 112 | [afterObjects addObject:object]; 113 | } 114 | 115 | XCTAssertNotEqualObjects(beforeObjects, afterObjects, @"Fast enumeration still returns same objects after setting new page"); 116 | } 117 | - (void)testIndexOfObjectReturnsRightIndex { 118 | 119 | NSNumber *testNumber = _secondPage[0]; 120 | 121 | XCTAssertEqual([[self array] indexOfObject:testNumber], MutablePagedArrayObjectsPerPage, @"Paged array returned wrong index for object"); 122 | } 123 | - (void)testIndexOfObjectReturnsNSNotFoundWhenLookingForAnObjectNotInTheArray { 124 | 125 | XCTAssert(([[self array] indexOfObject:@(NSNotFound)] == NSNotFound), @"Index returned for an object not present in the array"); 126 | } 127 | - (void)testObjectAtIndexForEmptyPageReturnsNSNull { 128 | 129 | id object = [[self array] objectAtIndex:MutablePagedArraySize-1]; 130 | XCTAssertEqualObjects([object class], [NSNull class], @"Array doesn't return NSNull for value not yet loaded"); 131 | } 132 | - (void)testNoThrowForLiteralSyntaxForEmptyIndex { 133 | 134 | XCTAssertNoThrow([self array][MutablePagedArraySize-1], @"Array shouldn't throw exception when using literal syntax for an empty index"); 135 | } 136 | - (void)testObjectAtIndexForTooLargeIndexReturnsNSRangeException { 137 | 138 | XCTAssertThrowsSpecificNamed([[self array] objectAtIndex:MutablePagedArraySize], NSException, NSRangeException, @"Paged array doesn't throw NSRangeException when accessing index beyond its size"); 139 | } 140 | - (void)testMutableCopyWorks { 141 | 142 | NSMutableArray *mutableCopy = [[self array] mutableCopy]; 143 | [mutableCopy removeObjectsInArray:_secondPage]; 144 | [mutableCopy removeObjectIdenticalTo:[NSNull null]]; 145 | 146 | XCTAssertEqualObjects(mutableCopy, _firstPage, @"Mutable copy doesn't match original"); 147 | 148 | } 149 | - (void)testPagedArrayActsAsNSArray { 150 | 151 | XCTAssert([[self array] isKindOfClass:[NSArray class]], @"Paged array isn't an NSArray"); 152 | } 153 | - (void)testArrayProxyActuallyContainsNullValues { 154 | 155 | for (id object in [self array]) { 156 | if ([object isKindOfClass:[NSNull class]]) { 157 | return; 158 | } 159 | } 160 | 161 | XCTFail(@"Array proxy didn't contain null values for empty pages"); 162 | } 163 | - (void)testArrayRealCountMatchesProxyCount { 164 | 165 | NSMutableArray *objects = [NSMutableArray array]; 166 | for (id object in [self array]) { 167 | [objects addObject:object]; 168 | } 169 | 170 | XCTAssertEqual(objects.count, [[self array] count], @"Real count doesn't match proxy count"); 171 | } 172 | - (void)testNumberOfPages { 173 | XCTAssertEqual([_pagedArray numberOfPages], ceil((CGFloat)MutablePagedArraySize/MutablePagedArrayObjectsPerPage), @"Wrong number of pages"); 174 | } 175 | @end 176 | 177 | #pragma mark Delegate tests 178 | @interface AWPagedArrayTestsTestingDelegate : NSObject { 179 | @public 180 | NSUInteger _accessIndex; 181 | id _returnedObject; 182 | } 183 | @end 184 | @implementation AWPagedArrayTestsTestingDelegate 185 | - (void)pagedArray:(AWPagedArray *)pagedArray willAccessIndex:(NSUInteger)index returnObject:(__autoreleasing id *)returnObject { 186 | _accessIndex = index; 187 | _returnedObject = *returnObject; 188 | } 189 | @end 190 | 191 | @interface AWPagedArrayTestsReturnObjectChangingDelegate : AWPagedArrayTestsTestingDelegate { 192 | @public 193 | id _objectToChangeReturnValueTo; 194 | } 195 | @end 196 | @implementation AWPagedArrayTestsReturnObjectChangingDelegate 197 | - (void)pagedArray:(AWPagedArray *)pagedArray willAccessIndex:(NSUInteger)index returnObject:(__autoreleasing id *)returnObject { 198 | *returnObject = @"test"; 199 | [super pagedArray:pagedArray willAccessIndex:index returnObject:returnObject]; 200 | } 201 | @end 202 | 203 | @implementation AWPagedArrayTests (DelegateTests) 204 | - (void)testReturnValuesAreCorrectForDelegateCall { 205 | 206 | AWPagedArrayTestsTestingDelegate *delegate = [AWPagedArrayTestsTestingDelegate new]; 207 | _pagedArray.delegate = delegate; 208 | 209 | NSUInteger index = 5; 210 | id object = [self array][index]; 211 | 212 | XCTAssertEqual(delegate->_accessIndex, index, @"Delegate's returned index doesn't match index accessed from array"); 213 | XCTAssertEqualObjects(delegate->_returnedObject, object, @"Delegate's returnObject doesn't match returned object from array"); 214 | } 215 | - (void)testDelegateCanChangeReturnedObject { 216 | 217 | AWPagedArrayTestsReturnObjectChangingDelegate *delegate = [AWPagedArrayTestsReturnObjectChangingDelegate new]; 218 | _pagedArray.delegate = delegate; 219 | id objectToChangeTo = @"test"; 220 | 221 | delegate->_objectToChangeReturnValueTo = objectToChangeTo; 222 | 223 | id returnedObject = [self array][0]; 224 | 225 | XCTAssertEqual(returnedObject, objectToChangeTo, @"Delegate couldn't replace object returned by array"); 226 | 227 | } 228 | @end 229 | 230 | 231 | -------------------------------------------------------------------------------- /Tests/AWPagedArrayTests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 93E0475F19056F750065900E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93E0475E19056F750065900E /* XCTest.framework */; }; 11 | 93E0476119056F750065900E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93E0476019056F750065900E /* Foundation.framework */; }; 12 | 93E0476319056F750065900E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93E0476219056F750065900E /* UIKit.framework */; }; 13 | 93E0476919056F750065900E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 93E0476719056F750065900E /* InfoPlist.strings */; }; 14 | 93E0477119056FC30065900E /* AWPagedArrayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 93E0477019056FC30065900E /* AWPagedArrayTests.m */; }; 15 | 93E0477419056FEE0065900E /* AWPagedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 93E0477319056FEE0065900E /* AWPagedArray.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 93E0475B19056F750065900E /* AWPagedArray tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AWPagedArray tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 93E0475E19056F750065900E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 21 | 93E0476019056F750065900E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 22 | 93E0476219056F750065900E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 23 | 93E0476619056F750065900E /* AWPagedArray tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AWPagedArray tests-Info.plist"; sourceTree = ""; }; 24 | 93E0476819056F750065900E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 25 | 93E0476C19056F750065900E /* AWPagedArray tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AWPagedArray tests-Prefix.pch"; sourceTree = ""; }; 26 | 93E0477019056FC30065900E /* AWPagedArrayTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AWPagedArrayTests.m; sourceTree = SOURCE_ROOT; }; 27 | 93E0477219056FEE0065900E /* AWPagedArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AWPagedArray.h; path = ../AWPagedArray.h; sourceTree = ""; }; 28 | 93E0477319056FEE0065900E /* AWPagedArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AWPagedArray.m; path = ../AWPagedArray.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 93E0475819056F750065900E /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | 93E0475F19056F750065900E /* XCTest.framework in Frameworks */, 37 | 93E0476319056F750065900E /* UIKit.framework in Frameworks */, 38 | 93E0476119056F750065900E /* Foundation.framework in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 93E0475019056F410065900E = { 46 | isa = PBXGroup; 47 | children = ( 48 | 93E0477219056FEE0065900E /* AWPagedArray.h */, 49 | 93E0477319056FEE0065900E /* AWPagedArray.m */, 50 | 93E0476419056F750065900E /* AWPagedArray tests */, 51 | 93E0475D19056F750065900E /* Frameworks */, 52 | 93E0475C19056F750065900E /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 93E0475C19056F750065900E /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 93E0475B19056F750065900E /* AWPagedArray tests.xctest */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 93E0475D19056F750065900E /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 93E0475E19056F750065900E /* XCTest.framework */, 68 | 93E0476019056F750065900E /* Foundation.framework */, 69 | 93E0476219056F750065900E /* UIKit.framework */, 70 | ); 71 | name = Frameworks; 72 | sourceTree = ""; 73 | }; 74 | 93E0476419056F750065900E /* AWPagedArray tests */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 93E0477019056FC30065900E /* AWPagedArrayTests.m */, 78 | 93E0476519056F750065900E /* Supporting Files */, 79 | ); 80 | path = "AWPagedArray tests"; 81 | sourceTree = ""; 82 | }; 83 | 93E0476519056F750065900E /* Supporting Files */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 93E0476619056F750065900E /* AWPagedArray tests-Info.plist */, 87 | 93E0476719056F750065900E /* InfoPlist.strings */, 88 | 93E0476C19056F750065900E /* AWPagedArray tests-Prefix.pch */, 89 | ); 90 | name = "Supporting Files"; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | 93E0475A19056F750065900E /* AWPagedArray tests */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = 93E0476D19056F750065900E /* Build configuration list for PBXNativeTarget "AWPagedArray tests" */; 99 | buildPhases = ( 100 | 93E0475719056F750065900E /* Sources */, 101 | 93E0475819056F750065900E /* Frameworks */, 102 | 93E0475919056F750065900E /* Resources */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = "AWPagedArray tests"; 109 | productName = "AWPagedArray tests"; 110 | productReference = 93E0475B19056F750065900E /* AWPagedArray tests.xctest */; 111 | productType = "com.apple.product-type.bundle.unit-test"; 112 | }; 113 | /* End PBXNativeTarget section */ 114 | 115 | /* Begin PBXProject section */ 116 | 93E0475119056F410065900E /* Project object */ = { 117 | isa = PBXProject; 118 | attributes = { 119 | LastUpgradeCheck = 0510; 120 | }; 121 | buildConfigurationList = 93E0475419056F410065900E /* Build configuration list for PBXProject "AWPagedArrayTests" */; 122 | compatibilityVersion = "Xcode 3.2"; 123 | developmentRegion = English; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | ); 128 | mainGroup = 93E0475019056F410065900E; 129 | productRefGroup = 93E0475C19056F750065900E /* Products */; 130 | projectDirPath = ""; 131 | projectRoot = ""; 132 | targets = ( 133 | 93E0475A19056F750065900E /* AWPagedArray tests */, 134 | ); 135 | }; 136 | /* End PBXProject section */ 137 | 138 | /* Begin PBXResourcesBuildPhase section */ 139 | 93E0475919056F750065900E /* Resources */ = { 140 | isa = PBXResourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 93E0476919056F750065900E /* InfoPlist.strings in Resources */, 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXResourcesBuildPhase section */ 148 | 149 | /* Begin PBXSourcesBuildPhase section */ 150 | 93E0475719056F750065900E /* Sources */ = { 151 | isa = PBXSourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 93E0477119056FC30065900E /* AWPagedArrayTests.m in Sources */, 155 | 93E0477419056FEE0065900E /* AWPagedArray.m in Sources */, 156 | ); 157 | runOnlyForDeploymentPostprocessing = 0; 158 | }; 159 | /* End PBXSourcesBuildPhase section */ 160 | 161 | /* Begin PBXVariantGroup section */ 162 | 93E0476719056F750065900E /* InfoPlist.strings */ = { 163 | isa = PBXVariantGroup; 164 | children = ( 165 | 93E0476819056F750065900E /* en */, 166 | ); 167 | name = InfoPlist.strings; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXVariantGroup section */ 171 | 172 | /* Begin XCBuildConfiguration section */ 173 | 93E0475519056F410065900E /* Debug */ = { 174 | isa = XCBuildConfiguration; 175 | buildSettings = { 176 | }; 177 | name = Debug; 178 | }; 179 | 93E0475619056F410065900E /* Release */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | }; 183 | name = Release; 184 | }; 185 | 93E0476E19056F750065900E /* Debug */ = { 186 | isa = XCBuildConfiguration; 187 | buildSettings = { 188 | ALWAYS_SEARCH_USER_PATHS = NO; 189 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 190 | CLANG_CXX_LIBRARY = "libc++"; 191 | CLANG_ENABLE_MODULES = YES; 192 | CLANG_ENABLE_OBJC_ARC = YES; 193 | CLANG_WARN_BOOL_CONVERSION = YES; 194 | CLANG_WARN_CONSTANT_CONVERSION = YES; 195 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 196 | CLANG_WARN_EMPTY_BODY = YES; 197 | CLANG_WARN_ENUM_CONVERSION = YES; 198 | CLANG_WARN_INT_CONVERSION = YES; 199 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = NO; 202 | FRAMEWORK_SEARCH_PATHS = ( 203 | "$(SDKROOT)/Developer/Library/Frameworks", 204 | "$(inherited)", 205 | "$(DEVELOPER_FRAMEWORKS_DIR)", 206 | ); 207 | GCC_C_LANGUAGE_STANDARD = gnu99; 208 | GCC_DYNAMIC_NO_PIC = NO; 209 | GCC_OPTIMIZATION_LEVEL = 0; 210 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 211 | GCC_PREFIX_HEADER = "AWPagedArray tests/AWPagedArray tests-Prefix.pch"; 212 | GCC_PREPROCESSOR_DEFINITIONS = ( 213 | "DEBUG=1", 214 | "$(inherited)", 215 | ); 216 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 217 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 218 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 219 | GCC_WARN_UNDECLARED_SELECTOR = YES; 220 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 221 | GCC_WARN_UNUSED_FUNCTION = YES; 222 | GCC_WARN_UNUSED_VARIABLE = YES; 223 | INFOPLIST_FILE = "AWPagedArray tests/AWPagedArray tests-Info.plist"; 224 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 225 | ONLY_ACTIVE_ARCH = YES; 226 | PRODUCT_NAME = "$(TARGET_NAME)"; 227 | SDKROOT = iphoneos; 228 | WRAPPER_EXTENSION = xctest; 229 | }; 230 | name = Debug; 231 | }; 232 | 93E0476F19056F750065900E /* Release */ = { 233 | isa = XCBuildConfiguration; 234 | buildSettings = { 235 | ALWAYS_SEARCH_USER_PATHS = NO; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_ENABLE_MODULES = YES; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | COPY_PHASE_STRIP = YES; 249 | ENABLE_NS_ASSERTIONS = NO; 250 | FRAMEWORK_SEARCH_PATHS = ( 251 | "$(SDKROOT)/Developer/Library/Frameworks", 252 | "$(inherited)", 253 | "$(DEVELOPER_FRAMEWORKS_DIR)", 254 | ); 255 | GCC_C_LANGUAGE_STANDARD = gnu99; 256 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 257 | GCC_PREFIX_HEADER = "AWPagedArray tests/AWPagedArray tests-Prefix.pch"; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | INFOPLIST_FILE = "AWPagedArray tests/AWPagedArray tests-Info.plist"; 265 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | SDKROOT = iphoneos; 268 | VALIDATE_PRODUCT = YES; 269 | WRAPPER_EXTENSION = xctest; 270 | }; 271 | name = Release; 272 | }; 273 | /* End XCBuildConfiguration section */ 274 | 275 | /* Begin XCConfigurationList section */ 276 | 93E0475419056F410065900E /* Build configuration list for PBXProject "AWPagedArrayTests" */ = { 277 | isa = XCConfigurationList; 278 | buildConfigurations = ( 279 | 93E0475519056F410065900E /* Debug */, 280 | 93E0475619056F410065900E /* Release */, 281 | ); 282 | defaultConfigurationIsVisible = 0; 283 | defaultConfigurationName = Release; 284 | }; 285 | 93E0476D19056F750065900E /* Build configuration list for PBXNativeTarget "AWPagedArray tests" */ = { 286 | isa = XCConfigurationList; 287 | buildConfigurations = ( 288 | 93E0476E19056F750065900E /* Debug */, 289 | 93E0476F19056F750065900E /* Release */, 290 | ); 291 | defaultConfigurationIsVisible = 0; 292 | defaultConfigurationName = Release; 293 | }; 294 | /* End XCConfigurationList section */ 295 | }; 296 | rootObject = 93E0475119056F410065900E /* Project object */; 297 | } 298 | -------------------------------------------------------------------------------- /Tests/AWPagedArrayTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | --------------------------------------------------------------------------------