Advertisement
Guest User

Untitled

a guest
May 26th, 2015
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.56 KB | None | 0 0
  1. /*
  2. This gist presents a way to abstract Yapdatabase views and mappings into a NSFetchedResultsController-like API. If you
  3. are used to CoreData then it may sound familiar.
  4. Why?
  5. I've used this approach in the past because YapDatabase is awesome but incites tight coupling between the ViewController and the database.
  6. I like to keep boundaries flexible, and ViewController is the root of all evil, if not 'controlled' properly :)
  7.  
  8. With this approach, ViewController is the delegate and datasource of your dynamic view (UITableView or UICollectionView), but gets the
  9. data in a controlled manner from the ResultsController.
  10.  
  11. I've used this using a MVVM approach, not the classic MVC, so a the ViewModel would return a valid resultsController that the ViewController
  12. can bind to through it's delegate.
  13. */
  14.  
  15. // ---------- Datasource protocol for 'dynamic' views ---------------------
  16. @protocol MQGDataSource <UITableViewDataSource, UICollectionViewDataSource>
  17.  
  18. - (id)objectAtIndexPath:(NSIndexPath *)indexPath;
  19. - (NSUInteger)totalNumberOfItems;
  20.  
  21. @property (nonatomic, copy) MQGReusableViewConfigurationBlock cellConfigurationBlock;
  22. // Other configuration blocks can go here
  23.  
  24. @end
  25.  
  26. // ------------ Results controller delegate -------------------------------
  27. // Intentionally mirrors NSFetchedResultsControllerDelegate
  28.  
  29. typedef NS_ENUM(NSInteger, MQGFetchedResultsControllerChangeType) {
  30. MQGFetchedResultsControllerChangeTypeInsert,
  31. MQGFetchedResultsControllerChangeTypeDelete,
  32. MQGFetchedResultsControllerChangeTypeMove,
  33. MQGFetchedResultsControllerChangeTypeUpdate
  34. };
  35.  
  36. @protocol MQGFetchedResultsController
  37. - (void)controllerWillChangeContent:(id<MQGFetchedResultsController>)controller;
  38. - (void)controllerDidChangeContent:(id<MQGFetchedResultsController>)controller;
  39.  
  40. - (void)controller:(id<MQGFetchedResultsController>)controller
  41. didChangeObject:(id)object
  42. atIndexPath:(NSIndexPath *)indexPath
  43. forChangeType:(MQGFetchedResultsControllerChangeType)changeType
  44. newIndexPath:(NSIndexPath *)newIndexPath;
  45.  
  46. - (void)controller:(id<MQGFetchedResultsController>)controller
  47. didChangeSectionAtIndex:(NSInteger)index
  48. forChangeType:(MQGFetchedResultsControllerChangeType)changeType;
  49. @end
  50.  
  51. // ---------------------- The actual fetched results controller only accepts a delegate. -----------------------------
  52. @protocol MQGFetchedResultsController <MQGDataSource>
  53.  
  54. @property (nonatomic, weak) id<MQGFetchedResultsControllerDelegate> delegate;
  55.  
  56. @end
  57.  
  58. // ---------------------- A results controllef using YapDatabase, view + mappings -------------------------------------
  59. @import UIKit;
  60. #import "MQGFetchedResultsController.h"
  61.  
  62. @class YapDatabase;
  63. @class YapDatabaseView;
  64. @class YapDatabaseViewMappings;
  65.  
  66. @interface MQGYapFetchedResultsController : NSObject <MQGFetchedResultsController>
  67.  
  68. - (instancetype)initWithDatabase:(YapDatabase *)database
  69. mappings:(YapDatabaseViewMappings *)mappings
  70. collection:(NSString *)collection
  71. cellConfiguration:(MQGReusableViewConfigurationBlock)cellConfiguration;
  72.  
  73. @property (nonatomic, readonly, strong) YapDatabaseViewMappings *mappings;
  74.  
  75. @end
  76.  
  77. @interface MQGYapFetchedResultsController ()
  78. @property (nonatomic, copy) NSString *collectionName;
  79. @property (nonatomic, strong) YapDatabaseConnection *connection;
  80. @property (nonatomic, strong) YapDatabase *database;
  81. @property (nonatomic, readwrite, strong) YapDatabaseViewMappings *mappings;
  82. @end
  83.  
  84. @implementation MQGYapFetchedResultsController
  85. @synthesize delegate = _delegate;
  86. @synthesize cellConfigurationBlock = _cellConfigurationBlock;
  87.  
  88. - (instancetype)initWithDatabase:(YapDatabase *)database
  89. mappings:(YapDatabaseViewMappings *)mappings
  90. collection:(NSString *)collection
  91. cellConfiguration:(MQGReusableViewConfigurationBlock)cellConfiguration {
  92. NSParameterAssert(collection);
  93. NSParameterAssert(database);
  94. NSParameterAssert(mappings.view);
  95. self = [super init];
  96. if (self) {
  97. _mappings = mappings;
  98. _collectionName = collection;
  99. _cellConfigurationBlock = cellConfiguration;
  100. _database = database;
  101. _connection = [_database newConnection];
  102. [_connection beginLongLivedReadTransaction];
  103. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yapDatabaseModifiedNotification:) name:YapDatabaseModifiedNotification object:_database];
  104.  
  105. [_connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
  106. [_mappings updateWithTransaction:transaction];
  107. }];
  108. }
  109. return self;
  110. }
  111.  
  112. - (void)dealloc {
  113. [[NSNotificationCenter defaultCenter] removeObserver:self];
  114. }
  115.  
  116. #pragma mark - Public API
  117.  
  118. - (id)objectAtIndexPath:(NSIndexPath *)indexPath {
  119. if ((NSUInteger)indexPath.item >= [self.mappings numberOfItemsInSection:(NSUInteger)indexPath.section]) {
  120. return nil;
  121. }
  122. __block id object = nil;
  123. [self.connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
  124. object = [[transaction extension:self.extensionName] objectAtIndexPath:indexPath withMappings:self.mappings];
  125. }];
  126. return object;
  127. }
  128.  
  129. - (NSUInteger)totalNumberOfItems {
  130. return [self.mappings numberOfItemsInAllGroups];
  131. }
  132.  
  133. #pragma mark - UITableViewDataSource
  134.  
  135. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  136. return (NSInteger)[[self mappings] numberOfSections];
  137. }
  138.  
  139. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  140. return (NSInteger)[[self mappings] numberOfItemsInSection : (NSUInteger)section];
  141. }
  142.  
  143. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  144. NSParameterAssert(self.cellConfigurationBlock);
  145. id object = [self objectAtIndexPath:indexPath];
  146. return object ? self.cellConfigurationBlock(tableView, object, indexPath) : nil;
  147. }
  148.  
  149. #pragma mark - UICollectionViewDataSource
  150.  
  151. - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
  152. return (NSInteger)[[self mappings] numberOfSections];
  153. }
  154.  
  155. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  156. return (NSInteger)[[self mappings] numberOfItemsInSection : (NSUInteger)section];
  157. }
  158.  
  159. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  160. NSParameterAssert(self.cellConfigurationBlock);
  161. id object = [self objectAtIndexPath:indexPath];
  162. return self.cellConfigurationBlock(collectionView, object, indexPath);
  163. }
  164.  
  165. #pragma mark - Accessors
  166.  
  167. - (NSString *)extensionName {
  168. return self.mappings.view;
  169. }
  170.  
  171. #pragma mark - Updates
  172.  
  173. - (void)yapDatabaseModifiedNotification:(NSNotification *)notification {
  174. NSArray *notifications = [self.connection beginLongLivedReadTransaction];
  175.  
  176. NSArray *sectionChanges = nil;
  177. NSArray *rowChanges = nil;
  178.  
  179. [[self.connection ext:self.extensionName] getSectionChanges:&sectionChanges
  180. rowChanges:&rowChanges
  181. forNotifications:notifications
  182. withMappings:self.mappings];
  183.  
  184. if ([rowChanges count] == 0 && [sectionChanges count] == 0) {
  185. return;
  186. }
  187.  
  188. if ([self.delegate respondsToSelector:@selector(controllerDidChangeContent:)]) {
  189. [self.delegate controllerWillChangeContent:self];
  190. }
  191.  
  192. for (YapDatabaseViewSectionChange *sectionChange in sectionChanges) {
  193. if ([self.delegate respondsToSelector:@selector(controller:didChangeSectionAtIndex:forChangeType:)]) {
  194. MQGFetchedResultsControllerChangeType type = [self yapChangeTypeToMQGChangeType:sectionChange.type];
  195. [self.delegate controller:self didChangeSectionAtIndex:(NSInteger)sectionChange.index forChangeType:type];
  196. }
  197. }
  198.  
  199. for (YapDatabaseViewRowChange *rowChange in rowChanges) {
  200. YapDatabaseViewChangeType changeType = rowChange.type;
  201. BOOL sameGroup = [[rowChange originalGroup] isEqualToString:[rowChange finalGroup]];
  202. BOOL sameSection = [rowChange finalSection] == [rowChange originalSection];
  203. BOOL sameIndex = [rowChange finalIndex] == [rowChange originalIndex];
  204. // Fixes issue with Yap where makes move to same indexes, thus not refreshing properly
  205. if (changeType == YapDatabaseViewChangeMove && sameGroup && sameSection && sameIndex) {
  206. changeType = YapDatabaseViewChangeUpdate;
  207. }
  208. id object = [self objectAtIndexPath:rowChange.indexPath];
  209. if ([self.delegate respondsToSelector:@selector(controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:)]) {
  210. [self.delegate controller:self
  211. didChangeObject:object
  212. atIndexPath:rowChange.indexPath
  213. forChangeType:[self yapChangeTypeToMQGChangeType:changeType]
  214. newIndexPath:rowChange.newIndexPath];
  215. }
  216. }
  217.  
  218. if ([self.delegate respondsToSelector:@selector(controllerDidChangeContent:)]) {
  219. [self.delegate controllerDidChangeContent:self];
  220. }
  221. }
  222.  
  223. - (MQGFetchedResultsControllerChangeType)yapChangeTypeToMQGChangeType:(YapDatabaseViewChangeType)type {
  224. switch (type) {
  225. case YapDatabaseViewChangeDelete:
  226. return MQGFetchedResultsControllerChangeTypeDelete;
  227. case YapDatabaseViewChangeInsert:
  228. return MQGFetchedResultsControllerChangeTypeInsert;
  229. case YapDatabaseViewChangeMove:
  230. return MQGFetchedResultsControllerChangeTypeMove;
  231. case YapDatabaseViewChangeUpdate:
  232. return MQGFetchedResultsControllerChangeTypeUpdate;
  233. }
  234. }
  235. @end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement