Created
May 10, 2016 11:14
-
-
Save p2/815203a0736e5d1dc9b5ed0d32917f88 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CBLUITableSource.h | |
// CouchbaseLite | |
// | |
// Created by Jens Alfke on 8/2/11. | |
// Copyright 2011-2013 Couchbase, Inc. All rights reserved. | |
// | |
#import <UIKit/UIKit.h> | |
@class CBLDocument, CBLLiveQuery, CBLQueryRow; | |
/** | |
* An object that represents a table view section. | |
*/ | |
@interface CBLUITableSection : NSObject | |
/** The full name for the section. */ | |
@property (copy, nonatomic) NSString *sectionName; | |
/** An identifying key for the section at your disposal. */ | |
@property (copy, nonatomic) NSString *sectionKey; | |
/** The index to be shown for this section. */ | |
@property (copy, nonatomic) NSString *sectionIndexTitle; | |
+ (instancetype)sectionWithRow:(CBLQueryRow *)row; | |
+ (instancetype)sectionWithRows:(NSArray *)rows; | |
/** The number of rows. */ | |
- (NSUInteger)count; | |
/** Returns the row with the given index. */ | |
- (CBLQueryRow *)rowAtIndex:(NSUInteger)index; | |
/** Returns the index for the desired object, or NSNotFound if it's not in the receiver's rows. */ | |
- (NSUInteger)indexForDocument:(CBLDocument *)document; | |
/** Returns the index of the desired object, or NSNotFound if it's not in the receiver's rows. */ | |
- (NSUInteger)indexForElementWithModelIdentifier:(NSString *)identifier; | |
/** Adds an object. */ | |
- (void)addObject:(CBLQueryRow *)row; | |
/** Remove the given row. */ | |
- (void)removeObjectAtIndex:(NSUInteger)index; | |
/** Remove the given rows. */ | |
- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes; | |
@end | |
/** A UITableView data source driven by a CBLLiveQuery. | |
It populates the table rows from the query rows, and automatically updates the table as the | |
query results change when the database is updated. | |
A CBLUITableSource can be created in a nib. If so, its tableView outlet should be wired up to | |
the UITableView it manages, and the table view's dataSource outlet should be wired to it. */ | |
@interface CBLUITableSourceExt : NSObject <UITableViewDataSource | |
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) | |
, UIDataSourceModelAssociation | |
#endif | |
> | |
/** The table view to manage. */ | |
@property (nonatomic, retain) IBOutlet UITableView* tableView; | |
/** The query whose rows will be displayed in the table. */ | |
@property (retain) CBLLiveQuery* query; | |
/** The number of rows as per last query reload. */ | |
@property (nonatomic, readonly) NSUInteger totalRows; | |
/** Rebuilds the table from the query's current .rows property. */ | |
- (void) reloadFromQuery; | |
#pragma mark Row Accessors: | |
/** The current array of sections containing arrays of CBLQueryRows; this is used to feed the table. */ | |
@property (nonatomic, readonly) NSArray* sections; | |
/** The current array of CBLQueryRows being used as the data source for the table. | |
@attention This getter only returns the rows from the first section, which is no issue if you don't use sections. */ | |
@property (nonatomic, readonly) NSArray* rows DEPRECATED_ATTRIBUTE; | |
/** Convenience accessor to get the row object for a given table row index. | |
@attention This method only looks in the first section, which is no issue if you don't use sections. | |
*/ | |
- (CBLQueryRow*) rowAtIndex: (NSUInteger)index DEPRECATED_ATTRIBUTE; | |
/** Convenience accessor to find the index path of the row with a given document. */ | |
- (NSIndexPath*) indexPathForDocument: (CBLDocument*)document __attribute__((nonnull)); | |
/** Convenience accessor to return the query row at a given index path. */ | |
- (CBLQueryRow*) rowAtIndexPath: (NSIndexPath*)path __attribute__((nonnull)); | |
/** Convenience accessor to return the document at a given index path. */ | |
- (CBLDocument*) documentAtIndexPath: (NSIndexPath*)path __attribute__((nonnull)); | |
#pragma mark Displaying The Table: | |
/** Set to YES to report the appropriate section's "sectionName" as section title; defaults to NO. | |
@attention If this is YES the delegate method "couchTableSource:titleForHeaderInSection:" will NOT be called! */ | |
@property (nonatomic) BOOL useSectionNamesAsHeaders; | |
/** Set to YES to report the sections' "sectionIndexTitle" as section index titles to the table view; defaults to NO. | |
@attention If this is YES the delegate method "sectionIndexTitlesForCouchTableSource:" will NOT be called! */ | |
@property (nonatomic) BOOL useSectionIndexTitles; | |
/** If non-nil, specifies the property name of the query row's value that will be used for the table row's visible label. | |
If the row's value is not a dictionary, or if the property doesn't exist, the property will next be looked up in the document's properties. | |
If this doesn't meet your needs for labeling rows, you should implement -couchTableSource:willUseCell:forRow: in the table's delegate. */ | |
@property (copy) NSString* labelProperty; | |
#pragma mark Editing The Table: | |
/** Is the user allowed to delete rows by UI gestures? (Defaults to YES.) */ | |
@property (nonatomic) BOOL deletionAllowed; | |
/** Deletes the documents at the given row indexes, animating the removal from the table. */ | |
- (BOOL) deleteDocumentsAtIndexes: (NSArray*)indexPaths | |
error: (NSError**)outError __attribute__((nonnull(1))); | |
/** Asynchronously deletes the given documents, animating the removal from the table. */ | |
- (BOOL) deleteDocuments: (NSArray*)documents | |
error: (NSError**)outError __attribute__((nonnull(1))); | |
@end | |
/** Additional methods for the table view's delegate, that will be invoked by the CBLUITableSource. */ | |
@protocol CBLUITableSourceDelegate <UITableViewDelegate> | |
@optional | |
- (NSArray *)sectionIndexTitlesForCouchTableSource:(CBLUITableSourceExt*)source; | |
- (NSInteger)couchTableSource:(CBLUITableSourceExt *)source | |
sectionForSectionIndexTitle:(NSString *)title | |
atIndex:(NSInteger)index; | |
- (NSString *)couchTableSource:(CBLUITableSourceExt*)source | |
titleForHeaderInSection:(NSInteger)section; | |
/** Allows delegate to return its own custom cell, just like -tableView:cellForRowAtIndexPath:. | |
If this returns nil the table source will create its own cell, as if this method were not implemented. */ | |
- (UITableViewCell *)couchTableSource:(CBLUITableSourceExt*)source | |
cellForRowAtIndexPath:(NSIndexPath *)indexPath; | |
/** Called when the query has returned a new set of rows to enable the delegate to sectionize the rows. | |
@return A mutable array of sections containing mutable arrays (!) of CBLQueryRow objects passed into the method */ | |
- (NSMutableArray *)couchTableSource:(CBLUITableSourceExt *)source | |
sectionizeRows:(NSArray *)rows; | |
/** Called after the query's results change, before the table view is reloaded. */ | |
- (void)couchTableSource:(CBLUITableSourceExt*)source | |
willUpdateFromQuery:(CBLLiveQuery*)query; | |
/** Called after the query's results change to update the table view. If this method is not implemented by the delegate, reloadData is called on the table view.*/ | |
- (void)couchTableSource:(CBLUITableSourceExt*)source | |
updateFromQuery:(CBLLiveQuery*)query | |
previousSections:(NSArray *)previousSections; | |
/** Called after the query's results change to update the table view. If this method is not implemented by the delegate, reloadData is called on the table view. | |
@attention This method only returns the rows from the first section, which is no problem if you do not sectionize the data. | |
@attention This method is **not** called when `couchTableSource:updateFromQuery:previousSections:` has been implemented. */ | |
- (void)couchTableSource:(CBLUITableSourceExt*)source | |
updateFromQuery:(CBLLiveQuery*)query | |
previousRows:(NSArray *)previousRows DEPRECATED_ATTRIBUTE; | |
/** Called after the query's results change, after the table view has been reloaded. */ | |
- (void)couchTableSource:(CBLUITableSourceExt*)source | |
didUpdateFromQuery:(CBLLiveQuery*)query; | |
/** Called from -tableView:cellForRowAtIndexPath: just before it returns, giving the delegate a chance to customize the new cell. */ | |
- (void)couchTableSource:(CBLUITableSourceExt*)source | |
willUseCell:(UITableViewCell*)cell | |
forRow:(CBLQueryRow*)row; | |
/** Called when the user wants to delete a row. | |
If the delegate implements this method, it will be called *instead of* the | |
default behavior of deleting the associated document. | |
@param source The CBLUITableSource | |
@param row The query row corresponding to the row to delete | |
@return True if the row was deleted, false if not. */ | |
- (bool)couchTableSource:(CBLUITableSourceExt*)source | |
deleteRow:(CBLQueryRow*)row; | |
/** Called upon failure of a document deletion triggered by the user deleting a row. */ | |
- (void)couchTableSource:(CBLUITableSourceExt*)source | |
deleteFailed:(NSError*)error; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CBLUITableSource.m | |
// CouchbaseLite | |
// | |
// Created by Jens Alfke on 8/2/11. | |
// Copyright 2011-2013 Couchbase, Inc. All rights reserved. | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file | |
// except in compliance with the License. You may obtain a copy of the License at | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// Unless required by applicable law or agreed to in writing, software distributed under the | |
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, | |
// either express or implied. See the License for the specific language governing permissions | |
// and limitations under the License. | |
#import "CBLUITableSourceExt.h" | |
#import <CouchbaseLite/CouchbaseLite.h> | |
@interface CBLUITableSourceExt () | |
{ | |
@private | |
UITableView* _tableView; | |
CBLLiveQuery* _query; | |
NSString* _labelProperty; | |
BOOL _deletionAllowed; | |
BOOL _ignoreNextObservedRowChange; ///< we need this because on deleting rows, our observer would call "reloadTable" before the animation takes place | |
} | |
@property (strong, nonatomic) NSMutableArray* mutableSections; | |
@property (nonatomic, readwrite) NSUInteger totalRows; | |
@end | |
@implementation CBLUITableSourceExt | |
- (instancetype) init { | |
self = [super init]; | |
if (self) { | |
_deletionAllowed = YES; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[_query removeObserver: self forKeyPath: @"rows"]; | |
} | |
#pragma mark - | |
#pragma mark ACCESSORS: | |
@synthesize tableView=_tableView; | |
@synthesize mutableSections=_mutableSections; | |
- (NSArray*) rows { | |
return [_mutableSections[0] copy]; | |
} | |
- (NSArray*) sections { | |
return [_mutableSections copy]; | |
} | |
- (CBLQueryRow*) rowAtIndex: (NSUInteger)index { | |
return _mutableSections[0][index]; | |
} | |
- (CBLQueryRow*) rowAtIndexPath: (NSIndexPath*)path { | |
if ((NSInteger)[_mutableSections count] > path.section) { | |
CBLUITableSection *sectionObj = _mutableSections[path.section]; | |
return [sectionObj rowAtIndex:path.row]; | |
} | |
return nil; | |
} | |
- (CBLDocument*) documentAtIndexPath: (NSIndexPath*)path { | |
return [self rowAtIndexPath: path].document; | |
} | |
- (NSIndexPath*) indexPathForDocument: (CBLDocument*)document { | |
NSUInteger section = 0; | |
for (CBLUITableSection *sectionObj in _mutableSections) { | |
NSUInteger row = [sectionObj indexForDocument:document]; | |
if (NSNotFound != row) { | |
return [NSIndexPath indexPathForRow:row inSection:section]; | |
} | |
section++; | |
} | |
return nil; | |
} | |
#define TELL_DELEGATE(sel, obj) \ | |
(([_tableView.delegate respondsToSelector: sel]) \ | |
? [_tableView.delegate performSelector: sel withObject: self withObject: obj] \ | |
: nil) | |
#pragma mark - | |
#pragma mark QUERY HANDLING: | |
- (CBLLiveQuery*) query { | |
return _query; | |
} | |
- (void) setQuery:(CBLLiveQuery *)query { | |
if (query != _query) { | |
[_query removeObserver: self forKeyPath: @"rows"]; | |
_query = query; | |
[_query addObserver: self forKeyPath: @"rows" options: 0 context: NULL]; | |
[self reloadFromQuery]; | |
} | |
} | |
- (void) reloadFromQuery { | |
_totalRows = 0; | |
CBLQueryEnumerator* rowEnum = _query.rows; | |
if (rowEnum) { | |
id delegate = _tableView.delegate; | |
// retrieve new rows and sectionize, if desired | |
NSArray *oldSections = _mutableSections; | |
NSArray *allRows = rowEnum.allObjects; | |
if ([delegate respondsToSelector:@selector(couchTableSource:sectionizeRows:)]) { | |
NSMutableArray *sectionized = [delegate couchTableSource: self sectionizeRows: allRows]; | |
NSAssert(!sectionized || [sectionized isKindOfClass:[NSMutableArray class]], @"Must return a mutable array"); | |
NSAssert(0 == [sectionized count] || [sectionized[0] isKindOfClass:[CBLUITableSection class]], @"Must fill sections with CBLUITableSection objects"); | |
_mutableSections = sectionized; | |
} | |
else { | |
CBLUITableSection *section = [CBLUITableSection sectionWithRows: allRows]; | |
_mutableSections = [NSMutableArray arrayWithObject: section]; | |
} | |
TELL_DELEGATE(@selector(couchTableSource:willUpdateFromQuery:), _query); | |
_totalRows = [allRows count]; | |
// update table view | |
if ([delegate respondsToSelector: @selector(couchTableSource:updateFromQuery:previousSections:)]) { | |
[delegate couchTableSource: self | |
updateFromQuery: _query | |
previousSections: oldSections]; | |
} | |
else if ([delegate respondsToSelector: @selector(couchTableSource:updateFromQuery:previousRows:)]) { | |
[delegate couchTableSource: self | |
updateFromQuery: _query | |
previousRows: ([oldSections count] > 0) ? oldSections[0] : nil]; | |
} | |
else { | |
[self.tableView reloadData]; | |
} | |
TELL_DELEGATE(@selector(couchTableSource:didUpdateFromQuery:), _query); | |
} | |
} | |
- (void) observeValueForKeyPath: (NSString*)keyPath ofObject: (id)object | |
change: (NSDictionary*)change context: (void*)context | |
{ | |
if (object == _query) { | |
if (!_ignoreNextObservedRowChange) { | |
[self reloadFromQuery]; | |
} | |
else { | |
_totalRows = [_query.rows.allObjects count]; | |
TELL_DELEGATE(@selector(couchTableSource:didUpdateFromQuery:), _query); // still let the delegate know that the table changed | |
} | |
_ignoreNextObservedRowChange = NO; | |
} | |
} | |
#pragma mark - | |
#pragma mark DATA SOURCE PROTOCOL: | |
@synthesize labelProperty=_labelProperty; | |
- (NSString*) labelForRow: (CBLQueryRow*)row { | |
id value = row.value; | |
if (_labelProperty) { | |
if ([value isKindOfClass: [NSDictionary class]]) | |
value = [value objectForKey: _labelProperty]; | |
else | |
value = nil; | |
if (!value) | |
value = [row.document propertyForKey: _labelProperty]; | |
} | |
return [value description]; | |
} | |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { | |
return [_mutableSections count]; | |
} | |
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { | |
if (_useSectionIndexTitles) { | |
if (_totalRows >= tableView.sectionIndexMinimumDisplayRowCount) { | |
return [_mutableSections valueForKey:@"sectionIndexTitle"]; | |
} | |
return nil; | |
} | |
return TELL_DELEGATE(@selector(sectionIndexTitlesForCouchTableSource:), nil); | |
} | |
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { | |
if ([_tableView.delegate respondsToSelector: @selector(couchTableSource:sectionForSectionIndexTitle:atIndex:)]) { | |
return [(id<CBLUITableSourceDelegate>)_tableView.delegate couchTableSource:self sectionForSectionIndexTitle:title atIndex:index]; | |
} | |
return index; | |
} | |
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { | |
if (_useSectionNamesAsHeaders) { | |
if (_totalRows >= tableView.sectionIndexMinimumDisplayRowCount) { | |
CBLUITableSection *sectionObj = _mutableSections[section]; | |
return sectionObj.sectionName; | |
} | |
return nil; | |
} | |
if ([_tableView.delegate respondsToSelector: @selector(couchTableSource:titleForHeaderInSection:)]) { | |
return [(id<CBLUITableSourceDelegate>)_tableView.delegate couchTableSource:self titleForHeaderInSection:section]; | |
} | |
return nil; | |
} | |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { | |
return [_mutableSections[section] count]; | |
} | |
- (UITableViewCell *)tableView:(UITableView *)tableView | |
cellForRowAtIndexPath:(NSIndexPath *)indexPath | |
{ | |
// Allow the delegate to create its own cell: | |
UITableViewCell* cell = TELL_DELEGATE(@selector(couchTableSource:cellForRowAtIndexPath:), | |
indexPath); | |
if (!cell) { | |
// ...if it doesn't, create a cell for it: | |
cell = [tableView dequeueReusableCellWithIdentifier: @"CBLUITableDelegate"]; | |
if (!cell) | |
cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault | |
reuseIdentifier: @"CBLUITableDelegate"]; | |
CBLQueryRow* row = [self rowAtIndexPath: indexPath]; | |
cell.textLabel.text = [self labelForRow: row]; | |
// Allow the delegate to customize the cell: | |
id delegate = _tableView.delegate; | |
if ([delegate respondsToSelector: @selector(couchTableSource:willUseCell:forRow:)]) | |
[(id<CBLUITableSourceDelegate>)delegate couchTableSource: self willUseCell: cell forRow: row]; | |
} | |
return cell; | |
} | |
#pragma mark - | |
#pragma mark EDITING: | |
@synthesize deletionAllowed=_deletionAllowed; | |
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { | |
return _deletionAllowed; | |
} | |
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { | |
// Queries have a sort order so reordering doesn't generally make sense. | |
return NO; | |
} | |
- (void)tableView:(UITableView *)tableView | |
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle | |
forRowAtIndexPath:(NSIndexPath *)indexPath | |
{ | |
if (editingStyle == UITableViewCellEditingStyleDelete) { | |
BOOL deleteRows = YES; | |
// Delete the document from the database. | |
CBLQueryRow* row = [self rowAtIndexPath: indexPath]; | |
id<CBLUITableSourceDelegate> delegate = (id<CBLUITableSourceDelegate>)_tableView.delegate; | |
if ([delegate respondsToSelector: @selector(couchTableSource:deleteRow:)]) { | |
if (![delegate couchTableSource: self deleteRow: row]) { | |
deleteRows = NO; | |
} | |
} | |
else { | |
NSError* error; | |
if (![row.document.currentRevision deleteDocument: &error]) { | |
TELL_DELEGATE(@selector(couchTableSource:deleteFailed:), error); | |
deleteRows = NO; | |
} | |
} | |
// Delete the row from the table data source. We ignore the next observed change since this would reload the table and abort our animation, but we | |
// still call the "couchTableSource:willUpdateFromQuery:" delegate method here and "couchTableSource:willUpdateFromQuery:" when the observation comes | |
// in. | |
if (deleteRows) { | |
_ignoreNextObservedRowChange = YES; | |
TELL_DELEGATE(@selector(couchTableSource:willUpdateFromQuery:), _query); | |
[_mutableSections[indexPath.section] removeObjectAtIndex: indexPath.row]; | |
[self.tableView deleteRowsAtIndexPaths: [NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationFade]; | |
} | |
} | |
} | |
- (BOOL) deleteDocuments: (NSArray*)documents | |
atIndexes: (NSArray*)indexPaths | |
error: (NSError**)outError | |
{ | |
__block NSError* error = nil; | |
BOOL ok = [_query.database inTransaction: ^{ | |
for (CBLDocument* doc in documents) { | |
if (![doc.currentRevision deleteDocument: &error]) | |
return NO; | |
} | |
return YES; | |
}]; | |
if (!ok) { | |
if (outError) | |
*outError = error; | |
return NO; | |
} | |
NSMutableDictionary *perSection = [NSMutableDictionary dictionaryWithCapacity:[indexPaths count]]; | |
for (NSIndexPath* path in indexPaths) { | |
NSMutableIndexSet *indexSet = perSection[@(path.section)]; | |
if (!indexSet) { | |
indexSet = [NSMutableIndexSet indexSet]; | |
perSection[@(path.section)] = indexSet; | |
} | |
[indexSet addIndex: path.row]; | |
} | |
for (NSNumber *sectionNum in [perSection allKeys]) { | |
NSIndexSet *indexSet = perSection[sectionNum]; | |
[_mutableSections[[sectionNum integerValue]] removeObjectsAtIndexes: indexSet]; | |
} | |
[_tableView deleteRowsAtIndexPaths: indexPaths withRowAnimation: UITableViewRowAnimationFade]; | |
return YES; | |
} | |
- (BOOL) deleteDocumentsAtIndexes: (NSArray*)indexPaths error: (NSError**)outError { | |
NSMutableArray *docs = [NSMutableArray arrayWithCapacity:[indexPaths count]]; | |
for (NSIndexPath *path in indexPaths) { | |
[docs addObject:[self documentAtIndexPath:path]]; | |
} | |
// NSArray* docs = [indexPaths my_map: ^(id path) {return [self documentAtIndexPath: path];}]; | |
return [self deleteDocuments: docs atIndexes: indexPaths error: outError]; | |
} | |
- (BOOL) deleteDocuments: (NSArray*)documents error: (NSError**)outError { | |
NSMutableArray *paths = [NSMutableArray arrayWithCapacity:[documents count]]; | |
for (CBLDocument *doc in documents) { | |
[paths addObject:[self indexPathForDocument: doc]]; | |
} | |
// NSArray* paths = [documents my_map: ^(id doc) {return [self indexPathForDocument: doc];}]; | |
return [self deleteDocuments: documents atIndexes: paths error: outError]; | |
} | |
#pragma mark - STATE RESTORATION: | |
- (NSString *) modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx | |
inView:(UIView *)view | |
{ | |
CBLQueryRow* row = [self rowAtIndexPath: idx]; | |
return row.key; | |
} | |
- (NSIndexPath *) indexPathForElementWithModelIdentifier:(NSString *)identifier | |
inView:(UIView *)view | |
{ | |
if (identifier) { | |
NSUInteger section = 0; | |
for (CBLUITableSection *sectionObj in _mutableSections) { | |
NSUInteger row = [sectionObj indexForElementWithModelIdentifier:identifier]; | |
if (NSNotFound != row) { | |
return [NSIndexPath indexPathForRow: row inSection: section]; | |
} | |
section++; | |
} | |
} | |
return nil; | |
} | |
@end | |
BOOL $my_equal(id obj1, id obj2) // Like -isEqual: but works even if either/both are nil | |
{ | |
if( obj1 ) | |
return obj2 && [obj1 isEqual: obj2]; | |
else | |
return obj2==nil; | |
} | |
@interface CBLUITableSection () | |
@property (strong, nonatomic) NSMutableArray *rows; | |
@end | |
@implementation CBLUITableSection | |
+ (instancetype)sectionWithRow:(CBLQueryRow *)row | |
{ | |
CBLUITableSection *section = [self new]; | |
section.rows = [NSMutableArray arrayWithObjects:row, nil]; | |
return section; | |
} | |
+ (instancetype)sectionWithRows:(NSArray *)rows | |
{ | |
CBLUITableSection *section = [self new]; | |
section.rows = rows ? [rows mutableCopy] : nil; | |
return section; | |
} | |
- (NSUInteger)count | |
{ | |
return [_rows count]; | |
} | |
#pragma mark - Finding Objects | |
- (CBLQueryRow *)rowAtIndex:(NSUInteger)index | |
{ | |
return ((NSInteger)[_rows count] > index) ? _rows[index] :nil; | |
} | |
- (NSUInteger)indexForDocument:(CBLDocument *)document | |
{ | |
NSParameterAssert(document); | |
NSString *documentID = document.documentID; | |
NSUInteger row = 0; | |
for (CBLQueryRow* queryRow in self.rows) { | |
if ([queryRow.documentID isEqualToString:documentID]) { | |
return row; | |
} | |
row++; | |
} | |
return NSNotFound; | |
} | |
- (NSUInteger)indexForElementWithModelIdentifier:(NSString *)identifier | |
{ | |
NSParameterAssert(identifier); | |
NSUInteger row = 0; | |
for (CBLQueryRow* queryRow in self.rows) { | |
if ($my_equal(queryRow.key, identifier)) { | |
return row; | |
} | |
row++; | |
} | |
return NSNotFound; | |
} | |
#pragma mark - Adding Objects | |
- (void)addObject:(CBLQueryRow *)row | |
{ | |
NSParameterAssert(row); | |
if (!_rows) { | |
self.rows = [NSMutableArray arrayWithObject:row]; | |
} | |
else { | |
[_rows addObject:row]; | |
} | |
} | |
#pragma mark - Removing Objects | |
- (void)removeObjectAtIndex:(NSUInteger)index | |
{ | |
[_rows removeObjectAtIndex:index]; | |
} | |
- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes | |
{ | |
[_rows removeObjectsAtIndexes:indexes]; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@implementation ExampleTableViewController | |
- (void)viewDidLoad { | |
[super viewDidLoad]; | |
self.dataSource = [CBLUITableSourceExt new]; | |
_dataSource.tableView = self.tableView; | |
self.tableView.dataSource = _dataSource; | |
} | |
- (NSInteger)couchTableSource:(CBLUITableSourceExt *)source sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { | |
return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index]; | |
} | |
- (NSMutableArray *)couchTableSource:(CBLUITableSourceExt *)source sectionizeRows:(NSArray *)rows { | |
// A -> Z ordering, create sections by localized collations | |
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation]; | |
NSUInteger max = [[collation sectionTitles] count]; | |
NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:max]; | |
for (NSUInteger i = 0; i < max; i++) { | |
CBLUITableSection *section = [CBLUITableSection new]; | |
section.sectionName = collation.sectionTitles[i]; | |
section.sectionIndexTitle = collation.sectionIndexTitles[i]; | |
[sections addObject:section]; | |
} | |
// fill the rows into the predefined sections, then remove empty sections | |
for (CBLQueryRow *row in rows) { | |
NSInteger i = [collation sectionForObject:row collationStringSelector:@selector(key1)]; // keys: 0 = doc-id, 1 = used name, 2 = birthday | |
[sections[i] addObject:row]; | |
} | |
for (CBLUITableSection *section in [sections reverseObjectEnumerator]) { | |
if (0 == [section count]) { | |
[sections removeObject:section]; | |
} | |
} | |
return sections; | |
} | |
- (UITableViewCell *)couchTableSource:(CBLUITableSourceExt *)source cellForRowAtIndexPath:(NSIndexPath *)indexPath { | |
UITableViewCell *cell = [source.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; | |
CBLDocument *doc = [source documentAtIndexPath:indexPath]; | |
// YourModel *model = [YourModel modelForDocument:doc]; | |
cell.textLabel.text = ... | |
return cell | |
} | |
- (BOOL)couchTableSource:(CBLUITableSourceExt *)source deleteRow:(CBLQueryRow *)row { | |
YourModel *model = [YourModel modelForDocument:row.document]; | |
... | |
return YES; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment