Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- This gist presents a way to abstract Yapdatabase views and mappings into a NSFetchedResultsController-like API. If you
- are used to CoreData then it may sound familiar.
- Why?
- I've used this approach in the past because YapDatabase is awesome but incites tight coupling between the ViewController and the database.
- I like to keep boundaries flexible, and ViewController is the root of all evil, if not 'controlled' properly :)
- With this approach, ViewController is the delegate and datasource of your dynamic view (UITableView or UICollectionView), but gets the
- data in a controlled manner from the ResultsController.
- I've used this using a MVVM approach, not the classic MVC, so a the ViewModel would return a valid resultsController that the ViewController
- can bind to through it's delegate.
- */
- // ---------- Datasource protocol for 'dynamic' views ---------------------
- @protocol MQGDataSource <UITableViewDataSource, UICollectionViewDataSource>
- - (id)objectAtIndexPath:(NSIndexPath *)indexPath;
- - (NSUInteger)totalNumberOfItems;
- @property (nonatomic, copy) MQGReusableViewConfigurationBlock cellConfigurationBlock;
- // Other configuration blocks can go here
- @end
- // ------------ Results controller delegate -------------------------------
- // Intentionally mirrors NSFetchedResultsControllerDelegate
- typedef NS_ENUM(NSInteger, MQGFetchedResultsControllerChangeType) {
- MQGFetchedResultsControllerChangeTypeInsert,
- MQGFetchedResultsControllerChangeTypeDelete,
- MQGFetchedResultsControllerChangeTypeMove,
- MQGFetchedResultsControllerChangeTypeUpdate
- };
- @protocol MQGFetchedResultsController
- - (void)controllerWillChangeContent:(id<MQGFetchedResultsController>)controller;
- - (void)controllerDidChangeContent:(id<MQGFetchedResultsController>)controller;
- - (void)controller:(id<MQGFetchedResultsController>)controller
- didChangeObject:(id)object
- atIndexPath:(NSIndexPath *)indexPath
- forChangeType:(MQGFetchedResultsControllerChangeType)changeType
- newIndexPath:(NSIndexPath *)newIndexPath;
- - (void)controller:(id<MQGFetchedResultsController>)controller
- didChangeSectionAtIndex:(NSInteger)index
- forChangeType:(MQGFetchedResultsControllerChangeType)changeType;
- @end
- // ---------------------- The actual fetched results controller only accepts a delegate. -----------------------------
- @protocol MQGFetchedResultsController <MQGDataSource>
- @property (nonatomic, weak) id<MQGFetchedResultsControllerDelegate> delegate;
- @end
- // ---------------------- A results controllef using YapDatabase, view + mappings -------------------------------------
- @import UIKit;
- #import "MQGFetchedResultsController.h"
- @class YapDatabase;
- @class YapDatabaseView;
- @class YapDatabaseViewMappings;
- @interface MQGYapFetchedResultsController : NSObject <MQGFetchedResultsController>
- - (instancetype)initWithDatabase:(YapDatabase *)database
- mappings:(YapDatabaseViewMappings *)mappings
- collection:(NSString *)collection
- cellConfiguration:(MQGReusableViewConfigurationBlock)cellConfiguration;
- @property (nonatomic, readonly, strong) YapDatabaseViewMappings *mappings;
- @end
- @interface MQGYapFetchedResultsController ()
- @property (nonatomic, copy) NSString *collectionName;
- @property (nonatomic, strong) YapDatabaseConnection *connection;
- @property (nonatomic, strong) YapDatabase *database;
- @property (nonatomic, readwrite, strong) YapDatabaseViewMappings *mappings;
- @end
- @implementation MQGYapFetchedResultsController
- @synthesize delegate = _delegate;
- @synthesize cellConfigurationBlock = _cellConfigurationBlock;
- - (instancetype)initWithDatabase:(YapDatabase *)database
- mappings:(YapDatabaseViewMappings *)mappings
- collection:(NSString *)collection
- cellConfiguration:(MQGReusableViewConfigurationBlock)cellConfiguration {
- NSParameterAssert(collection);
- NSParameterAssert(database);
- NSParameterAssert(mappings.view);
- self = [super init];
- if (self) {
- _mappings = mappings;
- _collectionName = collection;
- _cellConfigurationBlock = cellConfiguration;
- _database = database;
- _connection = [_database newConnection];
- [_connection beginLongLivedReadTransaction];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yapDatabaseModifiedNotification:) name:YapDatabaseModifiedNotification object:_database];
- [_connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
- [_mappings updateWithTransaction:transaction];
- }];
- }
- return self;
- }
- - (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- #pragma mark - Public API
- - (id)objectAtIndexPath:(NSIndexPath *)indexPath {
- if ((NSUInteger)indexPath.item >= [self.mappings numberOfItemsInSection:(NSUInteger)indexPath.section]) {
- return nil;
- }
- __block id object = nil;
- [self.connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
- object = [[transaction extension:self.extensionName] objectAtIndexPath:indexPath withMappings:self.mappings];
- }];
- return object;
- }
- - (NSUInteger)totalNumberOfItems {
- return [self.mappings numberOfItemsInAllGroups];
- }
- #pragma mark - UITableViewDataSource
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
- return (NSInteger)[[self mappings] numberOfSections];
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- return (NSInteger)[[self mappings] numberOfItemsInSection : (NSUInteger)section];
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- NSParameterAssert(self.cellConfigurationBlock);
- id object = [self objectAtIndexPath:indexPath];
- return object ? self.cellConfigurationBlock(tableView, object, indexPath) : nil;
- }
- #pragma mark - UICollectionViewDataSource
- - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
- return (NSInteger)[[self mappings] numberOfSections];
- }
- - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
- return (NSInteger)[[self mappings] numberOfItemsInSection : (NSUInteger)section];
- }
- - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
- NSParameterAssert(self.cellConfigurationBlock);
- id object = [self objectAtIndexPath:indexPath];
- return self.cellConfigurationBlock(collectionView, object, indexPath);
- }
- #pragma mark - Accessors
- - (NSString *)extensionName {
- return self.mappings.view;
- }
- #pragma mark - Updates
- - (void)yapDatabaseModifiedNotification:(NSNotification *)notification {
- NSArray *notifications = [self.connection beginLongLivedReadTransaction];
- NSArray *sectionChanges = nil;
- NSArray *rowChanges = nil;
- [[self.connection ext:self.extensionName] getSectionChanges:§ionChanges
- rowChanges:&rowChanges
- forNotifications:notifications
- withMappings:self.mappings];
- if ([rowChanges count] == 0 && [sectionChanges count] == 0) {
- return;
- }
- if ([self.delegate respondsToSelector:@selector(controllerDidChangeContent:)]) {
- [self.delegate controllerWillChangeContent:self];
- }
- for (YapDatabaseViewSectionChange *sectionChange in sectionChanges) {
- if ([self.delegate respondsToSelector:@selector(controller:didChangeSectionAtIndex:forChangeType:)]) {
- MQGFetchedResultsControllerChangeType type = [self yapChangeTypeToMQGChangeType:sectionChange.type];
- [self.delegate controller:self didChangeSectionAtIndex:(NSInteger)sectionChange.index forChangeType:type];
- }
- }
- for (YapDatabaseViewRowChange *rowChange in rowChanges) {
- YapDatabaseViewChangeType changeType = rowChange.type;
- BOOL sameGroup = [[rowChange originalGroup] isEqualToString:[rowChange finalGroup]];
- BOOL sameSection = [rowChange finalSection] == [rowChange originalSection];
- BOOL sameIndex = [rowChange finalIndex] == [rowChange originalIndex];
- // Fixes issue with Yap where makes move to same indexes, thus not refreshing properly
- if (changeType == YapDatabaseViewChangeMove && sameGroup && sameSection && sameIndex) {
- changeType = YapDatabaseViewChangeUpdate;
- }
- id object = [self objectAtIndexPath:rowChange.indexPath];
- if ([self.delegate respondsToSelector:@selector(controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:)]) {
- [self.delegate controller:self
- didChangeObject:object
- atIndexPath:rowChange.indexPath
- forChangeType:[self yapChangeTypeToMQGChangeType:changeType]
- newIndexPath:rowChange.newIndexPath];
- }
- }
- if ([self.delegate respondsToSelector:@selector(controllerDidChangeContent:)]) {
- [self.delegate controllerDidChangeContent:self];
- }
- }
- - (MQGFetchedResultsControllerChangeType)yapChangeTypeToMQGChangeType:(YapDatabaseViewChangeType)type {
- switch (type) {
- case YapDatabaseViewChangeDelete:
- return MQGFetchedResultsControllerChangeTypeDelete;
- case YapDatabaseViewChangeInsert:
- return MQGFetchedResultsControllerChangeTypeInsert;
- case YapDatabaseViewChangeMove:
- return MQGFetchedResultsControllerChangeTypeMove;
- case YapDatabaseViewChangeUpdate:
- return MQGFetchedResultsControllerChangeTypeUpdate;
- }
- }
- @end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement