├── .gitignore ├── Examples ├── Base.lproj │ └── Main.storyboard ├── Controller │ ├── CoreData │ │ ├── UsersCoreDataController.h │ │ ├── UsersCoreDataController.m │ │ ├── UsersRemoteCoreDataController.h │ │ └── UsersRemoteCoreDataController.m │ ├── DataStore │ │ ├── UsersDataStoreController.h │ │ ├── UsersDataStoreController.m │ │ ├── UsersRemoteDataStoreController.h │ │ └── UsersRemoteDataStoreController.m │ └── Views │ │ ├── UserCollectionCell.h │ │ ├── UserCollectionCell.m │ │ ├── UserTableCell.h │ │ └── UserTableCell.m ├── Data │ ├── Model.xcdatamodel │ │ └── contents │ ├── Store │ │ ├── CoreDataStore.h │ │ └── CoreDataStore.m │ ├── User.h │ └── User.m ├── Menu │ ├── MenuTableViewController.h │ └── MenuTableViewController.m ├── SessionManager │ ├── HTTPSessionManager.h │ └── HTTPSessionManager.m ├── Views │ ├── EmptyDataSetView.h │ └── EmptyDataSetView.m └── XLData.gif ├── LICENSE ├── Podfile ├── README.md ├── XLData.podspec ├── XLData.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── XLData.xcworkspace └── contents.xcworkspacedata ├── XLData ├── AppDelegate.h ├── AppDelegate.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── xl_appicon_120.png │ │ ├── xl_appicon_58.png │ │ └── xl_appicon_80.png │ ├── LaunchImage.launchimage │ │ ├── Contents.json │ │ └── xl_splash@2x.png │ ├── Posts_Selected.imageset │ │ ├── Contents.json │ │ └── Posts_Selected.png │ ├── Posts_Unselected.imageset │ │ ├── Contents.json │ │ └── Posts_Unselected.png │ ├── Users_Selected.imageset │ │ ├── Contents.json │ │ └── Users_Selected.png │ ├── Users_Unselected.imageset │ │ ├── Contents.json │ │ └── Users_Unselected.png │ ├── arrow.imageset │ │ ├── Contents.json │ │ └── arrow@2x.png │ └── default-avatar.imageset │ │ ├── Contents.json │ │ └── default-avatar@2x.png ├── Info.plist ├── XL │ ├── Core │ │ ├── Helpers │ │ │ ├── UIScrollView+SVInfiniteScrolling.h │ │ │ └── UIScrollView+SVInfiniteScrolling.m │ │ ├── Store │ │ │ ├── XLDataSectionStore.h │ │ │ ├── XLDataSectionStore.m │ │ │ ├── XLDataStore.h │ │ │ └── XLDataStore.m │ │ ├── View │ │ │ ├── XLNetworkStatusView.h │ │ │ ├── XLNetworkStatusView.m │ │ │ ├── XLSearchBar.h │ │ │ └── XLSearchBar.m │ │ ├── XLData.h │ │ └── XLData.m │ ├── CoreRemote │ │ └── Loader │ │ │ ├── XLDataLoader.h │ │ │ ├── XLDataLoader.m │ │ │ └── XLRemoteControllerDelegate.h │ ├── Local │ │ ├── CoreData │ │ │ ├── XLCoreDataController.h │ │ │ └── XLCoreDataController.m │ │ └── DataStore │ │ │ ├── XLDataStoreController.h │ │ │ └── XLDataStoreController.m │ └── Remote │ │ ├── CoreData │ │ ├── XLRemoteCoreDataController.h │ │ └── XLRemoteCoreDataController.m │ │ └── DataStore │ │ ├── XLRemoteDataStoreController.h │ │ └── XLRemoteDataStoreController.m └── main.m └── XLDataTests ├── Info.plist └── XLDataTests.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | Podfile.lock 23 | -------------------------------------------------------------------------------- /Examples/Controller/CoreData/UsersCoreDataController.h: -------------------------------------------------------------------------------- 1 | // 2 | // UsersCoreDataController 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLCoreDataController.h" 27 | 28 | @interface UsersCoreDataController : XLCoreDataController 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Examples/Controller/CoreData/UsersCoreDataController.m: -------------------------------------------------------------------------------- 1 | // 2 | // UsersRemoteCoreDataTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "UserCollectionCell.h" 27 | #import "UserTableCell.h" 28 | #import "XLData.h" 29 | #import "UsersCoreDataController.h" 30 | #import "CoreDataStore.h" 31 | #import 32 | 33 | @implementation UsersCoreDataController 34 | 35 | 36 | -(instancetype)initWithCoder:(NSCoder *)aDecoder 37 | { 38 | self = [super initWithCoder:aDecoder]; 39 | if (self){ 40 | self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:[User getFetchRequest] managedObjectContext:[CoreDataStore mainQueueContext] sectionNameKeyPath:nil cacheName:nil]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)viewDidLoad { 46 | [super viewDidLoad]; 47 | 48 | 49 | // Table View 50 | [self.tableView registerClass:[UserTableCell class] forCellReuseIdentifier:@"cell"]; 51 | self.tableView.allowsSelection = NO; 52 | self.tableView.tableFooterView = [UIView new]; 53 | 54 | // Collection View 55 | [self.collectionView registerClass:[UserCollectionCell class] forCellWithReuseIdentifier:@"cell"]; 56 | self.collectionView.allowsSelection = NO; 57 | UICollectionViewFlowLayout *collectionLayout = (id)self.collectionView.collectionViewLayout; 58 | collectionLayout.itemSize = CGSizeMake(100.0, 100.0); 59 | } 60 | 61 | #pragma mark - UITableViewDataSource 62 | 63 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 64 | { 65 | UserTableCell * cell = (UserTableCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 66 | 67 | User * dataItem = [self.fetchedResultsController objectAtIndexPath:indexPath]; 68 | 69 | cell.userName.text = [dataItem.userName copy]; 70 | [cell.userImage setImageWithURL:[NSURL URLWithString:[dataItem.userImageURL copy]] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 71 | return cell; 72 | } 73 | 74 | #pragma mark - UICollectionViewDataSource 75 | 76 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 77 | { 78 | UserCollectionCell * cell = (UserCollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; 79 | User * dataItem = [self.fetchedResultsController objectAtIndexPath:indexPath]; 80 | [cell.userImage setImageWithURL:[NSURL URLWithString:dataItem.userImageURL] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 81 | return cell; 82 | } 83 | 84 | #pragma mark - UITableViewDelegate 85 | 86 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 87 | { 88 | return 73.0f; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /Examples/Controller/CoreData/UsersRemoteCoreDataController.h: -------------------------------------------------------------------------------- 1 | // 2 | // UsersRemoteCoreDataTableViewController.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLRemoteCoreDataController.h" 27 | 28 | @interface UsersRemoteCoreDataController : XLRemoteCoreDataController 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Examples/Controller/CoreData/UsersRemoteCoreDataController.m: -------------------------------------------------------------------------------- 1 | // 2 | // UsersRemoteCoreDataTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "UserCollectionCell.h" 27 | #import "UserTableCell.h" 28 | #import "HTTPSessionManager.h" 29 | #import "XLData.h" 30 | #import "UsersRemoteCoreDataController.h" 31 | #import "CoreDataStore.h" 32 | #import 33 | #import "UsersRemoteDataStoreController.h" 34 | 35 | @interface UsersRemoteCoreDataController() 36 | 37 | @property (nonatomic, readonly) UsersRemoteDataStoreController * searchResultController; 38 | @property (nonatomic, readonly) UISearchController * searchController; 39 | 40 | @end 41 | 42 | @implementation UsersRemoteCoreDataController 43 | 44 | @synthesize searchController = _searchController; 45 | @synthesize searchResultController = _searchResultController; 46 | 47 | -(instancetype)initWithCoder:(NSCoder *)aDecoder 48 | { 49 | self = [super initWithCoder:aDecoder]; 50 | if (self){ 51 | self.dataLoader = [[XLDataLoader alloc] initWithURLString:@"/mobile/users.json"]; 52 | self.dataLoader.limit = 4; 53 | self.dataLoader.delegate = self; 54 | self.dataLoader.storeDelegate = self; 55 | 56 | self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:[User getFetchRequest] managedObjectContext:[CoreDataStore mainQueueContext] sectionNameKeyPath:nil cacheName:nil]; 57 | } 58 | return self; 59 | } 60 | 61 | - (void)viewDidLoad 62 | { 63 | // CollectionView 64 | [self.collectionView registerClass:[UserCollectionCell class] forCellWithReuseIdentifier:@"cell"]; 65 | self.collectionView.allowsSelection = NO; 66 | UICollectionViewFlowLayout *collectionLayout = (id)self.collectionView.collectionViewLayout; 67 | collectionLayout.itemSize = CGSizeMake(100.0, 100.0); 68 | 69 | [super viewDidLoad]; 70 | 71 | // TableView 72 | [self.tableView registerClass:[UserTableCell class] forCellReuseIdentifier:@"cell"]; 73 | self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; 74 | self.tableView.allowsSelection = NO; 75 | 76 | } 77 | 78 | #pragma mark - UITableViewDataSource 79 | 80 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 81 | { 82 | UserTableCell * cell = (UserTableCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 83 | 84 | User * dataItem = [self.fetchedResultsController objectAtIndexPath:indexPath]; 85 | 86 | cell.userName.text = [dataItem.userName copy]; 87 | [cell.userImage setImageWithURL:[NSURL URLWithString:[dataItem.userImageURL copy]] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 88 | return cell; 89 | } 90 | 91 | #pragma mark - UICollectionViewDataSource 92 | 93 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 94 | { 95 | UserCollectionCell * cell = (UserCollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; 96 | User * dataItem = [self.fetchedResultsController objectAtIndexPath:indexPath]; 97 | [cell.userImage setImageWithURL:[NSURL URLWithString:dataItem.userImageURL] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 98 | return cell; 99 | } 100 | 101 | #pragma mark - UITableViewDelegate 102 | 103 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 104 | { 105 | return 73.0f; 106 | } 107 | 108 | #pragma mark - XLDataLoaderDelegate 109 | 110 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader 111 | { 112 | return [HTTPSessionManager sharedClient]; 113 | } 114 | 115 | #pragma mark - XLDataLoaderStoreDelegate 116 | 117 | -(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler 118 | { 119 | NSUInteger offset = dataLoader.offset; 120 | NSUInteger limit = dataLoader.limit; 121 | NSArray * itemsArray = [dataLoader loadedDataItems]; 122 | // This flag indicates if there is more data to load 123 | [[CoreDataStore privateQueueContext] performBlock:^{ 124 | for (NSDictionary *item in itemsArray) { 125 | // Creates or updates the User a with the data that came from the server 126 | [User createOrUpdateWithServiceResult:item[@"user"] inContext:[CoreDataStore privateQueueContext]]; 127 | } 128 | 129 | // Remove outdated data 130 | NSFetchRequest * fetchRequest = [User getFetchRequest]; 131 | fetchRequest.fetchLimit = limit; 132 | fetchRequest.fetchOffset = offset; 133 | 134 | NSError *error; 135 | NSArray * oldObjects = [[CoreDataStore privateQueueContext] executeFetchRequest:fetchRequest error:&error]; 136 | NSArray * arrayToIterate = [oldObjects copy]; 137 | for (User *user in arrayToIterate){ 138 | NSArray *filteredArray = [itemsArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"user.id = %@" argumentArray:@[user.userId]]]; 139 | if (filteredArray.count == 0) { 140 | // This User no longer exists 141 | [[CoreDataStore privateQueueContext] deleteObject:user]; 142 | } 143 | } 144 | // 145 | [CoreDataStore savePrivateQueueContext]; 146 | completionHandler(); 147 | }]; 148 | } 149 | 150 | 151 | #pragma mark - Actions 152 | 153 | 154 | - (IBAction)searchTapped:(UIBarButtonItem *)sender 155 | { 156 | [self.searchController setActive:YES]; 157 | } 158 | 159 | #pragma mark - UISearchController 160 | 161 | -(UISearchController *)searchController 162 | { 163 | // UISearchController 164 | if (_searchController) return _searchController; 165 | self.definesPresentationContext = YES; 166 | _searchController = [[UISearchController alloc] initWithSearchResultsController:self.searchResultController]; 167 | _searchController.searchResultsUpdater = self.searchResultController; 168 | _searchController.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; 169 | [_searchController.searchBar sizeToFit]; 170 | _searchController.hidesNavigationBarDuringPresentation = NO; 171 | _searchController.delegate = self; 172 | return _searchController; 173 | } 174 | 175 | -(UsersRemoteDataStoreController *)searchResultController 176 | { 177 | if (_searchResultController) return _searchResultController; 178 | _searchResultController = [self.storyboard instantiateViewControllerWithIdentifier:@"SearchUsersTableViewController"]; 179 | _searchResultController.dataLoader.limit = 0; // no paging in search result 180 | _searchResultController.isSearchResultsController = YES; 181 | return _searchResultController; 182 | } 183 | 184 | #pragma mark - UISearchControllerDelegate 185 | 186 | -(void)presentSearchController:(UISearchController *)searchController 187 | { 188 | if ([searchController.delegate respondsToSelector:@selector(willPresentSearchController:)]){ 189 | [searchController.delegate willPresentSearchController:searchController]; 190 | } 191 | [self.navigationController presentViewController:searchController animated:YES completion:^{ 192 | if ([searchController.delegate respondsToSelector:@selector(didPresentSearchController:)]){ 193 | [searchController.delegate didPresentSearchController:searchController]; 194 | } 195 | }]; 196 | } 197 | 198 | 199 | - (void)didPresentSearchController:(UISearchController *)searchController 200 | { 201 | [searchController.searchBar becomeFirstResponder]; 202 | } 203 | 204 | @end 205 | -------------------------------------------------------------------------------- /Examples/Controller/DataStore/UsersDataStoreController.h: -------------------------------------------------------------------------------- 1 | // 2 | // UsersDataStoreController.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLDataStoreController.h" 27 | 28 | @interface UsersDataStoreController : XLDataStoreController 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Examples/Controller/DataStore/UsersDataStoreController.m: -------------------------------------------------------------------------------- 1 | // 2 | // UsersDataStoreController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import "UserTableCell.h" 28 | #import "UserCollectionCell.h" 29 | #import "UsersDataStoreController.h" 30 | 31 | @interface UsersDataStoreController () 32 | 33 | @end 34 | 35 | @implementation UsersDataStoreController 36 | { 37 | UIAlertController * __weak _alertController; 38 | } 39 | 40 | - (void)viewDidLoad { 41 | [super viewDidLoad]; 42 | 43 | // TableView 44 | [self.tableView registerClass:[UserTableCell class] forCellReuseIdentifier:@"cell"]; 45 | self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; 46 | self.tableView.allowsSelection = NO; 47 | 48 | // CollectionView 49 | [self.collectionView registerClass:[UserCollectionCell class] forCellWithReuseIdentifier:@"cell"]; 50 | self.collectionView.allowsSelection = NO; 51 | UICollectionViewFlowLayout *collectionLayout = (id)self.collectionView.collectionViewLayout; 52 | collectionLayout.itemSize = CGSizeMake(100.0, 100.0); 53 | 54 | // add default users 55 | [self.dataStore addDataItem:@{ 56 | @"imageURL": @"http://obscure-refuge-3149.herokuapp.com/images/Bart_Simpsons.png", 57 | @"name": @"Bart Simpsons" 58 | }]; 59 | [self.dataStore addDataItem:@{ 60 | @"imageURL": @"http://obscure-refuge-3149.herokuapp.com/images/Homer_Simpsons.png", 61 | @"name": @"Homer Simpsons" 62 | }]; 63 | [self.dataStore addDataItem:@{ 64 | @"imageURL": @"http://obscure-refuge-3149.herokuapp.com/images/Lisa_Simpsons.png", 65 | @"name": @"Lisa Simpsons" 66 | }]; 67 | [self.dataStore addDataItem:@{ 68 | @"imageURL": @"http://obscure-refuge-3149.herokuapp.com/images/Marge_Simpsons.png", 69 | @"name": @"Marge Simpsons" 70 | }]; 71 | 72 | } 73 | 74 | #pragma mark - UITableViewDataSource 75 | 76 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 77 | { 78 | UserTableCell * cell = (UserTableCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 79 | 80 | NSDictionary * dataItem = [self.dataStore dataAtIndexPath:indexPath]; 81 | 82 | cell.userName.text = [dataItem valueForKeyPath:@"name"]; 83 | [cell.userImage setImageWithURL:[NSURL URLWithString:[dataItem valueForKeyPath:@"imageURL"]] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 84 | return cell; 85 | } 86 | 87 | #pragma mark - UICollectionViewDataSource 88 | 89 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 90 | { 91 | UserCollectionCell * cell = (UserCollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; 92 | 93 | NSDictionary * dataItem = [self.dataStore dataAtIndexPath:indexPath]; 94 | 95 | [cell.userImage setImageWithURL:[NSURL URLWithString:[dataItem valueForKeyPath:@"imageURL"]] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 96 | return cell; 97 | } 98 | 99 | 100 | -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 101 | { 102 | XLDataSectionStore * section = [self.dataStore sectionAtIndex:indexPath.section]; 103 | [section removeDataItemAtIndex:indexPath.row]; 104 | if ([section numberOfItems] == 0){ 105 | [self.dataStore removeDataSection:section]; 106 | } 107 | } 108 | 109 | -(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath 110 | { 111 | return UITableViewCellEditingStyleDelete; 112 | } 113 | 114 | 115 | 116 | #pragma mark - UITableViewDelegate 117 | 118 | - (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 119 | { 120 | return 73.0f; 121 | } 122 | 123 | 124 | #pragma mark - Actions 125 | 126 | - (IBAction)addAction:(UIBarButtonItem *)sender 127 | { 128 | UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"Create a User" message:@"Please enter a name for the user.." preferredStyle:UIAlertControllerStyleAlert]; 129 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 130 | [textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; 131 | }]; 132 | [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 133 | [self dismissViewControllerAnimated:YES completion:nil]; 134 | }]]; 135 | UIAlertAction * action = [UIAlertAction actionWithTitle:@"Create" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 136 | NSString * text = [[alertController.textFields firstObject] text]; 137 | [self.dataStore addDataItem:@{ 138 | @"imageURL": @"", 139 | @"name": [text copy] 140 | }]; 141 | }]; 142 | action.enabled = NO; 143 | [alertController addAction:action]; 144 | [self presentViewController:alertController animated:YES completion:nil]; 145 | _alertController = alertController; 146 | } 147 | 148 | #pragma mark - Events 149 | 150 | -(void)textFieldDidChange:(UITextField *)textField 151 | { 152 | [_alertController.actions[1] setEnabled:(textField.text.length > 0)]; 153 | } 154 | 155 | 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /Examples/Controller/DataStore/UsersRemoteDataStoreController.h: -------------------------------------------------------------------------------- 1 | // 2 | // UsersTableViewController.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLRemoteDataStoreController.h" 27 | #import 28 | 29 | @interface UsersRemoteDataStoreController : XLRemoteDataStoreController 30 | 31 | 32 | @property BOOL isSearchResultsController; 33 | 34 | @end 35 | 36 | -------------------------------------------------------------------------------- /Examples/Controller/DataStore/UsersRemoteDataStoreController.m: -------------------------------------------------------------------------------- 1 | // 2 | // UsersTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "UserCollectionCell.h" 27 | #import "HTTPSessionManager.h" 28 | #import "UserTableCell.h" 29 | #import 30 | #import "UsersRemoteDataStoreController.h" 31 | 32 | 33 | @interface UsersRemoteDataStoreController () 34 | 35 | @property (nonatomic, readonly) UsersRemoteDataStoreController * searchResultController; 36 | @property (nonatomic, readonly) UISearchController * searchController; 37 | @property IBOutlet UIView * searchBarContainer; 38 | 39 | @end 40 | 41 | @implementation UsersRemoteDataStoreController 42 | 43 | @synthesize searchController = _searchController; 44 | @synthesize searchResultController = _searchResultController; 45 | 46 | - (instancetype)initWithCoder:(NSCoder *)coder 47 | { 48 | self = [super initWithCoder:coder]; 49 | if (self) { 50 | self.isSearchResultsController = NO; 51 | self.dataLoader = [[XLDataLoader alloc] initWithURLString:@"/mobile/users.json" offsetParamName:@"offset" limitParamName:@"limit" searchStringParamName:@"filter"]; 52 | self.dataLoader.delegate = self; 53 | self.dataLoader.storeDelegate = self; 54 | self.dataLoader.limit = 4; 55 | self.dataLoader.collectionKeyPath = @""; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)viewDidLoad { 61 | [super viewDidLoad]; 62 | 63 | // TableView 64 | [self.tableView registerClass:[UserTableCell class] forCellReuseIdentifier:@"cell"]; 65 | self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; 66 | self.tableView.allowsSelection = NO; 67 | 68 | // CollectionView 69 | [self.collectionView registerClass:[UserCollectionCell class] forCellWithReuseIdentifier:@"cell"]; 70 | self.collectionView.allowsSelection = NO; 71 | UICollectionViewFlowLayout *collectionLayout = (id)self.collectionView.collectionViewLayout; 72 | collectionLayout.itemSize = CGSizeMake(100.0, 100.0); 73 | 74 | if (!self.isSearchResultsController){ 75 | if (self.searchBarContainer){ 76 | [self.searchBarContainer addSubview:self.searchController.searchBar]; 77 | } 78 | else{ 79 | self.tableView.tableHeaderView = self.searchController.searchBar; 80 | } 81 | } 82 | } 83 | 84 | -(void)viewWillAppear:(BOOL)animated 85 | { 86 | [super viewWillAppear:animated]; 87 | [self.searchController.searchBar sizeToFit]; 88 | } 89 | 90 | 91 | #pragma mark - UITableViewDataSource 92 | 93 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 94 | { 95 | UserTableCell * cell = (UserTableCell *)[tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 96 | 97 | NSDictionary * dataItem = [self.dataStore dataAtIndexPath:indexPath]; 98 | 99 | cell.userName.text = [dataItem valueForKeyPath:@"user.name"]; 100 | [cell.userImage setImageWithURL:[NSURL URLWithString:[dataItem valueForKeyPath:@"user.imageURL"]] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 101 | return cell; 102 | } 103 | 104 | #pragma mark - UICollectionViewDataSource 105 | 106 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 107 | { 108 | UserCollectionCell * cell = (UserCollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; 109 | 110 | NSDictionary * dataItem = [self.dataStore dataAtIndexPath:indexPath]; 111 | 112 | [cell.userImage setImageWithURL:[NSURL URLWithString:[dataItem valueForKeyPath:@"user.imageURL"]] placeholderImage:[UIImage imageNamed:@"default-avatar"]]; 113 | return cell; 114 | } 115 | 116 | 117 | 118 | #pragma mark - UITableViewDelegate 119 | 120 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 121 | { 122 | return 73.0f; 123 | } 124 | 125 | #pragma mark - XLDataLoaderDelegate 126 | 127 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader 128 | { 129 | return [HTTPSessionManager sharedClient]; 130 | } 131 | 132 | #pragma mark - UISearchController 133 | 134 | -(UISearchController *)searchController 135 | { 136 | if (_searchController) return _searchController; 137 | 138 | self.definesPresentationContext = YES; 139 | _searchController = [[UISearchController alloc] initWithSearchResultsController:self.searchResultController]; 140 | _searchController.delegate = self; 141 | _searchController.searchResultsUpdater = self.searchResultController; 142 | _searchController.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; 143 | [_searchController.searchBar sizeToFit]; 144 | return _searchController; 145 | } 146 | 147 | 148 | -(UsersRemoteDataStoreController *)searchResultController 149 | { 150 | if (_searchResultController) return _searchResultController; 151 | _searchResultController = [self.storyboard instantiateViewControllerWithIdentifier:@"SearchUsersTableViewController"]; 152 | _searchResultController.dataLoader.limit = 0; // no paging in search result 153 | _searchResultController.isSearchResultsController = YES; 154 | return _searchResultController; 155 | } 156 | 157 | @end 158 | 159 | -------------------------------------------------------------------------------- /Examples/Controller/Views/UserCollectionCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // UserCollectionCell.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface UserCollectionCell : UICollectionViewCell 29 | 30 | @property (nonatomic) UIImageView * userImage; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Examples/Controller/Views/UserCollectionCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // UserCollectionCell.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "UserCollectionCell.h" 27 | 28 | @implementation UserCollectionCell 29 | 30 | @synthesize userImage = _userImage; 31 | 32 | 33 | -(instancetype)initWithFrame:(CGRect)frame 34 | { 35 | self = [super initWithFrame:frame]; 36 | if (self) { 37 | // Initialization code 38 | [self.contentView addSubview:self.userImage]; 39 | [self.contentView addConstraints:[self layoutConstraints]]; 40 | } 41 | return self; 42 | } 43 | 44 | #pragma mark - Views 45 | 46 | -(UIImageView *)userImage 47 | { 48 | if (_userImage) return _userImage; 49 | _userImage = [UIImageView new]; 50 | _userImage.translatesAutoresizingMaskIntoConstraints = NO; 51 | _userImage.layer.masksToBounds = YES; 52 | _userImage.layer.cornerRadius = 10.0f; 53 | return _userImage; 54 | } 55 | 56 | #pragma mark - Layout Constraints 57 | 58 | -(NSArray *)layoutConstraints{ 59 | 60 | NSMutableArray * result = [NSMutableArray array]; 61 | 62 | NSDictionary * views = @{ @"image": self.userImage}; 63 | 64 | 65 | NSDictionary *metrics = @{@"margin" :@12.0}; 66 | 67 | [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[image]-(margin)-|" 68 | options:NSLayoutFormatAlignAllTop 69 | metrics:metrics 70 | views:views]]; 71 | 72 | [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(margin)-[image]-(margin)-|" 73 | options:0 74 | metrics:metrics 75 | views:views]]; 76 | return result; 77 | } 78 | 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Examples/Controller/Views/UserTableCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // UserTableCell.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface UserTableCell : UITableViewCell 29 | 30 | @property (nonatomic) UIImageView * userImage; 31 | @property (nonatomic) UILabel * userName; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Examples/Controller/Views/UserTableCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // UserTableCell.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "UserTableCell.h" 27 | 28 | @implementation UserTableCell 29 | 30 | @synthesize userImage = _userImage; 31 | @synthesize userName = _userName; 32 | 33 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 34 | { 35 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 36 | if (self) { 37 | // Initialization code 38 | 39 | [self.contentView addSubview:self.userImage]; 40 | [self.contentView addSubview:self.userName]; 41 | 42 | [self.contentView addConstraints:[self layoutConstraints]]; 43 | } 44 | return self; 45 | } 46 | 47 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 48 | { 49 | [super setSelected:selected animated:animated]; 50 | 51 | // Configure the view for the selected state 52 | } 53 | 54 | 55 | #pragma mark - Views 56 | 57 | -(UIImageView *)userImage 58 | { 59 | if (_userImage) return _userImage; 60 | _userImage = [UIImageView new]; 61 | _userImage.translatesAutoresizingMaskIntoConstraints = NO; 62 | _userImage.layer.masksToBounds = YES; 63 | _userImage.layer.cornerRadius = 10.0f; 64 | return _userImage; 65 | } 66 | 67 | -(UILabel *)userName 68 | { 69 | if (_userName) return _userName; 70 | _userName = [UILabel new]; 71 | _userName.translatesAutoresizingMaskIntoConstraints = NO; 72 | _userName.font = [UIFont systemFontOfSize:15.0]; 73 | 74 | return _userName; 75 | } 76 | 77 | #pragma mark - Layout Constraints 78 | 79 | -(NSArray *)layoutConstraints{ 80 | 81 | NSMutableArray * result = [NSMutableArray array]; 82 | 83 | NSDictionary * views = @{ @"image": self.userImage, 84 | @"name": self.userName}; 85 | 86 | 87 | NSDictionary *metrics = @{@"imgSize":@50.0, 88 | @"margin" :@12.0}; 89 | 90 | [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(margin)-[image(imgSize)]-[name]" 91 | options:NSLayoutFormatAlignAllTop 92 | metrics:metrics 93 | views:views]]; 94 | 95 | [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(margin)-[image(imgSize)]" 96 | options:0 97 | metrics:metrics 98 | views:views]]; 99 | return result; 100 | } 101 | 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Examples/Data/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/Data/Store/CoreDataStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStore.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "User.h" 27 | #import 28 | #import 29 | 30 | @interface CoreDataStore : NSObject 31 | 32 | +(id)defaultStore; 33 | 34 | + (NSManagedObjectContext *)mainQueueContext; 35 | + (NSManagedObjectContext *)privateQueueContext; 36 | 37 | + (void)savePrivateQueueContext; 38 | + (void)saveMainQueueContext; 39 | 40 | + (NSManagedObjectID *)managedObjectIDFromString:(NSString *)managedObjectIDString; 41 | 42 | @end 43 | 44 | @interface NSManagedObject (Additions) 45 | 46 | +(instancetype)findFirstByAttribute:(NSString *)attribute withValue:(id)value inContext:(NSManagedObjectContext *)context; 47 | +(NSFetchRequest*)fetchRequest; 48 | +(instancetype)insert:(NSManagedObjectContext *)context; 49 | 50 | @end 51 | 52 | 53 | @interface User (Additions) 54 | 55 | + (User *)createOrUpdateWithServiceResult:(NSDictionary *)data inContext:(NSManagedObjectContext *)context; 56 | 57 | + (UIImage *)defaultProfileImage; 58 | 59 | + (NSPredicate *)getPredicateBySearchInput:(NSString *)search; 60 | 61 | + (NSFetchRequest *)getFetchRequest; 62 | 63 | + (NSFetchRequest *)getFetchRequestBySearchInput:(NSString *)search; 64 | 65 | @end 66 | 67 | -------------------------------------------------------------------------------- /Examples/Data/Store/CoreDataStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStore.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import UIKit; 27 | #import "CoreDataStore.h" 28 | 29 | @interface CoreDataStore () 30 | 31 | @property (strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 32 | @property (strong, nonatomic) NSManagedObjectModel *managedObjectModel; 33 | 34 | @property (strong, nonatomic) NSManagedObjectContext *mainQueueContext; 35 | @property (strong, nonatomic) NSManagedObjectContext *privateQueueContext; 36 | 37 | @end 38 | 39 | @implementation CoreDataStore 40 | 41 | 42 | + (instancetype)defaultStore { 43 | static CoreDataStore *_defaultStore = nil; 44 | static dispatch_once_t onceToken; 45 | dispatch_once(&onceToken, ^{ 46 | _defaultStore = [[self alloc] init]; 47 | }); 48 | return _defaultStore; 49 | } 50 | 51 | #pragma mark - Singleton Access 52 | 53 | + (NSManagedObjectContext *)mainQueueContext 54 | { 55 | return [[self defaultStore] mainQueueContext]; 56 | } 57 | 58 | + (NSManagedObjectContext *)privateQueueContext 59 | { 60 | return [[self defaultStore] privateQueueContext]; 61 | } 62 | 63 | +(void)savePrivateQueueContext 64 | { 65 | NSError * error; 66 | [[self privateQueueContext] save:&error]; 67 | NSAssert(!error, [error localizedDescription]); 68 | } 69 | 70 | + (void)saveMainQueueContext 71 | { 72 | NSError * error; 73 | [[self mainQueueContext] save:&error]; 74 | NSAssert(!error, [error localizedDescription]); 75 | } 76 | 77 | + (NSManagedObjectID *)managedObjectIDFromString:(NSString *)managedObjectIDString 78 | { 79 | return [[[self defaultStore] persistentStoreCoordinator] managedObjectIDForURIRepresentation:[NSURL URLWithString:managedObjectIDString]]; 80 | } 81 | 82 | #pragma mark - Lifecycle 83 | 84 | - (id)init 85 | { 86 | self = [super init]; 87 | if (self) { 88 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSavePrivateQueueContext:)name:NSManagedObjectContextDidSaveNotification object:[self privateQueueContext]]; 89 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSaveMainQueueContext:) name:NSManagedObjectContextDidSaveNotification object:[self mainQueueContext]]; 90 | } 91 | return self; 92 | } 93 | 94 | - (void)dealloc 95 | { 96 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 97 | } 98 | 99 | #pragma mark - Notifications 100 | 101 | - (void)contextDidSavePrivateQueueContext:(NSNotification *)notification 102 | { 103 | @synchronized(self) { 104 | [self.mainQueueContext performBlock:^{ 105 | for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) { 106 | [[self.mainQueueContext objectWithID:[object objectID]] willAccessValueForKey:nil]; 107 | } 108 | [self.mainQueueContext mergeChangesFromContextDidSaveNotification:notification]; 109 | }]; 110 | } 111 | } 112 | 113 | - (void)contextDidSaveMainQueueContext:(NSNotification *)notification 114 | { 115 | @synchronized(self) { 116 | [self.privateQueueContext performBlock:^{ 117 | [self.privateQueueContext mergeChangesFromContextDidSaveNotification:notification]; 118 | }]; 119 | } 120 | } 121 | 122 | #pragma mark - Getters 123 | 124 | - (NSManagedObjectContext *)mainQueueContext 125 | { 126 | if (!_mainQueueContext) { 127 | _mainQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 128 | _mainQueueContext.persistentStoreCoordinator = self.persistentStoreCoordinator; 129 | } 130 | 131 | return _mainQueueContext; 132 | } 133 | 134 | - (NSManagedObjectContext *)privateQueueContext 135 | { 136 | if (!_privateQueueContext) { 137 | _privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 138 | _privateQueueContext.persistentStoreCoordinator = self.persistentStoreCoordinator; 139 | } 140 | 141 | return _privateQueueContext; 142 | } 143 | 144 | #pragma mark - Stack Setup 145 | 146 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 147 | { 148 | if (!_persistentStoreCoordinator) { 149 | _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; 150 | NSError *error = nil; 151 | 152 | if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self persistentStoreURL] options:[self persistentStoreOptions] error:&error]) { 153 | NSLog(@"Error adding persistent store. %@, %@", error, error.userInfo); 154 | } 155 | } 156 | 157 | return _persistentStoreCoordinator; 158 | } 159 | 160 | - (NSManagedObjectModel *)managedObjectModel 161 | { 162 | if (!_managedObjectModel) { 163 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"mom"]; 164 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 165 | } 166 | 167 | return _managedObjectModel; 168 | } 169 | 170 | - (NSURL *)persistentStoreURL 171 | { 172 | return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Model.sqlite"]; 173 | } 174 | 175 | - (NSDictionary *)persistentStoreOptions 176 | { 177 | return @{NSInferMappingModelAutomaticallyOption: @YES, NSMigratePersistentStoresAutomaticallyOption: @YES, NSSQLitePragmasOption: @{@"synchronous": @"OFF"}}; 178 | } 179 | 180 | #pragma mark - Application's Documents directory 181 | 182 | // Returns the URL to the application's Documents directory. 183 | - (NSURL *)applicationDocumentsDirectory 184 | { 185 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 186 | } 187 | 188 | 189 | @end 190 | 191 | 192 | @implementation NSManagedObject (Additions) 193 | 194 | 195 | +(instancetype)findFirstByAttribute:(NSString *)attribute withValue:(id)value inContext:(NSManagedObjectContext *)context 196 | { 197 | NSString * predicateStr = [NSString stringWithFormat:@"%@ = %%@", attribute]; 198 | NSPredicate * searchByAttValue = [NSPredicate predicateWithFormat:predicateStr argumentArray:@[value]]; 199 | NSFetchRequest * fetchRequest = [self fetchRequest]; 200 | fetchRequest.predicate = searchByAttValue; 201 | fetchRequest.fetchLimit = 1; 202 | NSArray *result = [context executeFetchRequest:fetchRequest error:nil]; 203 | return [result firstObject]; 204 | } 205 | 206 | +(NSFetchRequest*)fetchRequest 207 | { 208 | return [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass(self)]; 209 | } 210 | 211 | +(NSEntityDescription*)entityDescriptor:(NSManagedObjectContext *)context 212 | { 213 | return [NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:context]; 214 | } 215 | 216 | +(instancetype)insert:(NSManagedObjectContext *)context 217 | { 218 | return [[NSManagedObject alloc] initWithEntity:[self entityDescriptor:context] insertIntoManagedObjectContext:context]; 219 | } 220 | 221 | @end 222 | 223 | 224 | @implementation User (Additions) 225 | 226 | + (User *)createOrUpdateWithServiceResult:(NSDictionary *)data inContext:(NSManagedObjectContext *)context; 227 | { 228 | User *user = [User findFirstByAttribute:@"userId" withValue:data[@"id"] inContext:context]; 229 | if (!user){ 230 | user = [User insert:context]; 231 | } 232 | user.userId = data[@"id"]; 233 | user.userImageURL = data[@"imageURL"]; 234 | user.userName = data[@"name"]; 235 | return user; 236 | } 237 | 238 | + (UIImage *)defaultProfileImage 239 | { 240 | return [UIImage imageNamed:@"default-avatar"]; 241 | } 242 | 243 | + (NSPredicate *)getPredicateBySearchInput:(NSString *)search { 244 | 245 | if (search && ![search isEqualToString:@""]){ 246 | return [NSPredicate predicateWithFormat:@"userName CONTAINS[cd] %@" , search]; 247 | } 248 | return nil; 249 | } 250 | 251 | + (NSFetchRequest *)getFetchRequest 252 | { 253 | return [User getFetchRequestBySearchInput:nil]; 254 | } 255 | 256 | + (NSFetchRequest *)getFetchRequestBySearchInput:(NSString *)search { 257 | NSFetchRequest * fetchRequest = [User fetchRequest]; 258 | fetchRequest.predicate = [User getPredicateBySearchInput:search]; 259 | fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"userName" ascending:YES selector:@selector(caseInsensitiveCompare:)]]; 260 | return fetchRequest; 261 | } 262 | 263 | @end 264 | -------------------------------------------------------------------------------- /Examples/Data/User.h: -------------------------------------------------------------------------------- 1 | // 2 | // User.h 3 | // XLData 4 | // 5 | // Created by Martin Barreto on 5/24/15. 6 | // Copyright (c) 2015 Xmartlabs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface User : NSManagedObject 14 | 15 | @property (nonatomic, retain) NSNumber * userId; 16 | @property (nonatomic, retain) NSString * userImageURL; 17 | @property (nonatomic, retain) NSString * userName; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/Data/User.m: -------------------------------------------------------------------------------- 1 | // 2 | // User.m 3 | // XLData 4 | // 5 | // Created by Martin Barreto on 5/24/15. 6 | // Copyright (c) 2015 Xmartlabs. All rights reserved. 7 | // 8 | 9 | #import "User.h" 10 | 11 | 12 | @implementation User 13 | 14 | @dynamic userId; 15 | @dynamic userImageURL; 16 | @dynamic userName; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Examples/Menu/MenuTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLDataStoreController.h" 27 | 28 | @interface MenuTableViewController : XLDataStoreController 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Examples/Menu/MenuTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLData.h" 27 | #import "MenuTableViewController.h" 28 | 29 | @interface MenuTableViewController () 30 | 31 | @end 32 | 33 | @implementation MenuTableViewController 34 | 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; 38 | 39 | 40 | [self.dataStore addDataSection:[XLDataSectionStore dataSectionStoreWithTitle:@"DATA STORE"]]; 41 | [self.dataStore addDataItem:@"Data Store TableView"]; 42 | [self.dataStore addDataItem:@"Data Store CollectionView"]; 43 | 44 | [self.dataStore addDataSection:[XLDataSectionStore dataSectionStoreWithTitle:@"REMOTE DATA STORE"]]; 45 | [self.dataStore addDataItem:@"Remote Data Store TableView"]; 46 | [self.dataStore addDataItem:@"Remote Data Store CollectionView"]; 47 | 48 | [self.dataStore addDataSection:[XLDataSectionStore dataSectionStoreWithTitle:@"CORE DATA"]]; 49 | [self.dataStore addDataItem:@"Core Data TableView"]; 50 | [self.dataStore addDataItem:@"Core Data CollectionView"]; 51 | 52 | [self.dataStore addDataSection:[XLDataSectionStore dataSectionStoreWithTitle:@"REMOTE CORE DATA"]]; 53 | [self.dataStore addDataItem:@"Remote Core Data TableView"]; 54 | [self.dataStore addDataItem:@"Remote Core Data CollectionView"]; 55 | } 56 | 57 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 58 | { 59 | UITableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 60 | cell.textLabel.text = [self.dataStore dataAtIndexPath:indexPath]; 61 | return cell; 62 | } 63 | 64 | 65 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 66 | { 67 | NSString * cellText = [self.dataStore dataAtIndexPath:indexPath]; 68 | [self performSegueWithIdentifier:cellText sender:nil]; 69 | } 70 | 71 | -(UITableViewRowAnimation)tableViewAnimationForDataStoreSectionChange:(XLDataStoreChangeType)dataStoreChange 72 | { 73 | return UITableViewRowAnimationNone; 74 | } 75 | 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Examples/SessionManager/HTTPSessionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPSessionManager.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "AFHTTPSessionManager.h" 27 | 28 | @interface HTTPSessionManager : AFHTTPSessionManager 29 | 30 | + (HTTPSessionManager *)sharedClient; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Examples/SessionManager/HTTPSessionManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPSessionManager.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "HTTPSessionManager.h" 27 | 28 | @implementation HTTPSessionManager 29 | 30 | //// Server Base URL 31 | static NSString * const APIBaseURLString = @"http://obscure-refuge-3149.herokuapp.com"; 32 | 33 | + (instancetype)sharedClient { 34 | static HTTPSessionManager *_sharedClient = nil; 35 | static dispatch_once_t onceToken; 36 | dispatch_once(&onceToken, ^{ 37 | _sharedClient = [[HTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:APIBaseURLString]]; 38 | [_sharedClient.reachabilityManager startMonitoring]; 39 | _sharedClient.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; 40 | }); 41 | 42 | return _sharedClient; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Examples/Views/EmptyDataSetView.h: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyDataSetView.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | #import 26 | 27 | @interface EmptyDataSetView : UIView 28 | 29 | @property (nonatomic, readonly) UILabel * textLabel; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Examples/Views/EmptyDataSetView.m: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyDataetView.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | #import "EmptyDataSetView.h" 26 | 27 | @implementation EmptyDataSetView 28 | 29 | @synthesize textLabel = _textLabel; 30 | 31 | - (id)initWithFrame:(CGRect)frame 32 | { 33 | self = [super initWithFrame:frame]; 34 | if (self) { 35 | // Initialization code 36 | [self addSubview:self.textLabel]; 37 | 38 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; 39 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]]; 40 | } 41 | return self; 42 | } 43 | 44 | #pragma mark - Properties 45 | 46 | -(UILabel *)textLabel 47 | { 48 | if (_textLabel) return _textLabel; 49 | _textLabel = [[UILabel alloc] init]; 50 | _textLabel.translatesAutoresizingMaskIntoConstraints = NO; 51 | return _textLabel; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Examples/XLData.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/Examples/XLData.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Xmartlabs ( http://xmartlabs.com ) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | 3 | ####### 4 | pod 'XLData', :path => '.' 5 | ####### same effect as 6 | #pod 'XLData/DataStore', :path => '.' 7 | #pod 'XLData/CoreData', :path => '.' 8 | #pod 'XLData/RemoteCoreData', :path => '.' 9 | #pod 'XLData/RemoteDataStore', :path => '.' 10 | ####### 11 | pod 'AFNetworkActivityLogger', '~>2.0' 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | XLData 2 | --------------- 3 | 4 | By [XMARTLABS](http://xmartlabs.com). 5 | 6 | [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/xmartlabs/XLData/blob/master/LICENSE) 7 | [![license](https://img.shields.io/badge/pod-2.0.0-blue.svg)](https://github.com/xmartlabs/XLData/releases) 8 | 9 | XLData provides an elegant, flexible and concise way to load, synchronize and show data sets into table and collection views. 10 | 11 | Purpose 12 | -------------- 13 | 14 | You probably implement table/collection view controllers on a daily basis since a large part of an iOS app involves loading, synchronizing and showing data in these views. 15 | 16 | The data you need to show might be stored in memory, a Core Data db, and often needs to be fetched from a json API endpoint. 17 | 18 | Apart from loading, synchronizing and showing data sets into a UITableView/UICollectionView it also handles the empty state view and "no internet connection" view by showing or hiding a proper view when the table becomes empty/no-empty or internet reachability changes respectively. 19 | 20 | XLData supports static and dynamic data sets. In order to support dynamic data sets efficiently it provides support for pagination and search, as well as a super simple way to fetch data from an API endpoint using AFNetworking. 21 | 22 | Whatever your source of data, XLData provides an elegant and concise solution to handle all challenges introduced above with minimal effort. We are sure you will love XLData! 23 | 24 | ![Gif Example](Examples/XLData.gif) 25 | 26 | What XLData does 27 | ------------------- 28 | 29 | * Show a data set stored in memory using a UITableView or a UICollectionView (works with both in-memory and Core Data data sets). 30 | * Keeps track of data set changes on runtime to update the UITableView/UICollectionView on the fly (works with both in-memory and Core Data data sets). 31 | * Provides a high-level abstraction to fetch a data set from a json API endpoint. You can check additional details [here](#xldataloader). 32 | * Provides support for pagination and filtering. 33 | * Provides an in-memory mechanism to store data sets. You can check additional details [here](#xldatastore). 34 | * Manage empty state view showing a customizable empty state view when the data set is empty. 35 | * Manage no internet connection view showing it when internet connection is not reachable. 36 | 37 | Usage 38 | ------------------- 39 | 40 | `XLData` supports different scenarios from in-memory to core data data sets, it is also able to synchronize the data set with the json results of an API endpoint. In this section we'll briefly explain how it can be used in typical scenarios. For a more detailed explanation please take a look at the [Examples](Examples) folder. 41 | 42 | 43 | #### Data Store table/collection view controller 44 | 45 | 1 - Create a view controller object that extends from `XLDataStoreController`. 46 | 47 | 2 - Add sections (`XLDataSectionStore` objects) and then items (any object) to a section. Items can be of any type. 48 | ```obj-c 49 | [self.dataStore addDataSection:[XLDataSectionStore dataSectionStoreWithTitle:@"Example"]]; 50 | [self.dataStore addDataItem:@{@"title": "Row title 1"}]; 51 | [self.dataStore addDataItem:@{@"title": "Row title 2"}]; 52 | ``` 53 | 54 | 3 - Provide either the table or collection view cell: 55 | ```obj-c 56 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 57 | { 58 | UITableViewCell * cell = .... 59 | // retrieve data set item at indexPath 60 | NSDictionary * dataItem = [self.dataStore dataAtIndexPath:indexPath]; 61 | // configure cell 62 | ... 63 | return cell; 64 | } 65 | ``` 66 | 67 | Any changes made to the data store will be automatically reflected in the table/collection view. ;) 68 | 69 | For further details on how to implement this kind of view controller take a look at [UsersDataStoreController.m](/Examples/Controller/DataStore/UsersDataStoreController.m) file. You can see it in action by running the Demo app and tapping the cell "Data Store TableView" or "Data Store CollectionView". 70 | 71 | 72 | #### Data Store table/collection view controller sync with remote json endpoint 73 | 74 | 1 - Create a view controller object that extends from `XLRemoteDataStoreController`. 75 | 76 | 2 - Set up the `dataLoader` property by following these steps: 77 | ```obj-c 78 | // instantiate a XLDataLoader instance and set `dataLoader` property with it. We can use a convenient initializer to configure offset, limit and filter query parameter names. 79 | self.dataLoader = [[XLDataLoader alloc] initWithURLString:@"/mobile/users.json" 80 | offsetParamName:@"offset" 81 | limitParamName:@"limit" 82 | searchStringParamName:@"filter"]; 83 | // assign the view controller as the delegate of the data loader 84 | self.dataLoader.delegate = self; 85 | // assign the view controller as the storeDelegate of the data loader 86 | self.dataLoader.storeDelegate = self; 87 | // configure how many items we want to fetch per request 88 | self.dataLoader.limit = 4; 89 | // configure the dataset path within the json result. In this example the dataset is in the json result's root. 90 | self.dataLoader.collectionKeyPath = @""; 91 | // configure any additional query parameter by providing key/value using the `parameter` property. 92 | self.dataLoader.parameters[@"paramName1"] = paramValue1; 93 | self.dataLoader.parameters[@"paramName2"] = paramValue2; 94 | ``` 95 | 96 | 3 - Provide a `AFHTTPSessionManager` by implementing the following `XLDataLoaderDelegate` method: 97 | ```obj-c 98 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader 99 | ``` 100 | 101 | 4 - Return the cell: 102 | ```obj-c 103 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 104 | { 105 | UITableViewCell * cell = .... 106 | // retrieve data set item at indexPath 107 | NSDictionary * dataItem = [self.dataStore dataAtIndexPath:indexPath]; 108 | // configure cell 109 | ... 110 | return cell; 111 | } 112 | ``` 113 | 114 | XLData will automatically update the dataStore using the data fetched by its `XLDataLoader` object and the current property values such as offset and limit. 115 | 116 | 5 - **Optional** Override a method to update the data store differently. By default XLData appends the fetched items to the last section of the data store. 117 | ```obj-c 118 | -(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler 119 | ``` 120 | You must call at the end `completionHandler` block and must not call super implementation in case you decide to implement it. 121 | 122 | The default implementation for the method above provided by `XLRemoteDataStoreController` is: 123 | 124 | ```obj-c 125 | [[self.dataStore lastSection] addDataItems:dataLoader.loadedDataItems fromIndex:dataLoader.offset]; 126 | completionHandler(); 127 | ``` 128 | 129 | For further details on how to implement this kind of view controller take a look at [UsersRemoteDataStoreController.m](/Examples/Controller/DataStore/UsersRemoteDataStoreController.m) file. You can see it in action by running the Demo app and tapping the cell "Remote Data Store TableView" or "Remote Data Store CollectionView". 130 | 131 | ####Core Data table/collection view controller 132 | 133 | 1 - Create a view controller object that extends from `XLCoreDataController` 134 | 135 | 2 - Set up `fetchedResultsController` property: 136 | ```obj-c 137 | self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:[User getFetchRequest] managedObjectContext:[CoreDataStore mainQueueContext] sectionNameKeyPath:nil cacheName:nil]; 138 | ``` 139 | 140 | 3 - Return the cell: 141 | ```obj-c 142 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 143 | { 144 | UITableViewCell * cell = ... 145 | // retrieve data set item at indexPath 146 | NSManagedObject * dataItem = [self.fetchedResultsController objectAtIndexPath:indexPath]; 147 | // configure cell 148 | ... 149 | return cell; 150 | } 151 | ``` 152 | 153 | For further details on how to implement this kind of view controller take a look at [UsersCoreDataController.m](/Examples/Controller/CoreData/UsersCoreDataController.m) file. You can see it in action by running the Demo app and tapping the cell "Core Data TableView" or "Core Data CollectionView". 154 | 155 | ####Core Data table/collection view controller sync with remote json endpoint 156 | 157 | 1 - Create a view controller object that extends from `XLRemoteCoreDataController`. 158 | 159 | 2 - Set up the `dataLoader` property by following these steps: 160 | ```obj-c 161 | // instantiate a `XLDataLoader` instance and set `dataLoader` property with it. We can use a convenient initializer to configure offset, limit and filter query parameter names. 162 | self.dataLoader = [[XLDataLoader alloc] initWithURLString:@"/mobile/users.json" 163 | offsetParamName:@"offset" 164 | limitParamName:@"limit" 165 | searchStringParamName:@"filter"]; 166 | // assign the view controller as the delegate of the data loader 167 | self.dataLoader.delegate = self; 168 | // assign the view controller as the storeDelegate of the data loader 169 | self.dataLoader.storeDelegate = self; 170 | // configure how many items we want to fetch per request 171 | self.dataLoader.limit = 4; 172 | // configure the dataset path within the json result. In this example the dataset is in the json result's root. 173 | self.dataLoader.collectionKeyPath = @""; 174 | // configure any additional query parameter by providing key/value using the `parameter` property. 175 | self.dataLoader.parameters[@"paramName1"] = paramValue1; 176 | self.dataLoader.parameters[@"paramName2"] = paramValue2; 177 | ``` 178 | 179 | 3 - Provide a `AFHTTPSessionManager` by implementing the following `XLDataLoaderDelegate` method: 180 | ```obj-c 181 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader 182 | ``` 183 | 184 | 4 - Return the cell: 185 | ```obj-c 186 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 187 | { 188 | UITableViewCell * cell = ... 189 | // retrieve data set item at indexPath 190 | NSManagedObject * dataItem = [self.fetchedResultsController objectAtIndexPath:indexPath]; 191 | // configure cell 192 | ... 193 | return cell; 194 | } 195 | ``` 196 | 197 | 5 - You must override the following method in order to synchronize the dataset fetched by the DataLoader with the Core Data dataset. 198 | ```obj-c 199 | -(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler 200 | ``` 201 | 202 | Make sure you invoke completionHandler block from within `-(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler` when you are done with the core data sync (insert, update, delete NSManagedObjects). 203 | 204 | For further details on how to implement this kind of view controller take a look at [UsersRemoteCoreDataController.m](/Examples/Controller/CoreData/UsersRemoteCoreDataController.m) file. You can see it in action by running the Demo app and tapping the cell "Remote Core Data TableView" or "Remote Core Data CollectionView". 205 | 206 | 207 | ###Customization 208 | 209 | #### How to set up an empty state view 210 | 211 | `XLDataStoreController` and `XLCoreDataController` expose `emptyDataSetView` property. We can set up a UIView either through storyboard (IBOutlet) or programatically. 212 | 213 | #### How to set up a networking status view 214 | 215 | `XLRemoteDataStoreController` and `XLRemoteCoreDataController` expose 'networkStatusView' property. We can set up a UIView either through storyboard (IBOutlet) or programatically. 216 | 217 | Take a look at the examples to see how those properties work. 218 | 219 | How it works? 220 | ------------------- 221 | 222 | In this section we will introduce two relevant abstractions that will help you have a better understanding of the library design and behaviour. 223 | 224 | ###XLDataStore 225 | 226 | As you may already know, XLData is able to show a data set that is stored in-memory or in a Core Data db. 227 | 228 | In order to show a Core Data data set we use the well-known class `NSFetchedResultsController`, making `XLCoreDataController` conforms to the `NSFetchedResultsControllerDelegate` protocol and get notified each time a new `NSManagedObjectModel` instance is added, deleted or modified within a `NSManagedObjectContext`. 229 | 230 | Since `NSFetchedResultsControllerDelegate` works well and is popular among the community, we designed a similar pattern for in-memory data sets. 231 | 232 | In order to accomplish that, `XLDataStoreController` store the in-memory data sets within a `XLDataStore` instance and conforms to the `XLDataStoreDelegate` to get notified whenever the in-memory data set is modified either by adding/removing a new item or section (`XLDataStoreSection`). 233 | 234 | `XLDataStoreDelegate` is analog to `NSFetchedResultsControllerDelegate` but it works with in-memory data sets. `XLDataStore` contains `XLDataStoreSection` instances which are able to store any kind of object. Cool right? 235 | 236 | BTW `XLDataStore` and its delegate are encapsulated and can be used independently and not just alongside `XLData` controllers. 237 | 238 | ###XLDataLoader 239 | 240 | As we have explained previously, `XLData` is able to fetch data from an API endpoint. 241 | To do so we define `XLDataLoader` which exposes several properties in order to support any kind of json format. 242 | 243 | This are some properties exposed by `XLDataLoader`: 244 | 245 | ```obj-c 246 | @property NSUInteger offset; 247 | @property NSUInteger limit; 248 | @property NSString * searchString; 249 | @property (nonatomic) NSMutableDictionary * parameters; 250 | @property (nonatomic) NSString * collectionKeyPath; 251 | @property (readonly) NSDictionary * loadedData; 252 | @property (readonly) NSArray * loadedDataItems; 253 | ``` 254 | 255 | Apart of these properties, `XLDataLoader` exposes a `delegate` and `storeDelegate`. 256 | 257 | ```obj-c 258 | @property (weak, nonatomic) id delegate; 259 | @property (weak, nonatomic) id storeDelegate; 260 | ``` 261 | 262 | Any object that conforms to `XLDataLoaderDelegate` can get notified about when a request starts, finish successfully or not. 263 | 264 | ```obj-c 265 | @protocol XLDataLoaderDelegate 266 | 267 | @required 268 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader; 269 | 270 | @optional 271 | -(void)dataLoaderDidStartLoadingData:(XLDataLoader *)dataLoader; 272 | -(void)dataLoaderDidLoadData:(XLDataLoader *)dataLoader; 273 | -(void)dataLoaderDidFailLoadData:(XLDataLoader *)dataLoader withError:(NSError *)error; 274 | 275 | @end 276 | ``` 277 | 278 | Any object that conforms to `XLDataLoaderStoreDelegate` can manipulate the fetched data and have the chance to update the data set or do something useful with the fetched data. 279 | 280 | ```obj-c 281 | @protocol XLDataLoaderStoreDelegate 282 | 283 | @optional 284 | -(NSDictionary *)dataLoader:(XLDataLoader *)dataLoader convertJsonDataToModelObject:(NSDictionary *)data; 285 | -(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler; 286 | 287 | @end 288 | ``` 289 | 290 | `XLRemoteDataStoreController` and `XLRemoteCoreDataController`conforms to `XLDataLoaderDelegate` and `XLDataLoaderStoreDelegate` protocols and updates the controller view accordingly by implementing some some of its methods. 291 | 292 | However `XLDataLoader` and its delegates can be used independently and not just alongside `XLData` controllers. 293 | 294 | 295 | How to run XLData examples 296 | --------------------------------- 297 | 298 | 1. Clone the repository `git@github.com:xmartlabs/XLData.git`. Optionally you can fork the repository and clone it from your own github account, this approach would be better if you want to contribute. 299 | 2. Move to project root folder. 300 | 3. Install example project cocoapod dependencies by running on terminal `pod install`. 301 | 4. Open XLData workspace using XCode and run the project. Enjoy! 302 | 303 | Installation 304 | -------------------------- 305 | 306 | The easiest way to use XLData in your app is via [CocoaPods](http://cocoapods.org/ "CocoaPods"). 307 | 308 | 1. Add the following line in the project's Podfile file: `pod 'XLData', '~> 2.0'`. 309 | 310 | 2. Run the command `pod install` from the Podfile folder directory. 311 | 312 | In order to avoid unnecessary dependencies, `XLData` is divided into standalone sub-modules (with different dependencies each) using CocoaPods subspecs: 313 | 314 | * `XLData/DataStore` includes in-memory related library classes. 315 | * `XLData/CoreData` includes Core Data related library classes (depends on Core Data framework). 316 | * `XLData/RemoteCoreData` includes Core Data related library classes and networking classes (depends on AFNetworking library and Core Data framework). 317 | * `XLData/RemoteDataStore` includes in-memory related library classes and networking classes (depends on AFNetworking library). 318 | 319 | Requirements 320 | ----------------------------- 321 | 322 | * ARC 323 | * iOS 8.0 and above 324 | 325 | 326 | Release Notes 327 | -------------- 328 | 329 | Version 2.0.2 330 | 331 | * Bug fixes and stability improvements. 332 | 333 | Version 2.0.0 334 | 335 | * Bug fixes. 336 | * Add completeHandler parameter to `-(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler` 337 | * Split up `XLDataLoaderDelegate` into `XLDataLoaderStoreDelegate` and `XLDataLoaderDelegate`. 338 | 339 | Version 1.0.2 340 | 341 | * Bug fixes. 342 | * Improve examples. 343 | 344 | Version 1.0.1 345 | 346 | * Fix podspec minor issue. 347 | 348 | Version 1.0.0 349 | 350 | * Initial release 351 | 352 | Author 353 | ----------------- 354 | 355 | [Martin Barreto](https://www.github.com/mtnBarreto "Martin Barreto Github") ([@mtnBarreto](http://twitter.com/mtnBarreto "@mtnBarreto")) 356 | 357 | 358 | Contact 359 | ---------------- 360 | 361 | Any suggestion or question? Please create a Github issue or reach me out. 362 | -------------------------------------------------------------------------------- /XLData.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'XLData' 3 | s.version = '2.0.2' 4 | s.license = 'MIT' 5 | s.summary = 'XLData provides an elegant and concise way to load, synchronize and show data sets into UITableViews and UICollectionViews.' 6 | s.homepage = 'https://github.com/xmartlabs/XLData' 7 | s.authors = { 'Martin Barreto' => 'martin@xmartlabs.com', 'Miguel Revetria' => 'miguel@xmartlabs.com' } 8 | s.source = { :git => 'https://github.com/xmartlabs/XLData.git', :tag => s.version } 9 | s.requires_arc = true 10 | s.ios.deployment_target = '8.0' 11 | s.ios.frameworks = 'UIKit', 'Foundation' 12 | 13 | s.subspec 'Core' do |sp| 14 | sp.source_files = 'XLData/XL/Core/**/*.{h,m}' 15 | end 16 | s.subspec 'CoreRemote' do |sp| 17 | sp.source_files = 'XLData/XL/CoreRemote/**/*.{h,m}' 18 | sp.dependency 'AFNetworking', '~> 2.0' 19 | end 20 | 21 | s.subspec 'DataStore' do |sp| 22 | sp.source_files = 'XLData/XL/Local/DataStore/**/*.{h,m}' 23 | sp.dependency 'XLData/Core' 24 | end 25 | s.subspec 'CoreData' do |sp| 26 | sp.source_files = 'XLData/XL/Local/CoreData/**/*.{h,m}' 27 | sp.dependency 'XLData/Core' 28 | sp.ios.frameworks = 'CoreData' 29 | end 30 | 31 | s.subspec 'RemoteDataStore' do |sp| 32 | sp.source_files = 'XLData/XL/Remote/DataStore/**/*.{h,m}' 33 | sp.dependency 'XLData/CoreRemote' 34 | sp.dependency 'XLData/DataStore' 35 | end 36 | s.subspec 'RemoteCoreData' do |sp| 37 | sp.source_files = 'XLData/XL/Remote/CoreData/**/*.{h,m}' 38 | sp.dependency 'XLData/CoreRemote' 39 | sp.dependency 'XLData/CoreData' 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /XLData.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XLData.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /XLData/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | #import 26 | 27 | @interface AppDelegate : UIResponder 28 | 29 | @property (strong, nonatomic) UIWindow *window; 30 | 31 | 32 | @end 33 | 34 | -------------------------------------------------------------------------------- /XLData/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "AppDelegate.h" 27 | #import 28 | 29 | @interface AppDelegate () 30 | 31 | @end 32 | 33 | @implementation AppDelegate 34 | 35 | 36 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 37 | // Override point for customization after application launch. 38 | [AFNetworkActivityLogger sharedLogger].filterPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { 39 | NSURLRequest * requestToEvaluate = (NSURLRequest *)evaluatedObject; 40 | return [requestToEvaluate.allHTTPHeaderFields[@"Accept"] isEqualToString:@"image/*"]; 41 | }]; 42 | [[AFNetworkActivityLogger sharedLogger] startLogging]; 43 | 44 | return YES; 45 | } 46 | 47 | - (void)applicationWillResignActive:(UIApplication *)application { 48 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 49 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 50 | } 51 | 52 | - (void)applicationDidEnterBackground:(UIApplication *)application { 53 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 54 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 55 | } 56 | 57 | - (void)applicationWillEnterForeground:(UIApplication *)application { 58 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 59 | } 60 | 61 | - (void)applicationDidBecomeActive:(UIApplication *)application { 62 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 63 | } 64 | 65 | - (void)applicationWillTerminate:(UIApplication *)application { 66 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /XLData/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "xl_appicon_58.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "29x29", 12 | "scale" : "3x" 13 | }, 14 | { 15 | "size" : "40x40", 16 | "idiom" : "iphone", 17 | "filename" : "xl_appicon_80.png", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "idiom" : "iphone", 22 | "size" : "40x40", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "60x60", 27 | "idiom" : "iphone", 28 | "filename" : "xl_appicon_120.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "idiom" : "iphone", 33 | "size" : "60x60", 34 | "scale" : "3x" 35 | } 36 | ], 37 | "info" : { 38 | "version" : 1, 39 | "author" : "xcode" 40 | } 41 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/AppIcon.appiconset/xl_appicon_120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/AppIcon.appiconset/xl_appicon_120.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/AppIcon.appiconset/xl_appicon_58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/AppIcon.appiconset/xl_appicon_58.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/AppIcon.appiconset/xl_appicon_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/AppIcon.appiconset/xl_appicon_80.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "8.0", 8 | "subtype" : "736h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "8.0", 16 | "subtype" : "736h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "8.0", 24 | "subtype" : "667h", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "orientation" : "portrait", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "7.0", 32 | "scale" : "2x" 33 | }, 34 | { 35 | "extent" : "full-screen", 36 | "idiom" : "iphone", 37 | "subtype" : "retina4", 38 | "filename" : "xl_splash@2x.png", 39 | "minimum-system-version" : "7.0", 40 | "orientation" : "portrait", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "orientation" : "portrait", 45 | "idiom" : "ipad", 46 | "extent" : "full-screen", 47 | "minimum-system-version" : "7.0", 48 | "scale" : "1x" 49 | }, 50 | { 51 | "orientation" : "landscape", 52 | "idiom" : "ipad", 53 | "extent" : "full-screen", 54 | "minimum-system-version" : "7.0", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "orientation" : "portrait", 59 | "idiom" : "ipad", 60 | "extent" : "full-screen", 61 | "minimum-system-version" : "7.0", 62 | "scale" : "2x" 63 | }, 64 | { 65 | "orientation" : "landscape", 66 | "idiom" : "ipad", 67 | "extent" : "full-screen", 68 | "minimum-system-version" : "7.0", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "orientation" : "portrait", 73 | "idiom" : "iphone", 74 | "extent" : "full-screen", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "orientation" : "portrait", 79 | "idiom" : "iphone", 80 | "extent" : "full-screen", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "orientation" : "portrait", 85 | "idiom" : "iphone", 86 | "extent" : "full-screen", 87 | "subtype" : "retina4", 88 | "scale" : "2x" 89 | }, 90 | { 91 | "orientation" : "portrait", 92 | "idiom" : "ipad", 93 | "extent" : "to-status-bar", 94 | "scale" : "1x" 95 | }, 96 | { 97 | "orientation" : "portrait", 98 | "idiom" : "ipad", 99 | "extent" : "full-screen", 100 | "scale" : "1x" 101 | }, 102 | { 103 | "orientation" : "landscape", 104 | "idiom" : "ipad", 105 | "extent" : "to-status-bar", 106 | "scale" : "1x" 107 | }, 108 | { 109 | "orientation" : "landscape", 110 | "idiom" : "ipad", 111 | "extent" : "full-screen", 112 | "scale" : "1x" 113 | }, 114 | { 115 | "orientation" : "portrait", 116 | "idiom" : "ipad", 117 | "extent" : "to-status-bar", 118 | "scale" : "2x" 119 | }, 120 | { 121 | "orientation" : "portrait", 122 | "idiom" : "ipad", 123 | "extent" : "full-screen", 124 | "scale" : "2x" 125 | }, 126 | { 127 | "orientation" : "landscape", 128 | "idiom" : "ipad", 129 | "extent" : "to-status-bar", 130 | "scale" : "2x" 131 | }, 132 | { 133 | "orientation" : "landscape", 134 | "idiom" : "ipad", 135 | "extent" : "full-screen", 136 | "scale" : "2x" 137 | } 138 | ], 139 | "info" : { 140 | "version" : 1, 141 | "author" : "xcode" 142 | } 143 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/LaunchImage.launchimage/xl_splash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/LaunchImage.launchimage/xl_splash@2x.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/Posts_Selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Posts_Selected.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/Posts_Selected.imageset/Posts_Selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/Posts_Selected.imageset/Posts_Selected.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/Posts_Unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Posts_Unselected.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/Posts_Unselected.imageset/Posts_Unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/Posts_Unselected.imageset/Posts_Unselected.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/Users_Selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Users_Selected.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/Users_Selected.imageset/Users_Selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/Users_Selected.imageset/Users_Selected.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/Users_Unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Users_Unselected.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/Users_Unselected.imageset/Users_Unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/Users_Unselected.imageset/Users_Unselected.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "arrow@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/arrow.imageset/arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/arrow.imageset/arrow@2x.png -------------------------------------------------------------------------------- /XLData/Images.xcassets/default-avatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "default-avatar@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /XLData/Images.xcassets/default-avatar.imageset/default-avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmartlabs/XLData/a07cebbbc5310ffc87ad65b9dc1a880a7bcd20b6/XLData/Images.xcassets/default-avatar.imageset/default-avatar@2x.png -------------------------------------------------------------------------------- /XLData/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /XLData/XL/Core/Helpers/UIScrollView+SVInfiniteScrolling.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVInfiniteScrolling.h 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | 12 | @class SVInfiniteScrollingView; 13 | 14 | @interface UIScrollView (SVInfiniteScrolling) 15 | 16 | - (void)addInfiniteScrollingWithActionHandler:(void (^)(void))actionHandler; 17 | - (void)triggerInfiniteScrolling; 18 | 19 | @property (nonatomic, strong, readonly) SVInfiniteScrollingView *infiniteScrollingView; 20 | @property (nonatomic, assign) BOOL showsInfiniteScrolling; 21 | 22 | @end 23 | 24 | 25 | enum { 26 | SVInfiniteScrollingStateStopped = 0, 27 | SVInfiniteScrollingStateTriggered, 28 | SVInfiniteScrollingStateLoading, 29 | SVInfiniteScrollingStateAll = 10 30 | }; 31 | 32 | typedef NSUInteger SVInfiniteScrollingState; 33 | 34 | @interface SVInfiniteScrollingView : UIView 35 | 36 | @property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 37 | @property (nonatomic, readonly) SVInfiniteScrollingState state; 38 | @property (nonatomic, readwrite) BOOL enabled; 39 | 40 | 41 | - (void)setCustomView:(UIView *)view forState:(SVInfiniteScrollingState)state; 42 | 43 | - (void)startAnimating; 44 | - (void)stopAnimating; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /XLData/XL/Core/Helpers/UIScrollView+SVInfiniteScrolling.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVInfiniteScrolling.m 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | #import "UIScrollView+SVInfiniteScrolling.h" 12 | 13 | 14 | static CGFloat const SVInfiniteScrollingViewHeight = 60; 15 | 16 | @interface SVInfiniteScrollingDotView : UIView 17 | 18 | @property (nonatomic, strong) UIColor *arrowColor; 19 | 20 | @end 21 | 22 | 23 | 24 | @interface SVInfiniteScrollingView () 25 | 26 | @property (nonatomic, copy) void (^infiniteScrollingHandler)(void); 27 | 28 | @property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; 29 | @property (nonatomic, readwrite) SVInfiniteScrollingState state; 30 | @property (nonatomic, strong) NSMutableArray *viewForState; 31 | @property (nonatomic, weak) UIScrollView *scrollView; 32 | @property (nonatomic, readwrite) CGFloat originalBottomInset; 33 | @property (nonatomic, assign) BOOL wasTriggeredByUser; 34 | @property (nonatomic, assign) BOOL isObserving; 35 | 36 | - (void)resetScrollViewContentInset; 37 | - (void)setScrollViewContentInsetForInfiniteScrolling; 38 | - (void)setScrollViewContentInset:(UIEdgeInsets)insets; 39 | 40 | @end 41 | 42 | 43 | 44 | #pragma mark - UIScrollView (SVInfiniteScrollingView) 45 | #import 46 | 47 | static char UIScrollViewInfiniteScrollingView; 48 | UIEdgeInsets scrollViewOriginalContentInsets; 49 | 50 | @implementation UIScrollView (SVInfiniteScrolling) 51 | 52 | @dynamic infiniteScrollingView; 53 | 54 | - (void)addInfiniteScrollingWithActionHandler:(void (^)(void))actionHandler { 55 | 56 | if(!self.infiniteScrollingView) { 57 | SVInfiniteScrollingView *view = [[SVInfiniteScrollingView alloc] initWithFrame:CGRectMake(0, self.contentSize.height, self.bounds.size.width, SVInfiniteScrollingViewHeight)]; 58 | view.infiniteScrollingHandler = actionHandler; 59 | view.scrollView = self; 60 | [self addSubview:view]; 61 | 62 | view.originalBottomInset = self.contentInset.bottom; 63 | self.infiniteScrollingView = view; 64 | self.showsInfiniteScrolling = YES; 65 | } 66 | } 67 | 68 | - (void)triggerInfiniteScrolling { 69 | self.infiniteScrollingView.state = SVInfiniteScrollingStateTriggered; 70 | [self.infiniteScrollingView startAnimating]; 71 | } 72 | 73 | - (void)setInfiniteScrollingView:(SVInfiniteScrollingView *)infiniteScrollingView { 74 | [self willChangeValueForKey:@"UIScrollViewInfiniteScrollingView"]; 75 | objc_setAssociatedObject(self, &UIScrollViewInfiniteScrollingView, 76 | infiniteScrollingView, 77 | OBJC_ASSOCIATION_ASSIGN); 78 | [self didChangeValueForKey:@"UIScrollViewInfiniteScrollingView"]; 79 | } 80 | 81 | - (SVInfiniteScrollingView *)infiniteScrollingView { 82 | return objc_getAssociatedObject(self, &UIScrollViewInfiniteScrollingView); 83 | } 84 | 85 | - (void)setShowsInfiniteScrolling:(BOOL)showsInfiniteScrolling { 86 | self.infiniteScrollingView.hidden = !showsInfiniteScrolling; 87 | 88 | if(!showsInfiniteScrolling) { 89 | if (self.infiniteScrollingView.isObserving) { 90 | [self removeObserver:self.infiniteScrollingView forKeyPath:@"contentOffset"]; 91 | [self removeObserver:self.infiniteScrollingView forKeyPath:@"contentSize"]; 92 | [self.infiniteScrollingView resetScrollViewContentInset]; 93 | self.infiniteScrollingView.isObserving = NO; 94 | } 95 | } 96 | else { 97 | if (!self.infiniteScrollingView.isObserving) { 98 | [self addObserver:self.infiniteScrollingView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 99 | [self addObserver:self.infiniteScrollingView forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; 100 | [self.infiniteScrollingView setScrollViewContentInsetForInfiniteScrolling]; 101 | self.infiniteScrollingView.isObserving = YES; 102 | 103 | [self.infiniteScrollingView setNeedsLayout]; 104 | self.infiniteScrollingView.frame = CGRectMake(0, self.contentSize.height, self.infiniteScrollingView.bounds.size.width, SVInfiniteScrollingViewHeight); 105 | } 106 | } 107 | } 108 | 109 | - (BOOL)showsInfiniteScrolling { 110 | return !self.infiniteScrollingView.hidden; 111 | } 112 | 113 | @end 114 | 115 | 116 | #pragma mark - SVInfiniteScrollingView 117 | @implementation SVInfiniteScrollingView 118 | 119 | // public properties 120 | @synthesize infiniteScrollingHandler, activityIndicatorViewStyle; 121 | 122 | @synthesize state = _state; 123 | @synthesize scrollView = _scrollView; 124 | @synthesize activityIndicatorView = _activityIndicatorView; 125 | 126 | 127 | - (id)initWithFrame:(CGRect)frame { 128 | if(self = [super initWithFrame:frame]) { 129 | 130 | // default styling values 131 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 132 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 133 | self.state = SVInfiniteScrollingStateStopped; 134 | self.enabled = YES; 135 | 136 | self.viewForState = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil]; 137 | } 138 | 139 | return self; 140 | } 141 | 142 | - (void)willMoveToSuperview:(UIView *)newSuperview { 143 | if (self.superview && newSuperview == nil) { 144 | UIScrollView *scrollView = (UIScrollView *)self.superview; 145 | if (scrollView.showsInfiniteScrolling) { 146 | if (self.isObserving) { 147 | [scrollView removeObserver:self forKeyPath:@"contentOffset"]; 148 | [scrollView removeObserver:self forKeyPath:@"contentSize"]; 149 | self.isObserving = NO; 150 | } 151 | } 152 | } 153 | } 154 | 155 | - (void)layoutSubviews { 156 | self.activityIndicatorView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 157 | } 158 | 159 | #pragma mark - Scroll View 160 | 161 | - (void)resetScrollViewContentInset { 162 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 163 | currentInsets.bottom = self.originalBottomInset; 164 | [self setScrollViewContentInset:currentInsets]; 165 | } 166 | 167 | - (void)setScrollViewContentInsetForInfiniteScrolling { 168 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 169 | currentInsets.bottom = self.originalBottomInset + SVInfiniteScrollingViewHeight; 170 | [self setScrollViewContentInset:currentInsets]; 171 | } 172 | 173 | - (void)setScrollViewContentInset:(UIEdgeInsets)contentInset { 174 | [UIView animateWithDuration:0.3 175 | delay:0 176 | options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState 177 | animations:^{ 178 | self.scrollView.contentInset = contentInset; 179 | } 180 | completion:NULL]; 181 | } 182 | 183 | #pragma mark - Observing 184 | 185 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 186 | if([keyPath isEqualToString:@"contentOffset"]) 187 | [self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]]; 188 | else if([keyPath isEqualToString:@"contentSize"]) { 189 | [self layoutSubviews]; 190 | self.frame = CGRectMake(0, self.scrollView.contentSize.height, self.bounds.size.width, SVInfiniteScrollingViewHeight); 191 | } 192 | } 193 | 194 | - (void)scrollViewDidScroll:(CGPoint)contentOffset { 195 | if(self.state != SVInfiniteScrollingStateLoading && self.enabled) { 196 | CGFloat scrollViewContentHeight = self.scrollView.contentSize.height; 197 | CGFloat scrollOffsetThreshold = scrollViewContentHeight-self.scrollView.bounds.size.height; 198 | 199 | if(!self.scrollView.isDragging && self.state == SVInfiniteScrollingStateTriggered) 200 | self.state = SVInfiniteScrollingStateLoading; 201 | else if(contentOffset.y > scrollOffsetThreshold && self.state == SVInfiniteScrollingStateStopped && self.scrollView.isDragging) 202 | self.state = SVInfiniteScrollingStateTriggered; 203 | else if(contentOffset.y < scrollOffsetThreshold && self.state != SVInfiniteScrollingStateStopped) 204 | self.state = SVInfiniteScrollingStateStopped; 205 | } 206 | } 207 | 208 | #pragma mark - Getters 209 | 210 | - (UIActivityIndicatorView *)activityIndicatorView { 211 | if(!_activityIndicatorView) { 212 | _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 213 | _activityIndicatorView.hidesWhenStopped = YES; 214 | [self addSubview:_activityIndicatorView]; 215 | } 216 | return _activityIndicatorView; 217 | } 218 | 219 | - (UIActivityIndicatorViewStyle)activityIndicatorViewStyle { 220 | return self.activityIndicatorView.activityIndicatorViewStyle; 221 | } 222 | 223 | #pragma mark - Setters 224 | 225 | - (void)setCustomView:(UIView *)view forState:(SVInfiniteScrollingState)state { 226 | id viewPlaceholder = view; 227 | 228 | if(!viewPlaceholder) 229 | viewPlaceholder = @""; 230 | 231 | if(state == SVInfiniteScrollingStateAll) 232 | [self.viewForState replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[viewPlaceholder, viewPlaceholder, viewPlaceholder]]; 233 | else 234 | [self.viewForState replaceObjectAtIndex:state withObject:viewPlaceholder]; 235 | 236 | self.state = self.state; 237 | } 238 | 239 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)viewStyle { 240 | self.activityIndicatorView.activityIndicatorViewStyle = viewStyle; 241 | } 242 | 243 | #pragma mark - 244 | 245 | - (void)triggerRefresh { 246 | self.state = SVInfiniteScrollingStateTriggered; 247 | self.state = SVInfiniteScrollingStateLoading; 248 | } 249 | 250 | - (void)startAnimating{ 251 | self.state = SVInfiniteScrollingStateLoading; 252 | } 253 | 254 | - (void)stopAnimating { 255 | self.state = SVInfiniteScrollingStateStopped; 256 | } 257 | 258 | - (void)setState:(SVInfiniteScrollingState)newState { 259 | 260 | if(_state == newState) 261 | return; 262 | 263 | SVInfiniteScrollingState previousState = _state; 264 | _state = newState; 265 | 266 | for(id otherView in self.viewForState) { 267 | if([otherView isKindOfClass:[UIView class]]) 268 | [otherView removeFromSuperview]; 269 | } 270 | 271 | id customView = [self.viewForState objectAtIndex:newState]; 272 | BOOL hasCustomView = [customView isKindOfClass:[UIView class]]; 273 | 274 | if(hasCustomView) { 275 | [self addSubview:customView]; 276 | CGRect viewBounds = [customView bounds]; 277 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 278 | [customView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 279 | } 280 | else { 281 | CGRect viewBounds = [self.activityIndicatorView bounds]; 282 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 283 | [self.activityIndicatorView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 284 | 285 | switch (newState) { 286 | case SVInfiniteScrollingStateStopped: 287 | [self.activityIndicatorView stopAnimating]; 288 | break; 289 | 290 | case SVInfiniteScrollingStateTriggered: 291 | [self.activityIndicatorView startAnimating]; 292 | break; 293 | 294 | case SVInfiniteScrollingStateLoading: 295 | [self.activityIndicatorView startAnimating]; 296 | break; 297 | } 298 | } 299 | 300 | if(previousState == SVInfiniteScrollingStateTriggered && newState == SVInfiniteScrollingStateLoading && self.infiniteScrollingHandler && self.enabled) 301 | self.infiniteScrollingHandler(); 302 | } 303 | 304 | @end 305 | -------------------------------------------------------------------------------- /XLData/XL/Core/Store/XLDataSectionStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @class XLDataStore; 29 | 30 | @interface XLDataSectionStore : NSObject 31 | 32 | + (instancetype)dataSectionStore; 33 | + (instancetype)dataSectionStoreWithTitle:(NSString *)title; 34 | 35 | @property (weak) XLDataStore * dataStore; 36 | 37 | @property (readonly) NSString * title; 38 | 39 | - (NSUInteger)numberOfItems; 40 | 41 | - (id)dataAtIndex:(NSUInteger)index; 42 | - (void)addDataItem:(id)item; 43 | - (void)addDataItems:(NSArray *)items; 44 | - (void)addDataItems:(NSArray *)items fromIndex:(NSUInteger)index; 45 | - (void)removeDataItemAtIndex:(NSUInteger)indexPath; 46 | - (BOOL)removeDataItem:(id)item; 47 | - (BOOL)removeDataItemMatchingPredicate:(NSPredicate *)predicate; 48 | - (BOOL)removeDataItemsMatchingPredicate:(NSPredicate *)predicate; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /XLData/XL/Core/Store/XLDataSectionStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import UIKit; 27 | #import "XLDataStore.h" 28 | #import "XLDataSectionStore.h" 29 | 30 | @interface XLDataSectionStore() 31 | 32 | @property NSMutableArray * dataRows; 33 | @property NSString * title; 34 | 35 | @end 36 | 37 | @implementation XLDataSectionStore 38 | 39 | @synthesize title = _title; 40 | 41 | 42 | - (instancetype)init 43 | { 44 | return [self initWithTitle:nil]; 45 | } 46 | 47 | - (instancetype)initWithTitle:(NSString *)title 48 | { 49 | self = [super init]; 50 | if (self) { 51 | _title = title; 52 | _dataRows = [[NSMutableArray alloc] init]; 53 | [self addObserver:self forKeyPath:@"dataRows" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:0]; 54 | } 55 | return self; 56 | } 57 | 58 | + (instancetype)dataSectionStore 59 | { 60 | return [self dataSectionStoreWithTitle:nil]; 61 | } 62 | 63 | + (instancetype)dataSectionStoreWithTitle:(NSString *)title 64 | { 65 | return [[self alloc] initWithTitle:title]; 66 | } 67 | 68 | - (NSUInteger)numberOfItems 69 | { 70 | return self.dataRows.count; 71 | } 72 | 73 | - (NSString *)title 74 | { 75 | return _title; 76 | } 77 | 78 | -(void)setTitle:(NSString *)title 79 | { 80 | _title = title; 81 | } 82 | 83 | -(id)dataAtIndex:(NSUInteger)index 84 | { 85 | return [self.dataRows objectAtIndex:index]; 86 | } 87 | 88 | -(void)addDataItem:(id)item 89 | { 90 | [self insertObject:item inDataRowsAtIndex:[self countOfDataRows]]; 91 | } 92 | 93 | -(void)addDataItems:(NSArray *)items 94 | { 95 | [self addDataItems:items fromIndex:[self countOfDataRows]]; 96 | } 97 | 98 | -(void)addDataItems:(NSArray *)items fromIndex:(NSUInteger)fromIndex 99 | { 100 | if (fromIndex >= self.dataRows.count){ 101 | [self insertDataRows:items atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.dataRows.count, items.count)]]; 102 | } 103 | else { // fromIndex < self.dataRows.count 104 | NSUInteger newCountOfItems = fromIndex + items.count; 105 | NSUInteger countOfReplacedObjects = MIN(items.count, self.dataRows.count - fromIndex); 106 | NSIndexSet * indexSetToReplace = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(fromIndex, countOfReplacedObjects)]; 107 | [self replaceDataRowsAtIndexes:indexSetToReplace withDataRows:[items objectsAtIndexes:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, countOfReplacedObjects)]]]; 108 | if (countOfReplacedObjects < items.count){ 109 | NSIndexSet * indexSetNotReplacedItems = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(countOfReplacedObjects, items.count - countOfReplacedObjects)]; 110 | [self insertDataRows:[items objectsAtIndexes:indexSetNotReplacedItems] atIndexes:indexSetNotReplacedItems]; 111 | } 112 | [self removeDataItemsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(newCountOfItems, self.dataRows.count - newCountOfItems)]]; 113 | } 114 | } 115 | 116 | -(void)removeDataItemAtIndex:(NSUInteger)indexPath 117 | { 118 | [self removeDataItemsAtIndexes:[NSIndexSet indexSetWithIndex:indexPath]]; 119 | } 120 | 121 | -(BOOL)removeDataItem:(id)item 122 | { 123 | NSUInteger index = [self.dataRows indexOfObject:item]; 124 | if (index != NSNotFound){ 125 | [self removeDataItemAtIndex:index]; 126 | return YES; 127 | } 128 | return NO; 129 | } 130 | 131 | - (BOOL)removeDataItemMatchingPredicate:(NSPredicate *)predicate 132 | { 133 | BOOL __block result = NO; 134 | [[self.dataRows copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 135 | @try { 136 | result = [predicate evaluateWithObject:obj substitutionVariables:nil]; 137 | if (result){ 138 | [self removeDataItemAtIndex:idx]; 139 | *stop = YES; 140 | } 141 | } 142 | @catch (NSException *exception) {} 143 | }]; 144 | return result; 145 | } 146 | 147 | - (BOOL)removeDataItemsMatchingPredicate:(NSPredicate *)predicate 148 | { 149 | BOOL __block result = NO; 150 | [[self.dataRows copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 151 | @try { 152 | BOOL evalResult = [predicate evaluateWithObject:obj substitutionVariables:nil]; 153 | if (evalResult){ 154 | [self removeDataItemAtIndex:idx]; 155 | } 156 | result = result || evalResult; 157 | } 158 | @catch (NSException *exception) {} 159 | }]; 160 | return result; 161 | } 162 | 163 | -(void)removeDataItemsAtIndexes:(NSIndexSet *)indexSet{ 164 | [self removeDataRowsAtIndexes:indexSet]; 165 | } 166 | 167 | 168 | -(void)dealloc 169 | { 170 | [self removeObserver:self forKeyPath:@"dataRows"]; 171 | } 172 | 173 | #pragma mark - KVO 174 | 175 | -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 176 | { 177 | if (!self.dataStore.delegate) return; 178 | if ([keyPath isEqualToString:@"dataRows"]){ 179 | if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeInsertion)]){ 180 | NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey]; 181 | if (indexSet.firstIndex != NSNotFound){ 182 | NSArray * newRows = [change objectForKey:NSKeyValueChangeNewKey]; 183 | [self.dataStore.delegate dataStoreWillChangeContent:self.dataStore]; 184 | NSUInteger sectionIndex = [self.dataStore indexOfSection:self]; 185 | NSAssert(sectionIndex != NSNotFound, @"sectionIndex must not be equal to NSNotFound"); 186 | NSUInteger index __block = 0; 187 | [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 188 | [self.dataStore.delegate dataStore:self.dataStore didChangeObject:newRows[index++] 189 | atIndexPath:nil forChangeType:XLDataStoreChangeTypeInsert 190 | newIndexPath:[NSIndexPath indexPathForRow:idx inSection:sectionIndex]]; 191 | }]; 192 | [self.dataStore.delegate dataStoreDidChangeContent:self.dataStore]; 193 | } 194 | } 195 | else if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeRemoval)]){ 196 | NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey]; 197 | if (indexSet.firstIndex != NSNotFound){ 198 | NSArray * oldRows = [change objectForKey:NSKeyValueChangeOldKey]; 199 | [self.dataStore.delegate dataStoreWillChangeContent:self.dataStore]; 200 | NSUInteger sectionIndex = [self.dataStore indexOfSection:self]; 201 | NSAssert(sectionIndex != NSNotFound, @"sectionIndex must not be equal to NSNotFound"); 202 | NSUInteger index __block = 0; 203 | [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 204 | [self.dataStore.delegate dataStore:self.dataStore 205 | didChangeObject:oldRows[index++] 206 | atIndexPath:[NSIndexPath indexPathForRow:idx inSection:sectionIndex] 207 | forChangeType:XLDataStoreChangeTypeDelete 208 | newIndexPath:nil]; 209 | }]; 210 | [self.dataStore.delegate dataStoreDidChangeContent:self.dataStore]; 211 | } 212 | } 213 | else{ 214 | NSAssert(true, @""); 215 | } 216 | } 217 | } 218 | 219 | #pragma mark - KVC 220 | 221 | -(NSUInteger)countOfDataRows 222 | { 223 | return self.dataRows.count; 224 | } 225 | 226 | - (id)objectInDataRowsAtIndex:(NSUInteger)index 227 | { 228 | return [self.dataRows objectAtIndex:index]; 229 | } 230 | 231 | - (NSArray *)dataRowsAtIndexes:(NSIndexSet *)indexes 232 | { 233 | return [self.dataRows objectsAtIndexes:indexes]; 234 | } 235 | 236 | -(void)insertObject:(id)object inDataRowsAtIndex:(NSUInteger)index 237 | { 238 | [self.dataRows insertObject:object atIndex:index]; 239 | } 240 | 241 | -(void)insertDataRows:(NSArray *)array atIndexes:(NSIndexSet *)indexes 242 | { 243 | [self.dataRows insertObjects:array atIndexes:indexes]; 244 | } 245 | 246 | -(void)removeDataRowsAtIndexes:(NSIndexSet *)indexes 247 | { 248 | [self.dataRows removeObjectsAtIndexes:indexes]; 249 | } 250 | 251 | - (void)removeObjectFromDataRowsAtIndex:(NSUInteger)index 252 | { 253 | [self.dataRows removeObjectAtIndex:index]; 254 | } 255 | 256 | -(void)replaceObjectInDataRowsAtIndex:(NSUInteger)index withObject:(id)object 257 | { 258 | [self.dataRows replaceObjectAtIndex:index withObject:object]; 259 | } 260 | 261 | -(void)replaceDataRowsAtIndexes:(NSIndexSet *)indexes withDataRows:(NSArray *)array 262 | { 263 | [self.dataRows replaceObjectsAtIndexes:indexes withObjects:array]; 264 | } 265 | 266 | 267 | @end 268 | -------------------------------------------------------------------------------- /XLData/XL/Core/Store/XLDataStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLDataSectionStore.h" 27 | #import 28 | 29 | @protocol XLDataStoreDelegate; 30 | 31 | 32 | /** 33 | * This is a data store abstraction, we can add sections and items to its sections. Every change we make on the DataStore will be notified throught the delegate property (id delegate). 34 | */ 35 | @interface XLDataStore : NSObject 36 | 37 | @property (weak, nonatomic) id delegate; 38 | 39 | - (instancetype)init; 40 | - (instancetype)initWithDelegate:(id)delegate; 41 | 42 | /** 43 | * Returns YES if the dataStore has no sections, otherwise NO. 44 | * 45 | * @return YES if the dataStore has no sections 46 | */ 47 | - (BOOL)isEmpty; 48 | 49 | /** 50 | * Returns the number of sections contained in the dataStore. 51 | * 52 | * @return the number of sections in the dataStore. 53 | */ 54 | - (NSUInteger)numberOfSections; 55 | 56 | /** 57 | * Returns the number of items contained in the section placed at section index. 58 | * 59 | * @param section specify the index of the section 60 | * 61 | * @return Returns the number of items contained in the section 62 | */ 63 | - (NSUInteger)numberOfItemsInSection:(NSUInteger)section; 64 | 65 | /** 66 | * Returns the count of items within the data store. 67 | * 68 | * @return Total number of items, it's the sum of all section items. 69 | */ 70 | - (NSUInteger)totalNumberOfItems; 71 | 72 | /** 73 | * Returns the section located at the specified index. 74 | * 75 | * If index is beyond the end of the dataStore (that is, if index is greater than or equal to the value returned by numberOfSections), an NSRangeException is raised. 76 | * @param index An index within the bounds of the dataStore. 77 | * 78 | * @return XLDataStoreSection instance. 79 | */ 80 | - (XLDataSectionStore *)sectionAtIndex:(NSUInteger)index; 81 | 82 | /** 83 | * It returns the sections located at the last index, if the dataStore hasn't got any section, it creates one empty section, adds it to the dataStore and return it. 84 | * 85 | * @return section located at the last index. 86 | */ 87 | - (XLDataSectionStore *)lastSection; 88 | 89 | /** 90 | * Returns the object located at indexPath, if there is no object located at indexPath an NSRangeException is raised. 91 | * 92 | * @param indexPath An indexPath that specify a section value for lookup the sectionStore and a row for lookup the object (data) within the sectionStore. 93 | * 94 | * @return data located at indexPath. 95 | */ 96 | - (id)dataAtIndexPath:(NSIndexPath *)indexPath; 97 | 98 | /** 99 | * Returns the index whose corresponding section is equal to a given section. 100 | * 101 | * @param section a XLDataSection object. 102 | * 103 | * @return The index whose corresponding section is equal to section. If none of the dataSections in the dataStore is equal to section, returns NSNotFound. 104 | */ 105 | - (NSUInteger)indexOfSection:(XLDataSectionStore *)section; 106 | 107 | /** 108 | * Inserts the given dataSection to the end of dataStore sections collection. 109 | * 110 | * @param dataSection 111 | */ 112 | -(void)addDataSection:(XLDataSectionStore *)dataSection; 113 | 114 | /** 115 | * Inserts the given dataSection into the dataSection collection at a given index. 116 | * 117 | * @param dataSection dataSection to insert 118 | * @param index The index in the dataSection's collection at which to insert the dataSection. This value must not be greater than the count of sections in the dataStore. 119 | * Important 120 | * Raises an NSRangeException if index is greater than the number of sections in the dataStore. 121 | */ 122 | -(void)addDataSection:(XLDataSectionStore *)dataSection atIndex:(NSUInteger)index; 123 | 124 | /** 125 | * Insert dataSection before beforeSection, if beforeSection is not present in the dataStore the dataSection is inserted at the end of the dataStore. 126 | * 127 | * @param dataSection section to insert. 128 | * @param beforeSection section used to determine the proper location to insert dataSection. 129 | */ 130 | -(void)addDataSection:(XLDataSectionStore *)dataSection beforeSection:(XLDataSectionStore *)beforeSection; 131 | 132 | /** 133 | * Insert dataSection after afterSection, if afterSection is not present in the dataStore the dataSection is inserted at the end of the dataStore. 134 | * 135 | * @param dataSection section to insert. 136 | * @param afterSection section used to determine the proper location to insert dataSection. 137 | */ 138 | -(void)addDataSection:(XLDataSectionStore *)dataSection afterSection:(XLDataSectionStore *)afterSection; 139 | 140 | /** 141 | * Insert the data item into the last section of the dataStore 142 | * 143 | * @param item An item to insert. 144 | */ 145 | -(void)addDataItem:(id)item; 146 | 147 | /** 148 | * Insert items at the end of the last section. 149 | * 150 | * @param items items to be inserted. 151 | */ 152 | -(void)addDataItems:(NSArray *)items; 153 | 154 | /** 155 | * Removes section from the dataStore. 156 | * 157 | * @param section dataSection to be removed from dataStore. 158 | */ 159 | -(void)removeDataSection:(XLDataSectionStore *)section; 160 | 161 | /** 162 | * Removes the data item located at specific indexPath 163 | * 164 | * @param indexPath The indexPath of the data item to remove 165 | */ 166 | -(void)removeDataItemAtIndexPath:(NSIndexPath *)indexPath; 167 | 168 | /** 169 | * Removes item from the dataStore. 170 | * 171 | * @param item data item to remove. 172 | * 173 | * @return YES if the item is removed, otherwise NO. 174 | */ 175 | -(BOOL)removeDataItem:(id)item; 176 | 177 | /** 178 | * Removes the data item that matches the predicate. 179 | * 180 | * @param predicate A predicate expression used to get the item that should be removed. 181 | * 182 | * @return YES if the item is removed, otherwise NO. 183 | */ 184 | -(BOOL)removeDataItemMatchingPredicate:(NSPredicate *)predicate; 185 | 186 | /** 187 | * Removes the data items that match the predicate. 188 | * 189 | * @param predicate A predicate expression used to get the items that should be removed. 190 | * 191 | * @return YES if at least one item was removed, otherwise NO. 192 | */ 193 | -(BOOL)removeDataItemsMatchingPredicate:(NSPredicate *)predicate; 194 | 195 | 196 | @end 197 | 198 | 199 | 200 | @protocol XLDataStoreDelegate 201 | 202 | typedef NS_ENUM(NSUInteger, XLDataStoreChangeType) { 203 | XLDataStoreChangeTypeInsert = 1, 204 | XLDataStoreChangeTypeDelete = 2, 205 | XLDataStoreChangeTypeMove = 3, 206 | XLDataStoreChangeTypeUpdate = 4 207 | }; 208 | 209 | /* Notifies the delegate that a fetched object has been changed due to an add, remove, move, or update. Enables NSFetchedResultsController change tracking. 210 | controller - controller instance that noticed the change on its fetched objects 211 | anObject - changed object 212 | indexPath - indexPath of changed object (nil for inserts) 213 | type - indicates if the change was an insert, delete, move, or update 214 | newIndexPath - the destination path for inserted or moved objects, nil otherwise 215 | 216 | Changes are reported with the following heuristics: 217 | 218 | On Adds and Removes, only the Added/Removed object is reported. It's assumed that all objects that come after the affected object are also moved, but these moves are not reported. 219 | The Move object is reported when the changed attribute on the object is one of the sort descriptors used in the fetch request. An update of the object is assumed in this case, but no separate update message is sent to the delegate. 220 | The Update object is reported when an object's state changes, and the changed attributes aren't part of the sort keys. 221 | */ 222 | @optional 223 | - (void)dataStore:(XLDataStore *)dataStore didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(XLDataStoreChangeType)type newIndexPath:(NSIndexPath *)newIndexPath; 224 | 225 | /* Notifies the delegate of added or removed sections. Enables NSFetchedResultsController change tracking. 226 | 227 | controller - controller instance that noticed the change on its sections 228 | sectionInfo - changed section 229 | index - index of changed section 230 | type - indicates if the change was an insert or delete 231 | 232 | Changes on section info are reported before changes on fetchedObjects. 233 | */ 234 | @optional 235 | - (void)dataStore:(XLDataStore *)dataStore didChangeSection:(XLDataSectionStore *)sectionStore atIndex:(NSUInteger)sectionIndex forChangeType:(XLDataStoreChangeType)type; 236 | 237 | /* Notifies the delegate that section and object changes are about to be processed and notifications will be sent. Enables NSFetchedResultsController change tracking. 238 | Clients utilizing a UITableView may prepare for a batch of updates by responding to this method with -beginUpdates 239 | */ 240 | @optional 241 | - (void)dataStoreWillChangeContent:(XLDataStore *)dataStore; 242 | 243 | /* Notifies the delegate that all section and object changes have been sent. Enables NSFetchedResultsController change tracking. 244 | Providing an empty implementation will enable change tracking if you do not care about the individual callbacks. 245 | */ 246 | @optional 247 | - (void)dataStoreDidChangeContent:(XLDataStore *)dataStore; 248 | 249 | 250 | @end 251 | -------------------------------------------------------------------------------- /XLData/XL/Core/Store/XLDataStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLDataSectionStore.h" 27 | #import "XLDataStore.h" 28 | @import UIKit; 29 | 30 | @class XLDataStore; 31 | 32 | @interface XLDataStore() 33 | 34 | @property NSMutableArray * dataSections; 35 | 36 | @end 37 | 38 | @implementation XLDataStore 39 | 40 | - (instancetype)init 41 | { 42 | self = [super init]; 43 | if (self) { 44 | } 45 | return self; 46 | } 47 | 48 | - (instancetype)initWithDelegate:(id)delegate 49 | { 50 | self = [self init]; 51 | if (self) { 52 | _dataSections = [[NSMutableArray alloc] init]; 53 | _delegate = delegate; 54 | [self addObserver:self forKeyPath:@"dataSections" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:0]; 55 | } 56 | return self; 57 | } 58 | 59 | -(BOOL)isEmpty 60 | { 61 | return self.dataSections.count == 0; 62 | } 63 | 64 | - (NSUInteger)numberOfSections 65 | { 66 | return self.dataSections.count; 67 | } 68 | 69 | - (NSUInteger)numberOfItemsInSection:(NSUInteger)section 70 | { 71 | return [[self sectionAtIndex:section] numberOfItems]; 72 | } 73 | 74 | -(NSUInteger)totalNumberOfItems 75 | { 76 | NSUInteger result __block = 0; 77 | [self.dataSections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 78 | result += [(XLDataSectionStore *)obj numberOfItems]; 79 | }]; 80 | return result; 81 | } 82 | 83 | - (XLDataSectionStore *)sectionAtIndex:(NSUInteger)index 84 | { 85 | return [self.dataSections objectAtIndex:index]; 86 | } 87 | 88 | - (XLDataSectionStore *)lastSection 89 | { 90 | if ([self countOfDataSections] == 0){ 91 | XLDataSectionStore * dataSection = [[XLDataSectionStore alloc] init]; 92 | [self addDataSection:dataSection]; 93 | } 94 | return [self objectInDataSectionsAtIndex:([self countOfDataSections] - 1)]; 95 | } 96 | 97 | - (id)dataAtIndexPath:(NSIndexPath *)indexPath 98 | { 99 | return [[self sectionAtIndex:indexPath.section] dataAtIndex:indexPath.row]; 100 | } 101 | 102 | 103 | -(void)addDataSection:(XLDataSectionStore *)dataSection 104 | { 105 | [self insertObject:dataSection inDataSectionsAtIndex:[self countOfDataSections]]; 106 | } 107 | 108 | -(void)addDataSection:(XLDataSectionStore *)dataSection atIndex:(NSUInteger)index 109 | { 110 | [self insertObject:dataSection inDataSectionsAtIndex:index]; 111 | } 112 | 113 | -(void)addDataSection:(XLDataSectionStore *)dataSection beforeSection:(XLDataSectionStore *)beforeSection 114 | { 115 | NSUInteger index; 116 | if ((index = [self indexOfSection:beforeSection]) != NSNotFound) { 117 | [self insertObject:dataSection inDataSectionsAtIndex:index]; 118 | } 119 | else{ 120 | // if beforeSection does not exist we insert at the end. 121 | [self addDataSection:dataSection]; 122 | } 123 | } 124 | 125 | -(void)addDataSection:(XLDataSectionStore *)dataSection afterSection:(XLDataSectionStore *)afterSection 126 | { 127 | NSUInteger index; 128 | if ((index = [self indexOfSection:afterSection]) != NSNotFound) { 129 | [self insertObject:dataSection inDataSectionsAtIndex:index+1]; 130 | } 131 | else{ 132 | // if afterSection does not exist we insert at the end. 133 | [self addDataSection:dataSection]; 134 | } 135 | } 136 | 137 | -(void)addDataItem:(id)item 138 | { 139 | [[self lastSection] addDataItem:item]; 140 | } 141 | 142 | -(void)addDataItems:(NSArray *)items 143 | { 144 | [[self lastSection] addDataItems:items]; 145 | } 146 | 147 | -(NSUInteger)indexOfSection:(XLDataSectionStore *)section 148 | { 149 | return [self.dataSections indexOfObject:section]; 150 | } 151 | 152 | -(void)removeDataSectionAtIndex:(NSUInteger)index 153 | { 154 | if (self.dataSections.count > index){ 155 | [self removeObjectFromDataSectionsAtIndex:index]; 156 | } 157 | } 158 | 159 | 160 | -(void)removeDataSection:(XLDataSectionStore *)dataSection 161 | { 162 | NSUInteger index = NSNotFound; 163 | if ((index = [self.dataSections indexOfObject:dataSection]) != NSNotFound){ 164 | [self removeDataSectionAtIndex:index]; 165 | } 166 | } 167 | 168 | -(void)removeDataItemAtIndexPath:(NSIndexPath *)indexPath 169 | { 170 | if (self.dataSections.count > indexPath.section){ 171 | XLDataSectionStore *dataSection = [self.dataSections objectAtIndex:indexPath.section]; 172 | [dataSection removeDataItemAtIndex:indexPath.row]; 173 | } 174 | } 175 | 176 | -(BOOL)removeDataItem:(id)item 177 | { 178 | for (XLDataSectionStore * dataSection in self.dataSections) { 179 | BOOL removed = [dataSection removeDataItem:item]; 180 | if (removed){ 181 | return YES; 182 | } 183 | } 184 | return NO; 185 | } 186 | 187 | -(BOOL)removeDataItemMatchingPredicate:(NSPredicate *)predicate 188 | { 189 | BOOL result __block = NO; 190 | [[self.dataSections copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 191 | result = [(XLDataSectionStore *)obj removeDataItemMatchingPredicate:predicate]; 192 | if (result){ 193 | *stop = YES; 194 | } 195 | }]; 196 | return result; 197 | } 198 | 199 | -(BOOL)removeDataItemsMatchingPredicate:(NSPredicate *)predicate 200 | { 201 | BOOL result __block = NO; 202 | [self.dataSections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 203 | result = result || [(XLDataSectionStore *)obj removeDataItemsMatchingPredicate:predicate]; 204 | }]; 205 | return result; 206 | } 207 | 208 | -(void)dealloc 209 | { 210 | @try { 211 | [self removeObserver:self forKeyPath:@"dataSections"]; 212 | } 213 | @catch (NSException *exception) {} 214 | } 215 | 216 | #pragma mark - KVO 217 | 218 | -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 219 | { 220 | if (!self.delegate) return; 221 | if ([keyPath isEqualToString:@"dataSections"]){ 222 | if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeInsertion)]){ 223 | NSArray * newSections = [change objectForKey:NSKeyValueChangeNewKey]; 224 | [self.delegate dataStoreWillChangeContent:self]; 225 | NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey]; 226 | NSUInteger index __block = 0; 227 | [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 228 | [self.delegate dataStore:self didChangeSection:newSections[index++] atIndex:idx forChangeType:XLDataStoreChangeTypeInsert]; 229 | }]; 230 | [self.delegate dataStoreDidChangeContent:self]; 231 | } 232 | else if ([[change objectForKey:NSKeyValueChangeKindKey] isEqualToNumber:@(NSKeyValueChangeRemoval)]){ 233 | NSIndexSet * indexSet = [change objectForKey:NSKeyValueChangeIndexesKey]; 234 | [self.delegate dataStoreWillChangeContent:self]; 235 | [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 236 | [self.delegate dataStore:self didChangeSection:nil atIndex:idx forChangeType:XLDataStoreChangeTypeDelete]; 237 | }]; 238 | [self.delegate dataStoreDidChangeContent:self]; 239 | } 240 | } 241 | } 242 | 243 | 244 | #pragma mark - KVC 245 | 246 | -(NSUInteger)countOfDataSections 247 | { 248 | return self.dataSections.count; 249 | } 250 | 251 | 252 | -(id)objectInDataSectionsAtIndex:(NSUInteger)index 253 | { 254 | return [self.dataSections objectAtIndex:index]; 255 | } 256 | 257 | -(NSArray *)dataSectionsAtIndexes:(NSIndexSet *)indexes 258 | { 259 | return [self.dataSections objectsAtIndexes:indexes]; 260 | } 261 | 262 | -(void)insertObject:(XLDataSectionStore *)object inDataSectionsAtIndex:(NSUInteger)index 263 | { 264 | object.dataStore = self; 265 | [self.dataSections insertObject:object atIndex:index]; 266 | } 267 | 268 | -(void)removeObjectFromDataSectionsAtIndex:(NSUInteger)index 269 | { 270 | XLDataSectionStore * dataSection = [self objectInDataSectionsAtIndex:index]; 271 | dataSection.dataStore = nil; 272 | [self.dataSections removeObjectAtIndex:index]; 273 | } 274 | 275 | @end 276 | -------------------------------------------------------------------------------- /XLData/XL/Core/View/XLNetworkStatusView.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface XLNetworkStatusView : UIView 29 | 30 | @property (nonatomic, readonly) UILabel * messageLabel; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /XLData/XL/Core/View/XLNetworkStatusView.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLNetworkStatusView.h" 27 | 28 | @implementation XLNetworkStatusView 29 | 30 | @synthesize messageLabel = _messageLabel; 31 | 32 | - (id)initWithFrame:(CGRect)frame 33 | { 34 | self = [super initWithFrame:frame]; 35 | if (self) { 36 | // Initialization code 37 | [self setBackgroundColor:[UIColor orangeColor]]; 38 | [self addSubview:self.messageLabel]; 39 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.messageLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]]; 40 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.messageLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]]; 41 | self.layer.zPosition = MAXFLOAT; 42 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 43 | } 44 | return self; 45 | } 46 | 47 | 48 | #pragma mark - Properties 49 | 50 | -(UILabel *)messageLabel 51 | { 52 | if (_messageLabel) return _messageLabel; 53 | _messageLabel = [[UILabel alloc] init]; 54 | [_messageLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; 55 | [_messageLabel setTextColor:[UIColor whiteColor]]; 56 | [_messageLabel setText:NSLocalizedString(@"No internet connection", nil)]; 57 | UIFont * font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; 58 | [_messageLabel setFont:font]; 59 | return _messageLabel; 60 | } 61 | 62 | 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /XLData/XL/Core/View/XLSearchBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface XLSearchBar : UISearchBar 29 | 30 | 31 | -(void)startActivityIndicator; 32 | 33 | -(void)stopActivityIndicator; 34 | 35 | -(UITextField *)textField; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /XLData/XL/Core/View/XLSearchBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLSearchBar.h" 27 | 28 | @implementation XLSearchBar 29 | 30 | - (id)initWithFrame:(CGRect)frame 31 | { 32 | self = [super initWithFrame:frame]; 33 | if (self) { 34 | // Initialization code 35 | self.autocapitalizationType = UITextAutocapitalizationTypeNone; 36 | self.autocorrectionType = UITextAutocorrectionTypeNo; 37 | UITextField * textField = [XLSearchBar textField:self]; 38 | textField.clearButtonMode = UITextFieldViewModeNever; 39 | self.placeholder = NSLocalizedString(@"Search", @"Search"); 40 | } 41 | return self; 42 | } 43 | 44 | -(UITextField *)textField 45 | { 46 | return [XLSearchBar textField:self]; 47 | } 48 | 49 | +(UITextField *)textField:(UIView *)view 50 | { 51 | if ([view isKindOfClass:[UITextField class]]){ 52 | return (UITextField *)view; 53 | } 54 | for (UIView * subview in view.subviews) { 55 | UITextField * textField = [self textField:subview]; 56 | if (textField) return textField; 57 | } 58 | return nil; 59 | } 60 | 61 | -(void)stopActivityIndicator 62 | { 63 | UITextField *searchField = [XLSearchBar textField:self]; 64 | if (searchField) { 65 | if ([searchField.rightView isKindOfClass:[UIActivityIndicatorView class]]){ 66 | [((UIActivityIndicatorView *)searchField.rightView) stopAnimating]; 67 | } 68 | } 69 | 70 | } 71 | 72 | -(void)startActivityIndicator 73 | { 74 | UITextField *searchField = [XLSearchBar textField:self]; 75 | if (searchField) { 76 | if (![searchField.rightView isKindOfClass:[UIActivityIndicatorView class]]){ 77 | UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 78 | searchField.rightView = spinner; 79 | searchField.rightViewMode = UITextFieldViewModeAlways; 80 | } 81 | [((UIActivityIndicatorView *)searchField.rightView) startAnimating]; 82 | } 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /XLData/XL/Core/XLData.h: -------------------------------------------------------------------------------- 1 | // 2 | // XLData.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import Foundation; 27 | #import "XLDataStore.h" 28 | #import "XLDataSectionStore.h" 29 | 30 | typedef NS_ENUM(NSUInteger, XLDataControllerType) { 31 | XLDataStoreControllerTypeTableView, 32 | XLDataStoreControllerTypeCollectionView 33 | }; 34 | 35 | /** 36 | * This protocol is conformed by XLDataStoreController & XLCoreDataController 37 | */ 38 | @protocol XLDataController 39 | 40 | 41 | /** 42 | * You can override XLDataStoreController, XLCoreDataController implementation of this method to determine when a data set is empty. Sometimes an empty section represent an empty data set, sometimes a data set without any section represent an empty data set. The value of this method is used to show hide the emptyDataSetView. 43 | */ 44 | -(BOOL)isEmptyDataSet; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /XLData/XL/Core/XLData.m: -------------------------------------------------------------------------------- 1 | // 2 | // XLData.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLData.h" 27 | 28 | 29 | -------------------------------------------------------------------------------- /XLData/XL/CoreRemote/Loader/XLDataLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import Foundation; 27 | #import 28 | #import "XLDataLoader.h" 29 | 30 | 31 | extern NSString * const kXLDataLoaderErrorDomain; 32 | extern NSString * const kXLRemoteDataLoaderDefaultKeyForNonDictionaryResponse; 33 | 34 | 35 | @class XLDataLoader; 36 | 37 | @protocol XLDataLoaderDelegate 38 | 39 | @required 40 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader; 41 | 42 | @optional 43 | -(void)dataLoaderDidStartLoadingData:(XLDataLoader *)dataLoader; 44 | -(void)dataLoaderDidLoadData:(XLDataLoader *)dataLoader; 45 | -(void)dataLoaderDidFailLoadData:(XLDataLoader *)dataLoader withError:(NSError *)error; 46 | 47 | @end 48 | 49 | @protocol XLDataLoaderStoreDelegate 50 | 51 | @optional 52 | -(NSDictionary *)dataLoader:(XLDataLoader *)dataLoader convertJsonDataToModelObject:(NSDictionary *)data; 53 | -(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler; 54 | 55 | @end 56 | 57 | @interface XLDataLoader : NSObject 58 | { 59 | BOOL _isLoadingData; 60 | BOOL _hasMoreToLoad; 61 | NSUInteger _offset; 62 | NSUInteger _limit; 63 | NSString * _searchString; 64 | } 65 | 66 | 67 | @property (weak, nonatomic) id delegate; 68 | @property (weak, nonatomic) id storeDelegate; 69 | 70 | @property (readonly) NSString * URLString; 71 | @property NSUInteger offset; 72 | @property NSUInteger limit; 73 | @property NSString * searchString; 74 | @property (nonatomic) NSMutableDictionary * parameters; 75 | @property (nonatomic) NSString * collectionKeyPath; 76 | @property (readonly) NSDictionary * loadedData; 77 | @property (readonly) NSArray * loadedDataItems; 78 | 79 | -(instancetype)initWithURLString:(NSString *)URLString; 80 | -(instancetype)initWithURLString:(NSString *)URLString 81 | offsetParamName:(NSString *)offsetParamName 82 | limitParamName:(NSString *)limitParamName 83 | searchStringParamName:(NSString *)searchStringParamName; 84 | 85 | -(void)load; 86 | -(void)forceLoad:(BOOL)defaultValues; 87 | 88 | -(BOOL)isLoadingData; 89 | -(BOOL)hasMoreToLoad; 90 | 91 | -(void)cancelRequest; // cancels the active request 92 | 93 | @end 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /XLData/XL/CoreRemote/Loader/XLDataLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | 27 | #import 28 | #import "XLDataLoader.h" 29 | 30 | NSString * const kXLDataLoaderErrorDomain = @"XLDataLoaderErrorDomain"; 31 | NSString * const kXLRemoteDataLoaderDefaultKeyForNonDictionaryResponse = @"data"; 32 | 33 | @interface XLDataLoader() 34 | { 35 | NSURLSessionDataTask * _task; 36 | } 37 | 38 | @property NSUInteger expiryTimeInterval; 39 | @property NSDictionary * loadedData; 40 | @property NSString * offsetParamName; 41 | @property NSString * limitParamName; 42 | @property NSString * searchStingParamName; 43 | 44 | @end 45 | 46 | @implementation XLDataLoader 47 | 48 | 49 | // configutration properties 50 | @synthesize expiryTimeInterval = _expiryTimeInterval; 51 | 52 | // page paroperties 53 | @synthesize offset = _offset; 54 | @synthesize limit = _limit; 55 | 56 | // searchString 57 | @synthesize searchString = _searchString; 58 | 59 | 60 | -(instancetype)initWithURLString:(NSString *)urlString 61 | { 62 | self = [super init]; 63 | if (self){ 64 | [self setDefaultValues]; 65 | _URLString = urlString; 66 | _offsetParamName = @"offset"; 67 | _limitParamName = @"limit"; 68 | _searchStingParamName = @"search"; 69 | _limit = 0; 70 | } 71 | return self; 72 | } 73 | 74 | -(instancetype)initWithURLString:(NSString *)urlString offsetParamName:(NSString *)offsetParamName limitParamName:(NSString *)limitParamName searchStringParamName:(NSString *)searchStringParamName 75 | { 76 | self = [self initWithURLString:urlString]; 77 | if (self){ 78 | self.offsetParamName = offsetParamName; 79 | self.limitParamName = limitParamName; 80 | self.searchStingParamName = searchStringParamName; 81 | } 82 | return self; 83 | } 84 | 85 | -(void)setDefaultValues 86 | { 87 | _task = nil; 88 | _offset = 0; 89 | _loadedData = nil; 90 | _isLoadingData = NO; 91 | _hasMoreToLoad = YES; 92 | } 93 | 94 | -(NSDictionary *)getParameters 95 | { 96 | NSMutableDictionary * result = [self.parameters mutableCopy]; 97 | if (self.limit != 0){ 98 | [result addEntriesFromDictionary:@{self.offsetParamName : @(self.offset), self.limitParamName : @(self.limit) }]; 99 | } 100 | if (self.searchString.length > 0){ 101 | [result addEntriesFromDictionary:@{self.searchStingParamName : self.searchString }]; 102 | } 103 | return result; 104 | } 105 | 106 | -(BOOL)isLoadingData 107 | { 108 | return _isLoadingData; 109 | } 110 | 111 | -(BOOL)hasMoreToLoad 112 | { 113 | return _hasMoreToLoad; 114 | } 115 | 116 | -(NSArray *)loadedDataItems 117 | { 118 | if (self.loadedData){ 119 | return [self.loadedData valueForKeyPath:self.collectionKeyPath]; 120 | } 121 | return nil; 122 | } 123 | 124 | -(NSURLSessionDataTask *)prepareURLSessionTask 125 | { 126 | NSMutableURLRequest * request = self.prepareURLRequest; 127 | XLDataLoader * __weak weakSelf = self; 128 | return [[self sessionManagerForDataLoader:self] dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { 129 | if (error) { 130 | if (responseObject){ 131 | NSMutableDictionary * newUserInfo = [error.userInfo mutableCopy]; 132 | [newUserInfo setObject:responseObject forKey:AFNetworkingTaskDidCompleteSerializedResponseKey]; 133 | NSError * newError = [NSError errorWithDomain:error.domain code:error.code userInfo:newUserInfo]; 134 | [weakSelf dataLoaderDidFailLoadData:weakSelf withError:newError]; 135 | } 136 | else{ 137 | [weakSelf dataLoaderDidFailLoadData:weakSelf withError:error]; 138 | } 139 | } else { 140 | NSDictionary * data = [responseObject isKindOfClass:[NSDictionary class]] ? responseObject : @{ weakSelf.collectionKeyPath : responseObject }; 141 | if ([weakSelf.storeDelegate respondsToSelector:@selector(dataLoader:convertJsonDataToModelObject:)]){ 142 | data = [self.storeDelegate dataLoader:weakSelf convertJsonDataToModelObject:data]; 143 | } 144 | self.loadedData = data; 145 | void (^completionHandler)() = ^{ 146 | dispatch_async(dispatch_get_main_queue(), ^{ 147 | _isLoadingData = NO; 148 | _hasMoreToLoad = (weakSelf.limit != 0 && (weakSelf.loadedDataItems.count >= weakSelf.limit)); 149 | [weakSelf dataLoaderDidLoadData:weakSelf]; 150 | }); 151 | }; 152 | if ([weakSelf.storeDelegate respondsToSelector:@selector(dataLoaderUpdateDataStore:completionHandler:)]){ 153 | [weakSelf.storeDelegate dataLoaderUpdateDataStore:weakSelf completionHandler:completionHandler]; 154 | } 155 | else{ 156 | completionHandler(); 157 | } 158 | } 159 | }]; 160 | } 161 | 162 | -(NSMutableURLRequest *)prepareURLRequest 163 | { 164 | NSError * error; 165 | AFHTTPSessionManager * sessionManager = [self.delegate sessionManagerForDataLoader:self]; 166 | return [sessionManager.requestSerializer requestWithMethod:@"GET" URLString:[[NSURL URLWithString:self.URLString relativeToURL:sessionManager.baseURL] absoluteString] parameters:[self getParameters] error:&error]; 167 | } 168 | 169 | -(void)cancelRequest 170 | { 171 | [_task cancel]; 172 | _task = nil; 173 | } 174 | 175 | -(void)load 176 | { 177 | if (!_isLoadingData){ 178 | _isLoadingData = YES; 179 | _task = [self prepareURLSessionTask]; 180 | [_task resume]; 181 | [self dataLoaderDidStartLoadingData:self]; 182 | } 183 | } 184 | 185 | 186 | -(void)forceLoad:(BOOL)defaultValues 187 | { 188 | if (_task){ 189 | [self cancelRequest]; 190 | } 191 | if (defaultValues){ 192 | [self setDefaultValues]; 193 | } 194 | [self load]; 195 | } 196 | 197 | 198 | #pragma mark - Properties 199 | 200 | -(NSString *)collectionKeyPath 201 | { 202 | if (_collectionKeyPath) return _collectionKeyPath; 203 | return kXLRemoteDataLoaderDefaultKeyForNonDictionaryResponse; 204 | } 205 | 206 | -(NSMutableDictionary *)parameters 207 | { 208 | if (_parameters) return _parameters; 209 | _parameters = [[NSMutableDictionary alloc] init]; 210 | return _parameters; 211 | } 212 | 213 | #pragma mark - XLDataLoaderDelegate 214 | 215 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader 216 | { 217 | if (self.delegate != self){ 218 | return [self.delegate sessionManagerForDataLoader:dataLoader]; 219 | } 220 | @throw [NSException exceptionWithName:NSGenericException 221 | reason:[NSString stringWithFormat:@"%s must be overridden in a subclass", __PRETTY_FUNCTION__] 222 | userInfo:nil]; 223 | } 224 | 225 | -(void)dataLoaderDidStartLoadingData:(XLDataLoader *)dataLoader 226 | { 227 | if (self.delegate != self && [self.delegate respondsToSelector:@selector(dataLoaderDidStartLoadingData:)]){ 228 | [self.delegate dataLoaderDidStartLoadingData:dataLoader]; 229 | } 230 | } 231 | 232 | -(void)dataLoaderDidFailLoadData:(XLDataLoader *)dataLoader withError:(NSError *)error 233 | { 234 | // change flags 235 | _isLoadingData = NO; 236 | if (self.delegate != self && [self.delegate respondsToSelector:@selector(dataLoaderDidFailLoadData:withError:)]){ 237 | [self.delegate dataLoaderDidFailLoadData:self withError:error]; 238 | } 239 | } 240 | 241 | -(void)dataLoaderDidLoadData:(XLDataLoader *)dataLoader 242 | { 243 | if (self.delegate != self && [self.delegate respondsToSelector:@selector(dataLoaderDidLoadData:)]){ 244 | [self.delegate dataLoaderDidLoadData:self]; 245 | } 246 | } 247 | 248 | 249 | 250 | @end 251 | -------------------------------------------------------------------------------- /XLData/XL/CoreRemote/Loader/XLRemoteControllerDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // XLRemoteControllerDelegate.h 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import Foundation; 27 | #import 28 | #import "XLDataLoader.h" 29 | 30 | typedef NS_OPTIONS(NSUInteger, XLRemoteControllerOptions) { 31 | XLRemoteDataStoreControllerOptionSupportRefreshControl = 1 << 1, 32 | XLRemoteDataStoreControllerOptionPagingEnabled = 1 << 2, 33 | XLRemoteDataStoreControllerOptionShowNetworkReachability = 1 << 3, 34 | XLRemoteDataStoreControllerOptionShowNetworkConnectivityErrors = 1 << 4, 35 | XLRemoteDataStoreControllerOptionsFetchOnlyOnce = 1 << 5, 36 | XLRemoteDataStoreControllerOptionsSkipInitialFetch = 1 << 6, 37 | XLRemoteDataStoreControllerOptionDefault = XLRemoteDataStoreControllerOptionSupportRefreshControl | XLRemoteDataStoreControllerOptionPagingEnabled | XLRemoteDataStoreControllerOptionShowNetworkReachability 38 | }; 39 | 40 | #import "XLDataLoader.h" 41 | 42 | @protocol XLRemoteControllerDelegate 43 | 44 | @required 45 | -(void)dataController:(UIViewController *)controller showNoInternetConnection:(BOOL)animated; 46 | -(void)dataController:(UIViewController *)controller hideNoInternetConnection:(BOOL)animated; 47 | 48 | @end 49 | 50 | -------------------------------------------------------------------------------- /XLData/XL/Local/CoreData/XLCoreDataController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XLCoreDataController.h 3 | // XLForm ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import UIKit; 27 | @import CoreData; 28 | #import "XLData.h" 29 | 30 | @interface XLCoreDataController : UIViewController 31 | 32 | /** 33 | * Convenient Initializer to create a view controller that handles a table view or a collection view. 34 | * 35 | * @param controllerType type of view that the view controller manages. 36 | * 37 | * @return initialized XLCoreDataController instance. 38 | */ 39 | - (instancetype)initWithDataStoreControllerType:(XLDataControllerType)controllerType; 40 | - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 41 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; 42 | 43 | /** 44 | * NSFetchedResultsController used to load the data and keeps track of core data context in order to update the table/collection view accordingly. 45 | */ 46 | @property NSFetchedResultsController * fetchedResultsController; 47 | 48 | /** 49 | * returns the type of the controller. Data can be shown using a table view or a collection view depending of the initializer invoked or if you connect the table view or the collection view outlets. 50 | */ 51 | @property (nonatomic, readonly) XLDataControllerType dataStoreControllerType; 52 | 53 | @property (nonatomic) IBOutlet UITableView * tableView; 54 | @property (nonatomic) IBOutlet UICollectionView * collectionView; 55 | 56 | @property (nonatomic) IBOutlet UISearchBar * searchBar; 57 | 58 | /** 59 | * View used as background when tableView or collectionView is Empty. 60 | */ 61 | @property (nonatomic) IBOutlet UIView * emptyDataSetView; 62 | 63 | -(void)reloadDataSet; 64 | 65 | 66 | /** 67 | * You can invoke this method to update the NSFetchedResultController or the NSFetchRequest contained by it. It will also reload the view (tableView or collectionView). It will call updateFetchedResultConttroller allowing the developer to either return a new result Controller or its properies (sort, predicate, etc) 68 | */ 69 | -(void)updateFetchedResultControllerIfNeeded; 70 | 71 | @end 72 | 73 | 74 | @interface XLCoreDataController (__Protected) 75 | 76 | /** 77 | * UIAlertView is used by default to show errors. Override if you want to show the error differently. 78 | * 79 | * @param error Error to be shown. 80 | */ 81 | -(void)showError:(NSError*)error; 82 | 83 | /** 84 | * This method is invoked to allow you to update the NSFetchRequest or return a new NSFetchedResultsController instance. 85 | */ 86 | -(NSFetchedResultsController *)updateFetchedResultConttroller; 87 | 88 | 89 | /** 90 | * This method is invoked each time the table or collection view content changes. 91 | */ 92 | -(void)didChangeContent; 93 | 94 | /** 95 | * Override if you want to show the emptyStateView with a differnt animation. 96 | * 97 | * @param animated if YES perform a animated transition/ 98 | */ 99 | -(void)showEmptyStateView:(BOOL)animated; 100 | /** 101 | * Override if you want to hide the emptyStateView with a differnt animation. 102 | * 103 | * @param animated if YES perform a animated transition/ 104 | */ 105 | -(void)hideEmptyStateView:(BOOL)animated; 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /XLData/XL/Local/DataStore/XLDataStoreController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XLDataStoreController.h 3 | // XLForm ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLDataStore.h" 27 | #import "XLData.h" 28 | @import UIKit; 29 | 30 | @interface XLDataStoreController : UIViewController 31 | 32 | /** 33 | * Convenient Initializer to create a view controller that handles a table view or a collection view. 34 | * 35 | * @param controllerType type of view that the view controller manages. 36 | * 37 | * @return initialized XLCoreDataController instance. 38 | */ 39 | - (instancetype)initWithDataStoreControllerType:(XLDataControllerType)controllerType; 40 | - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 41 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; 42 | 43 | /** 44 | * data store used to load the data, view controller keeps track of it in order to update the table/collection view accordingly. 45 | */ 46 | @property XLDataStore * dataStore; 47 | 48 | /** 49 | * returns the type of the controller. Data can be shown using a table view or a collection view depending of the initializer invoked or if you connect the table view or the collection view outlets. 50 | */ 51 | @property (nonatomic, readonly) XLDataControllerType dataStoreControllerType; 52 | 53 | @property (nonatomic) IBOutlet UITableView * tableView; 54 | @property (nonatomic) IBOutlet UICollectionView * collectionView; 55 | 56 | @property (nonatomic) IBOutlet UISearchBar * searchBar; 57 | 58 | /** 59 | * View used as background when tableView or collectionView is Empty. 60 | */ 61 | @property (nonatomic) IBOutlet UIView * emptyDataSetView; 62 | 63 | 64 | -(void)reloadDataSet; 65 | 66 | @end 67 | 68 | 69 | @interface XLDataStoreController (__Protected) 70 | 71 | /** 72 | * UIAlertView is used by default to show errors. Override if you want to show the error differently. 73 | * 74 | * @param error Error to be shown. 75 | */ 76 | -(void)showError:(NSError*)error; 77 | 78 | /** 79 | * This method is invoked each time the table or collection view content changes. 80 | */ 81 | -(void)didChangeContent; 82 | 83 | 84 | /** 85 | * Override if you want to show the emptyStateView with a differnt animation. 86 | * 87 | * @param animated if YES perform a animated transition/ 88 | */ 89 | -(void)showEmptyStateView:(BOOL)animated; 90 | /** 91 | * Override if you want to hide the emptyStateView with a differnt animation. 92 | * 93 | * @param animated if YES perform a animated transition/ 94 | */ 95 | -(void)hideEmptyStateView:(BOOL)animated; 96 | 97 | 98 | /** 99 | * Override to change the animation used when a section is added, removed, etc. 100 | * 101 | * @param dataStoreChange kind of section data store modification 102 | * 103 | * @return animation to be used 104 | */ 105 | -(UITableViewRowAnimation)tableViewAnimationForDataStoreSectionChange:(XLDataStoreChangeType)dataStoreChange; 106 | 107 | 108 | /** 109 | * Override to change the animation used when a item is added, removed, etc. 110 | * 111 | * @param dataStoreChange kind of item data store modification 112 | * 113 | * @return animation to be used 114 | */ 115 | -(UITableViewRowAnimation)tableViewAnimationForDataStoreItemChange:(XLDataStoreChangeType)dataStoreChange; 116 | 117 | @end 118 | 119 | -------------------------------------------------------------------------------- /XLData/XL/Remote/CoreData/XLRemoteCoreDataController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import UIKit; 27 | #import "XLCoreDataController.h" 28 | #import "XLDataLoader.h" 29 | #import "XLSearchBar.h" 30 | #import "XLRemoteControllerDelegate.h" 31 | 32 | @protocol XLRemoteControllerDelegate; 33 | 34 | @interface XLRemoteCoreDataController : XLCoreDataController 35 | 36 | - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 37 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; 38 | 39 | /** 40 | * XLDataLoader helps us out with networking stuff. Concrete view controller implementation must conforms to XLDataLoaderDelegate and provides it with the necessary data (url, session manager, etc) to make it work. 41 | */ 42 | @property (nonatomic) XLDataLoader * dataLoader; 43 | 44 | /** 45 | * You can use options property to enable/disable controller functionality. 46 | */ 47 | @property XLRemoteControllerOptions options; 48 | 49 | /** 50 | * networkStatusView is shown when device doesn't have internet connection 51 | */ 52 | @property (nonatomic) IBOutlet UIView * networkStatusView; 53 | @property (readonly) UIRefreshControl * refreshControl; 54 | 55 | @property id remoteControllerDelegate; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /XLData/XL/Remote/CoreData/XLRemoteCoreDataController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLDataStore.h" 27 | #import "XLNetworkStatusView.h" 28 | #import "XLSearchBar.h" 29 | #import "UIScrollView+SVInfiniteScrolling.h" 30 | #import "XLRemoteCoreDataController.h" 31 | 32 | 33 | @implementation XLRemoteCoreDataController 34 | { 35 | UIView * _networkStatusView; 36 | NSTimer * _searchDelayTimer; 37 | BOOL _isConnectedToInternet; 38 | } 39 | 40 | @synthesize dataLoader = _dataLoader; 41 | @synthesize refreshControl = _refreshControl; 42 | @synthesize remoteControllerDelegate = _remoteControllerDelegate; 43 | 44 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 45 | { 46 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 47 | if (self){ 48 | [self initializeXLRemoteCoreDataController]; 49 | } 50 | return self; 51 | } 52 | 53 | - (instancetype)initWithCoder:(NSCoder *)coder 54 | { 55 | self = [super initWithCoder:coder]; 56 | if (self) { 57 | [self initializeXLRemoteCoreDataController]; 58 | } 59 | return self; 60 | } 61 | 62 | -(void)dealloc 63 | { 64 | self.collectionView.delegate = nil; 65 | self.collectionView.dataSource = nil; 66 | self.tableView.delegate = nil; 67 | self.tableView.dataSource = nil; 68 | } 69 | 70 | -(void)initializeXLRemoteCoreDataController 71 | { 72 | _networkStatusView = nil; 73 | _searchDelayTimer = nil; 74 | self.options = XLRemoteDataStoreControllerOptionDefault; 75 | self.dataLoader = nil; 76 | _isConnectedToInternet = YES; 77 | } 78 | 79 | #pragma mark - Properties 80 | 81 | -(UIRefreshControl *)refreshControl 82 | { 83 | if (_refreshControl) return _refreshControl; 84 | _refreshControl = [[UIRefreshControl alloc] init]; 85 | [_refreshControl addTarget:self action:@selector(refreshView:) forControlEvents:UIControlEventValueChanged]; 86 | return _refreshControl; 87 | } 88 | 89 | -(UIView *)networkStatusView 90 | { 91 | if (!_networkStatusView){ 92 | _networkStatusView = [XLNetworkStatusView new]; 93 | _networkStatusView.translatesAutoresizingMaskIntoConstraints = NO; 94 | [self.view addSubview:_networkStatusView]; 95 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[networkStatusView]|" options:0 metrics:0 views:@{@"networkStatusView": _networkStatusView}]]; 96 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide][networkStatusView(30)]" options:0 metrics:0 views:@{@"networkStatusView": _networkStatusView, @"topLayoutGuide": self.topLayoutGuide}]]; 97 | [self.view sendSubviewToBack:_networkStatusView]; 98 | } 99 | return _networkStatusView; 100 | } 101 | 102 | 103 | -(id)remoteControllerDelegate 104 | { 105 | if (_remoteControllerDelegate){ 106 | return _remoteControllerDelegate; 107 | } 108 | return self; 109 | } 110 | 111 | -(void)setRemoteControllerDelegate:(id)remoteControllerDelegate 112 | { 113 | _remoteControllerDelegate = remoteControllerDelegate; 114 | } 115 | 116 | #pragma mark - UIViewController life cycle. 117 | 118 | 119 | - (void)viewDidLoad 120 | { 121 | [super viewDidLoad]; 122 | if ((self.options & XLRemoteDataStoreControllerOptionSupportRefreshControl) == XLRemoteDataStoreControllerOptionSupportRefreshControl){ 123 | if (self.dataStoreControllerType == XLDataStoreControllerTypeTableView){ 124 | [self.tableView addSubview:self.refreshControl]; 125 | } 126 | else{ 127 | [self.collectionView addSubview:self.refreshControl]; 128 | } 129 | } 130 | if ((self.options & XLRemoteDataStoreControllerOptionPagingEnabled) == XLRemoteDataStoreControllerOptionPagingEnabled){ 131 | __typeof__(self) __weak weakSelf = self; 132 | if (self.dataStoreControllerType == XLDataStoreControllerTypeTableView){ 133 | [self.tableView addInfiniteScrollingWithActionHandler:^{ 134 | if (!weakSelf.dataLoader.isLoadingData){ 135 | [weakSelf.tableView.infiniteScrollingView startAnimating]; 136 | weakSelf.dataLoader.offset = weakSelf.dataLoader.offset + weakSelf.dataLoader.limit; 137 | [weakSelf.dataLoader load]; 138 | } 139 | }]; 140 | } 141 | else{ 142 | [self.collectionView addInfiniteScrollingWithActionHandler:^{ 143 | if (!weakSelf.dataLoader.isLoadingData){ 144 | [weakSelf.collectionView.infiniteScrollingView startAnimating]; 145 | weakSelf.dataLoader.offset = weakSelf.dataLoader.offset + weakSelf.dataLoader.limit; 146 | [weakSelf.dataLoader load]; 147 | } 148 | }]; 149 | } 150 | } 151 | } 152 | 153 | 154 | 155 | -(void)viewWillAppear:(BOOL)animated 156 | { 157 | [super viewWillAppear:animated]; 158 | [self.fetchedResultsController.fetchRequest setFetchLimit:(self.dataLoader.limit == 0 ? 0 : (self.dataLoader.offset + self.dataLoader.limit))]; 159 | NSError * error; 160 | [self.fetchedResultsController performFetch:&error]; 161 | self.dataStoreControllerType == XLDataStoreControllerTypeTableView ? [self.tableView reloadData] : [self.collectionView reloadData]; 162 | 163 | if (!((self.options & XLRemoteDataStoreControllerOptionsFetchOnlyOnce) == XLRemoteDataStoreControllerOptionsFetchOnlyOnce)|| self.isBeingPresented || self.isMovingToParentViewController){ 164 | if (!((self.options & XLRemoteDataStoreControllerOptionsSkipInitialFetch) == XLRemoteDataStoreControllerOptionsSkipInitialFetch)){ 165 | [self.dataLoader forceLoad:NO]; 166 | } 167 | } 168 | if ((self.options & XLRemoteDataStoreControllerOptionShowNetworkReachability) == XLRemoteDataStoreControllerOptionShowNetworkReachability){ 169 | [[NSNotificationCenter defaultCenter] addObserver:self 170 | selector:@selector(networkingReachabilityDidChange:) 171 | name:AFNetworkingReachabilityDidChangeNotification 172 | object:nil]; 173 | [self updateNoInternetConnectionOverlayIfNeeded:NO]; 174 | } 175 | } 176 | 177 | -(void)viewDidDisappear:(BOOL)animated 178 | { 179 | [super viewDidDisappear:animated]; 180 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil]; 181 | } 182 | 183 | -(void)refreshView:(UIRefreshControl *)refresh { 184 | self.fetchedResultsController.fetchRequest.fetchOffset = 0; 185 | self.fetchedResultsController.fetchRequest.fetchLimit = self.dataLoader.limit; 186 | [self.fetchedResultsController performFetch:nil]; 187 | if (self.dataLoader){ 188 | [[self dataSetView] reloadData]; 189 | [self.dataLoader forceLoad:YES]; 190 | } 191 | else{ 192 | [[[self dataSetView] infiniteScrollingView] stopAnimating]; 193 | [self.refreshControl endRefreshing]; 194 | } 195 | } 196 | 197 | 198 | #pragma mark - XLRemoteControllerDelegate 199 | 200 | -(void)dataController:(UIViewController *)controller showNoInternetConnection:(BOOL)animated 201 | { 202 | __weak __typeof(self)weakSelf = self; 203 | weakSelf.networkStatusView.alpha = 0.0; 204 | [self.networkStatusView.superview bringSubviewToFront:self.networkStatusView]; 205 | [UIView animateWithDuration:(animated ? 0.5 : 0.0) 206 | delay:0.0 207 | options:(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveLinear) 208 | animations:^{ 209 | weakSelf.networkStatusView.alpha = 1.0f; 210 | } 211 | completion:nil]; 212 | } 213 | 214 | -(void)dataController:(UIViewController *)controller hideNoInternetConnection:(BOOL)animated 215 | { 216 | __weak __typeof(self)weakSelf = self; 217 | [UIView animateWithDuration:(animated ? 0.5 : 0.0) delay:0.0 218 | options:(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationCurveLinear) 219 | animations:^{ 220 | weakSelf.networkStatusView.alpha = 0.0f; 221 | } 222 | completion:^(BOOL finished) { 223 | if (finished && _isConnectedToInternet){ 224 | [weakSelf.networkStatusView.superview sendSubviewToBack:weakSelf.networkStatusView]; 225 | } 226 | }]; 227 | } 228 | 229 | #pragma mark - XLDataLoaderDelegate 230 | 231 | -(void)dataLoaderDidStartLoadingData:(XLDataLoader *)dataLoader 232 | { 233 | if (dataLoader == self.dataLoader){ 234 | if ((self.options & XLRemoteDataStoreControllerOptionPagingEnabled) == XLRemoteDataStoreControllerOptionPagingEnabled){ 235 | [[[self dataSetView] infiniteScrollingView] startAnimating]; 236 | } 237 | } 238 | } 239 | 240 | -(void)dataLoaderDidLoadData:(XLDataLoader *)dataLoader 241 | { 242 | if (dataLoader == self.dataLoader){ 243 | UIScrollView * scrollView = [self dataSetView]; 244 | [scrollView.infiniteScrollingView stopAnimating]; 245 | [self.refreshControl endRefreshing]; 246 | scrollView.infiniteScrollingView.enabled = dataLoader.hasMoreToLoad; 247 | [self.fetchedResultsController.fetchRequest setFetchLimit:(self.dataLoader.limit == 0 ? 0 : (self.dataLoader.offset + self.dataLoader.limit))]; 248 | } 249 | } 250 | 251 | -(void)dataLoaderDidFailLoadData:(XLDataLoader *)dataLoader withError:(NSError *)error 252 | { 253 | if (dataLoader == self.dataLoader){ 254 | [[[self dataSetView] infiniteScrollingView] stopAnimating]; 255 | [self.refreshControl endRefreshing]; 256 | } 257 | if (error.code != NSURLErrorCancelled && (error.code != NSURLErrorNotConnectedToInternet || ((self.options & XLRemoteDataStoreControllerOptionShowNetworkConnectivityErrors) == XLRemoteDataStoreControllerOptionShowNetworkConnectivityErrors))){ 258 | [self showError:error]; 259 | } 260 | } 261 | 262 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader 263 | { 264 | @throw [NSException exceptionWithName:NSGenericException 265 | reason:[NSString stringWithFormat:@"%s must be overridden in a subclass", __PRETTY_FUNCTION__] 266 | userInfo:nil]; 267 | } 268 | 269 | 270 | #pragma mark - Helpers 271 | 272 | -(void)networkingReachabilityDidChange:(NSNotification *)notification 273 | { 274 | [self updateNoInternetConnectionOverlayIfNeeded:YES]; 275 | } 276 | 277 | -(void)updateNoInternetConnectionOverlayIfNeeded:(BOOL)animated 278 | { 279 | AFHTTPSessionManager * sessionManager; 280 | if ( !(sessionManager = ([self sessionManagerForDataLoader:self.dataLoader])) || (_isConnectedToInternet = ([sessionManager.reachabilityManager 281 | networkReachabilityStatus] != AFNetworkReachabilityStatusNotReachable))){ 282 | [self.remoteControllerDelegate dataController:self hideNoInternetConnection:animated]; 283 | } 284 | else{ 285 | [self.remoteControllerDelegate dataController:self showNoInternetConnection:animated]; 286 | } 287 | } 288 | 289 | -(id)dataSetView 290 | { 291 | 292 | if (self.dataStoreControllerType == XLDataStoreControllerTypeTableView){ 293 | return self.tableView; 294 | } 295 | return self.collectionView; 296 | } 297 | 298 | #pragma mark - UISearchResultsUpdating 299 | 300 | - (void)updateSearchResultsForSearchController:(UISearchController *)searchController; 301 | { 302 | [super updateSearchResultsForSearchController:searchController]; 303 | [self.dataLoader setSearchString:[searchController.searchBar.text copy]]; 304 | [self.dataLoader forceLoad:YES]; 305 | } 306 | 307 | 308 | @end 309 | -------------------------------------------------------------------------------- /XLData/XL/Remote/DataStore/XLRemoteDataStoreController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | @import UIKit; 27 | #import "XLDataStoreController.h" 28 | #import "XLDataStore.h" 29 | #import "XLDataLoader.h" 30 | #import "XLSearchBar.h" 31 | #import "XLRemoteControllerDelegate.h" 32 | 33 | @interface XLRemoteDataStoreController : XLDataStoreController 34 | 35 | - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 36 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER; 37 | 38 | /** 39 | * XLDataLoader helps us out with networking stuff. Concrete view controller implementation must conforms to XLDataLoaderDelegate and provides it with the necessary data (url, session manager, etc) to make it work. 40 | */ 41 | @property (nonatomic) XLDataLoader * dataLoader; 42 | 43 | /** 44 | * You can use options property to enable/disable controller functionality. 45 | */ 46 | @property XLRemoteControllerOptions options; 47 | 48 | /** 49 | * networkStatusView is shown when device doesn't have internet connection 50 | */ 51 | @property (nonatomic) IBOutlet UIView * networkStatusView; 52 | @property (readonly) UIRefreshControl * refreshControl; 53 | 54 | @property id remoteControllerDelegate; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /XLData/XL/Remote/DataStore/XLRemoteDataStoreController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XLRemoteDataStoreController.m 3 | // XLData ( https://github.com/xmartlabs/XLData ) 4 | // 5 | // Copyright (c) 2015 Xmartlabs ( http://xmartlabs.com ) 6 | // 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "XLDataStore.h" 27 | #import "XLNetworkStatusView.h" 28 | #import "XLSearchBar.h" 29 | #import "UIScrollView+SVInfiniteScrolling.h" 30 | #import "XLRemoteDataStoreController.h" 31 | 32 | 33 | @implementation XLRemoteDataStoreController 34 | { 35 | UIView * _networkStatusView; 36 | NSTimer * _searchDelayTimer; 37 | BOOL _isConnectedToInternet; 38 | } 39 | 40 | @synthesize dataLoader = _dataLoader; 41 | @synthesize refreshControl = _refreshControl; 42 | @synthesize remoteControllerDelegate = _remoteControllerDelegate; 43 | 44 | 45 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 46 | { 47 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 48 | if (self){ 49 | [self initializeXLRemoteDataStoreController]; 50 | } 51 | return self; 52 | } 53 | 54 | - (instancetype)initWithCoder:(NSCoder *)coder 55 | { 56 | self = [super initWithCoder:coder]; 57 | if (self) { 58 | [self initializeXLRemoteDataStoreController]; 59 | } 60 | return self; 61 | } 62 | 63 | -(void)dealloc 64 | { 65 | self.collectionView.delegate = nil; 66 | self.collectionView.dataSource = nil; 67 | self.tableView.delegate = nil; 68 | self.tableView.dataSource = nil; 69 | } 70 | 71 | -(void)initializeXLRemoteDataStoreController 72 | { 73 | _networkStatusView = nil; 74 | _searchDelayTimer = nil; 75 | self.options = XLRemoteDataStoreControllerOptionDefault; 76 | self.dataLoader = nil; 77 | _isConnectedToInternet = YES; 78 | } 79 | 80 | #pragma mark - Properties 81 | 82 | -(UIRefreshControl *)refreshControl 83 | { 84 | if (_refreshControl) return _refreshControl; 85 | _refreshControl = [[UIRefreshControl alloc] init]; 86 | [_refreshControl addTarget:self action:@selector(refreshView:) forControlEvents:UIControlEventValueChanged]; 87 | return _refreshControl; 88 | } 89 | 90 | -(UIView *)networkStatusView 91 | { 92 | if (!_networkStatusView){ 93 | _networkStatusView = [XLNetworkStatusView new]; 94 | _networkStatusView.translatesAutoresizingMaskIntoConstraints = NO; 95 | [self.view addSubview:_networkStatusView]; 96 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[networkStatusView]|" options:0 metrics:0 views:@{@"networkStatusView": _networkStatusView}]]; 97 | [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide][networkStatusView(30)]" options:0 metrics:0 views:@{@"networkStatusView": _networkStatusView, @"topLayoutGuide": self.topLayoutGuide}]]; 98 | [self.view sendSubviewToBack:_networkStatusView]; 99 | } 100 | return _networkStatusView; 101 | } 102 | 103 | -(id)remoteControllerDelegate 104 | { 105 | if (_remoteControllerDelegate){ 106 | return _remoteControllerDelegate; 107 | } 108 | return self; 109 | } 110 | 111 | -(void)setRemoteControllerDelegate:(id)remoteControllerDelegate 112 | { 113 | _remoteControllerDelegate = remoteControllerDelegate; 114 | } 115 | 116 | #pragma mark - UIViewController life cycle. 117 | 118 | 119 | - (void)viewDidLoad 120 | { 121 | [super viewDidLoad]; 122 | if ((self.options & XLRemoteDataStoreControllerOptionSupportRefreshControl) == XLRemoteDataStoreControllerOptionSupportRefreshControl){ 123 | if (self.dataStoreControllerType == XLDataStoreControllerTypeTableView){ 124 | [self.tableView addSubview:self.refreshControl]; 125 | } 126 | else{ 127 | [self.collectionView addSubview:self.refreshControl]; 128 | } 129 | } 130 | if ((self.options & XLRemoteDataStoreControllerOptionPagingEnabled) == XLRemoteDataStoreControllerOptionPagingEnabled){ 131 | __typeof__(self) __weak weakSelf = self; 132 | if (self.dataStoreControllerType == XLDataStoreControllerTypeTableView){ 133 | [self.tableView addInfiniteScrollingWithActionHandler:^{ 134 | if (!weakSelf.dataLoader.isLoadingData){ 135 | [weakSelf.tableView.infiniteScrollingView startAnimating]; 136 | weakSelf.dataLoader.offset = weakSelf.dataLoader.offset + weakSelf.dataLoader.limit; 137 | [weakSelf.dataLoader load]; 138 | } 139 | }]; 140 | } 141 | else{ 142 | [self.collectionView addInfiniteScrollingWithActionHandler:^{ 143 | if (!weakSelf.dataLoader.isLoadingData){ 144 | [weakSelf.collectionView.infiniteScrollingView startAnimating]; 145 | weakSelf.dataLoader.offset = weakSelf.dataLoader.offset + weakSelf.dataLoader.limit; 146 | [weakSelf.dataLoader load]; 147 | } 148 | }]; 149 | } 150 | } 151 | } 152 | 153 | -(void)viewWillAppear:(BOOL)animated 154 | { 155 | [super viewWillAppear:animated]; 156 | if (!((self.options & XLRemoteDataStoreControllerOptionsFetchOnlyOnce) == XLRemoteDataStoreControllerOptionsFetchOnlyOnce)|| self.isBeingPresented || self.isMovingToParentViewController){ 157 | if (!((self.options & XLRemoteDataStoreControllerOptionsSkipInitialFetch) == XLRemoteDataStoreControllerOptionsSkipInitialFetch)){ 158 | [self.dataLoader forceLoad:NO]; 159 | } 160 | } 161 | if ((self.options & XLRemoteDataStoreControllerOptionShowNetworkReachability) == XLRemoteDataStoreControllerOptionShowNetworkReachability){ 162 | [[NSNotificationCenter defaultCenter] addObserver:self 163 | selector:@selector(networkingReachabilityDidChange:) 164 | name:AFNetworkingReachabilityDidChangeNotification 165 | object:nil]; 166 | [self updateNoInternetConnectionOverlayIfNeeded:NO]; 167 | } 168 | } 169 | 170 | -(void)viewDidDisappear:(BOOL)animated 171 | { 172 | [super viewDidDisappear:animated]; 173 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil]; 174 | } 175 | 176 | -(void)refreshView:(UIRefreshControl *)refresh { 177 | if (self.dataLoader){ 178 | [[self dataSetView] reloadData]; 179 | [self.dataLoader forceLoad:YES]; 180 | }else{ 181 | [[[self dataSetView] infiniteScrollingView] stopAnimating]; 182 | [self.refreshControl endRefreshing]; 183 | } 184 | } 185 | 186 | 187 | #pragma mark - XLDataControllerDelegate 188 | 189 | -(void)dataController:(UIViewController *)controller showNoInternetConnection:(BOOL)animated 190 | { 191 | __weak __typeof(self)weakSelf = self; 192 | weakSelf.networkStatusView.alpha = 0.0; 193 | [self.networkStatusView.superview bringSubviewToFront:self.networkStatusView]; 194 | [UIView animateWithDuration:(animated ? 0.5 : 0.0) 195 | delay:0.0 196 | options:(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveLinear) 197 | animations:^{ 198 | weakSelf.networkStatusView.alpha = 1.0f; 199 | } 200 | completion:nil]; 201 | } 202 | 203 | -(void)dataController:(UIViewController *)controller hideNoInternetConnection:(BOOL)animated 204 | { 205 | __weak __typeof(self)weakSelf = self; 206 | [UIView animateWithDuration:(animated ? 0.5 : 0.0) delay:0.0 207 | options:(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationCurveLinear) 208 | animations:^{ 209 | weakSelf.networkStatusView.alpha = 0.0f; 210 | } 211 | completion:^(BOOL finished) { 212 | if (finished && _isConnectedToInternet){ 213 | [weakSelf.networkStatusView.superview sendSubviewToBack:weakSelf.networkStatusView]; 214 | } 215 | }]; 216 | } 217 | 218 | #pragma mark - XLDataLoaderDelegate 219 | 220 | -(void)dataLoaderDidStartLoadingData:(XLDataLoader *)dataLoader 221 | { 222 | if (dataLoader == self.dataLoader){ 223 | if ((self.options & XLRemoteDataStoreControllerOptionPagingEnabled) == XLRemoteDataStoreControllerOptionPagingEnabled){ 224 | if (self.dataStoreControllerType == XLDataStoreControllerTypeTableView){ 225 | [self.tableView.infiniteScrollingView startAnimating]; 226 | } 227 | else{ 228 | [self.collectionView.infiniteScrollingView startAnimating]; 229 | } 230 | } 231 | } 232 | } 233 | 234 | -(void)dataLoaderDidLoadData:(XLDataLoader *)dataLoader 235 | { 236 | if (dataLoader == self.dataLoader){ 237 | UIScrollView * scrollView = [self dataSetView]; 238 | [scrollView.infiniteScrollingView stopAnimating]; 239 | [self.refreshControl endRefreshing]; 240 | scrollView.infiniteScrollingView.enabled = dataLoader.hasMoreToLoad; 241 | } 242 | } 243 | 244 | -(void)dataLoaderDidFailLoadData:(XLDataLoader *)dataLoader withError:(NSError *)error 245 | { 246 | if (dataLoader == self.dataLoader){ 247 | [[[self dataSetView] infiniteScrollingView] stopAnimating]; 248 | [self.refreshControl endRefreshing]; 249 | } 250 | if (error.code != NSURLErrorCancelled && (error.code != NSURLErrorNotConnectedToInternet || ((self.options & XLRemoteDataStoreControllerOptionShowNetworkConnectivityErrors) == XLRemoteDataStoreControllerOptionShowNetworkConnectivityErrors))){ 251 | [self showError:error]; 252 | } 253 | } 254 | 255 | #pragma mark - XLDataLoaderStoreDelegate 256 | 257 | -(void)dataLoaderUpdateDataStore:(XLDataLoader *)dataLoader completionHandler:(void (^)())completionHandler 258 | { 259 | [[self.dataStore lastSection] addDataItems:dataLoader.loadedDataItems fromIndex:dataLoader.offset]; 260 | completionHandler(); 261 | } 262 | 263 | #pragma mark - Helpers 264 | 265 | -(void)networkingReachabilityDidChange:(NSNotification *)notification 266 | { 267 | [self updateNoInternetConnectionOverlayIfNeeded:YES]; 268 | } 269 | 270 | -(void)updateNoInternetConnectionOverlayIfNeeded:(BOOL)animated 271 | { 272 | AFHTTPSessionManager * sessionManager; 273 | if ( !(sessionManager = ([self sessionManagerForDataLoader:self.dataLoader])) || (_isConnectedToInternet = ([sessionManager.reachabilityManager 274 | networkReachabilityStatus] != AFNetworkReachabilityStatusNotReachable))){ 275 | [self.remoteControllerDelegate dataController:self hideNoInternetConnection:animated]; 276 | } 277 | else{ 278 | [self.remoteControllerDelegate dataController:self showNoInternetConnection:animated]; 279 | } 280 | } 281 | 282 | -(id)dataSetView 283 | { 284 | if (self.dataStoreControllerType == XLDataStoreControllerTypeTableView){ 285 | return self.tableView; 286 | } 287 | return self.collectionView; 288 | } 289 | 290 | #pragma mark - UISearchResultsUpdating 291 | 292 | - (void)updateSearchResultsForSearchController:(UISearchController *)searchController; 293 | { 294 | self.dataStore = nil; 295 | [self.tableView reloadData]; 296 | [self.dataLoader setSearchString:[searchController.searchBar.text copy]]; 297 | [self.dataLoader forceLoad:YES]; 298 | } 299 | 300 | 301 | #pragma mark - XLDataLoaderDelegate 302 | 303 | -(AFHTTPSessionManager *)sessionManagerForDataLoader:(XLDataLoader *)dataLoader 304 | { 305 | @throw [NSException exceptionWithName:NSGenericException 306 | reason:[NSString stringWithFormat:@"%s must be overridden in a subclass", __PRETTY_FUNCTION__] 307 | userInfo:nil]; 308 | } 309 | 310 | @end 311 | -------------------------------------------------------------------------------- /XLData/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // XLData 4 | // 5 | // Created by Martin Barreto on 5/6/15. 6 | // Copyright (c) 2015 Xmartlabs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XLDataTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | BNDL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | 29 | 30 | -------------------------------------------------------------------------------- /XLDataTests/XLDataTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // XLDataTests.m 3 | // XLDataTests 4 | // 5 | // Created by Martin Barreto on 5/6/15. 6 | // Copyright (c) 2015 Xmartlabs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface XLDataTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation XLDataTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------