Skip to content

Instantly share code, notes, and snippets.

@p2
Created May 10, 2016 11:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save p2/815203a0736e5d1dc9b5ed0d32917f88 to your computer and use it in GitHub Desktop.
Save p2/815203a0736e5d1dc9b5ed0d32917f88 to your computer and use it in GitHub Desktop.
//
// 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
//
// 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
@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