YapDatabaseViewMappings
@interface YapDatabaseViewMappings : NSObject <NSCopying>
/**
* Initializes a new mappings object.
* Use this initializer when the groups, and their order, are known at initialization time.
*
* @param allGroups
* The ordered array of group names.
* From the example above, this would be @[ @"wine", @"liquor", @"beer" ]
*
* @param registeredViewName
* This is the name of the view, as you registered it with the database system.
*/
+ (instancetype)mappingsWithGroups:(NSArray<NSString *> *)allGroups view:(NSString *)registeredViewName;
/**
* Initializes a new mappings object with a static list of groups.
* Use this initializer when the groups, and their order, are known at initialization time.
*
* @param allGroups
* The ordered array of group names.
* From the example above, this would be @[ @"wine", @"liquor", @"beer" ]
*
* @param registeredViewName
* This is the name of the view, as you registered it with the database system.
*/
- (id)initWithGroups:(NSArray<NSString *> *)allGroups
view:(NSString *)registeredViewName;
/**
* Initializes a new mappings object that uses a filterBlock and a sortBlock to dynamically construct sections from view.
* @param filterBlock
* Block that takes a string and returns a BOOL. returning YES will include the group in the sections of the mapping.
* @param sortBlock
* Block used to sort group names for groups that pass the filter.
* @param registeredViewName
* This is the name of the view, as you registered it with the database system.
*
*/
- (id)initWithGroupFilterBlock:(YapDatabaseViewMappingGroupFilter)filterBlock
sortBlock:(YapDatabaseViewMappingGroupSort)sortBlock
view:(NSString *)registeredViewName;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Accessors
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The allGroups property returns the groups that were passed in the init method.
* That is, all groups, whether currently visible or non-visible.
*
* @see visibleGroups
*/
@property (nonatomic, copy, readonly) NSArray<NSString *> *allGroups;
/**
* The registeredViewName that was passed in the init method.
*/
@property (nonatomic, copy, readonly) NSString *view;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* A group/section can either be "static" or "dynamic".
*
* A dynamic section automatically disappears if it becomes empty.
* A static section is always visible, regardless of its item count.
*
* By default all groups/sections are static.
* You can enable dynamic sections on a per-group basis (just for certain sections) or for all groups (all sections).
*
* If you enable dynamic sections, be sure to use the helper methods available in this class.
* This will drastically simplify things for you.
* For example:
*
* Let's say you have 3 groups: @[ @"wine", @"liquor", @"beer" ]
* You've enabled dynamic sections for all groups.
* Section 0 refers to what group?
*
* The answer depends entirely on the item count per section.
* If "wine" is empty, but "liquor" isn't, then section zero is "liquor".
* If "wine" and "liquor" are empty, but "beer" isn't, then section zero is "beer".
*
* But you can simply do this to get the answer:
*
* NSString *group = [mappings groupForSection:indexPath.section];
*
* @see numberOfSections
* @see groupForSection:
* @see visibleGroups
*
* Also note that there's an extremely helpful category: YapDatabaseViewTransaction(Mappings).
* Methods in this category will be of great help as you take advantage of advanced mappings configurations.
* For example:
*
* id object = [[transaction ext:@"myView"] objectAtIndexPath:indexPath withMappings:mappings];
*
* These methods are extensively unit tested to ensure they work properly with all kinds of mappings configurations.
* Translation: You can use them without thinking, and they'll just work everytime.
*
* The mappings object is also used to assist with tableView/collectionView change animations:
*
* - YapDatabaseViewConnection getSectionChanges:rowChanges:forNotifications:withMappings:
*
* If all your sections are static, then you won't ever get any section changes.
* But if you have one or more dynamic sections, then be sure to process the section changes.
* As the dynamic sections disappear & re-appear, the proper section changes will be emitted.
*
* By DEFAULT, all groups/sections are STATIC.
*
* You can configure this however you want to meet your needs.
* This includes per-group configuration, all-at-once, and even overrides.
*
* ORDER MATTERS.
*
* If you invoke setIsDynamicSectionForAllGroups, this sets the configuration for every group.
* Including future groups if using dynamic groups via initWithGroupFilterBlock:sortBlock:view:.
*
* Once the configuration is set for all groups, you can then choose to provide overriden settings for select groups.
* That is, if you then invoke setIsDynamicSection:forGroup: is will override the "global" setting
* for this particular group.
*/
- (void)setIsDynamicSection:(BOOL)isDynamic forGroup:(NSString *)group;
- (BOOL)isDynamicSectionForGroup:(NSString *)group;
- (void)setIsDynamicSectionForAllGroups:(BOOL)isDynamic;
- (BOOL)isDynamicSectionForAllGroups;
/**
* You can use the YapDatabaseViewRangeOptions class to configure a "range" that you would
* like to restrict your tableView / collectionView to.
*
* Two types of ranges are supported:
*
* 1. Fixed ranges.
* This is similar to using a LIMIT & OFFSET in a typical sql query.
*
* 2. Flexible ranges.
* These allow you to specify an initial range, and allow it to grow and shrink.
*
* The YapDatabaseViewRangeOptions header file has a lot of documentation on
* setting up and configuring range options.
*
* One of the best parts of using rangeOptions is that you get animations for free.
* For example:
*
* Say you have view that sorts items by sales rank.
* You want to display a tableView that displays the top 20 best-sellers. Simple enough so far.
* But you want the tableView to automatically update throughout the day as sales are getting processed.
* And you want the tableView to automatically animate any changes. (No wimping out with reloadData!)
* You can get this with only a few lines of code using range options.
*
* Note that if you're using range options, then the indexPaths in your UI might not match up directly
* with the indexes in the view's group. But don't worry.
* Just use the methods in "YapDatabaseViewTransaction (Mappings)" to automatically handle it all for you.
* Or, if you want to be advanced, the various mapping methods in this class.
*
* The rangeOptions you pass in are copied, and YapDatabaseViewMappings keeps a private immutable version of them.
* So if you make changes to the rangeOptions, you need to invoke this method again to set the changes.
*/
- (void)setRangeOptions:(nullable YapDatabaseViewRangeOptions *)rangeOpts forGroup:(NSString *)group;
- (nullable YapDatabaseViewRangeOptions *)rangeOptionsForGroup:(NSString *)group;
- (void)removeRangeOptionsForGroup:(NSString *)group;
/**
* There are some times when the drawing of one cell depends somehow on a neighboring cell.
* For example:
*
* Apple's SMS messaging app draws a timestamp if more than a certain amount of time has elapsed
* between a message and the previous message. The timestamp is actually drawn at the top of a cell.
* So cell-B would draw a timestamp at the top of its cell if cell-A represented a message
* that was sent/received say 3 hours ago.
*
* We refer to this as a "cell drawing dependency". For the example above, the timestamp drawing is dependent
* upon the cell at offset -1. That is, the drawing of cell at index 5 is dependent upon the cell at index (5-1).
*
* This method allows you to specify if there are cell drawing dependecies.
* For the example above you could simply do the following:
*
* [mappings setCellDrawingDependencyForNeighboringCellWithOffset:-1 forGroup:@""]
*
* This will inject extra YapDatabaseViewChangeUpdate's for cells that may have been affected by
* other Insert/Delete/Update/Move operations (and thus need to be redrawn).
*
* Continuing the example above, if the item at index 7 is deleted, then changeset processing will automatically emit
* and update change for the item that was previously at index 8. This is because, as specified by the "cell drawing
* offset" configuration, the drawing of index 8 was dependent upon the item before it (offset=-1). The item
* before it has changed, so it gets an update emitted for it automatically.
*
* Using this configuration makes it exteremely simple to handle various "cell drawing dependencies".
* You can just ask for changesets as you would if there weren't any dependencies,
* perform the boiler-plate updates, and everything just works.
*
* Note that if a YapDatabaseViewChangeUpdate is emitted due to a cell drawing dependeny,
* AND there were no actual updates for the corresponding item,
* and you'd like to detect these changes for whatever reason (optimizing, etc),
* then you can do so by checking to see if the rowChange.changes == YapDatabaseViewChangedDependency.
*
* If you have multiple cell drawing dependencies (e.g. +1 & -1),
* then you can pass in an NSSet of NSNumbers.
*/
- (void)setCellDrawingDependencyForNeighboringCellWithOffset:(NSInteger)offset forGroup:(NSString *)group;
- (void)setCellDrawingDependencyOffsets:(NSSet<NSNumber *> *)offsets forGroup:(NSString *)group;
- (NSSet<NSNumber *> *)cellDrawingDependencyOffsetsForGroup:(NSString *)group;
/**
* You can tell mappings to reverse a group/section if you'd like to display it in your tableView/collectionView
* in the opposite direction in which the items actually exist within the database.
*
* For example:
*
* You have a database view which sorts items by sales rank. The best-selling item is at index 0.
* Sometimes you use the view to display the top 20 best-selling items.
* But other times you use the view to display the worst-selling items (perhaps to dump these items in order
* to make room for new inventory). You want to display the worst-selling item at index 0.
* And the second worst-selling item at index 1. Etc.
* This happens to be the opposite sorting order from how the items are in stored in the database.
* So you simply use the reverse option in mappings to handle the math for you.
*
* It's important to understand the relationship between reversing a group and the other mapping options
* (such as ranges and cell-drawing-dependencies):
*
* Once you reverse a group (setIsReversed:YES forGroup:group) you can visualize the view as reversed in your head,
* and set all other mappings options as if it was actually reversed.
*
* >>>>> ORDER MATTERS <<<<<
*
* To be more precise:
*
* - After reversing a group, you can pass in rangeOptions as if the group were actually reversed in the database.
* This makes it easier to configure, as your mental model can match how you configure it.
*
* rangeOptions = [YapDatabaseViewRangeOptions fixedRangeWithLength:20 offset:0 from:YapDatabaseViewEnd];
* [mappings setRangeOptions:rangeOptions forGroup:@"books"];
* [mappings setIsReversed:YES forGroup:@"books"];
*
* is EQUIVALENT to:
*
* [mappings setIsReversed:YES forGroup:@"books"];
* rangeOptions = [YapDatabaseViewRangeOptions fixedRangeWithLength:20 offset:0 from:YapDatabaseViewBeginning];
* [mappings setRangeOptions:rangeOptions forGroup:@"books"];
*
* - In terms of cell-drawing-dependencies, its a similar effect.
*
* [mappings setCellDrawingDependencyForNeighboringCellWithOffset:+1 forGroup:@"books"]; // <-- Positive one
* [mappings setIsReversed:YES forGroup:@"books"];
*
* is EQUIVALENT to:
*
* [mappings setCellDrawingDependencyForNeighboringCellWithOffset:-1 forGroup:@"books"]; // <-- Negative one
* [mappings setIsReversed:YES forGroup:@"books"];
*
*
* In general, if you wish to visualize other configuration options in terms of how they're going to be displayed
* in your user interface, you should reverse the group BEFORE you make other configuration changes.
* Alternatively you might visualize it differently. Perhaps you're imaging the database view, applying
* range options first, and then reversing the final product for dispaly in a tableView.
* So in this case you should reverse the group AFTER you make other configuration changes.
*
* It's simply a matter of how you visualize it.
* Either order is fine, but one likely makes more sense in your head.
*/
- (void)setIsReversed:(BOOL)isReversed forGroup:(NSString *)group;
- (BOOL)isReversedForGroup:(NSString *)group;
/**
* This configuration allows you to take multiple groups in a database view,
* and display them in a single section in your tableView / collectionView.
*
* It's called a "consolidated group".
*
* Further, you can configure a threshold where the mappings will automatically switch between
* using a "consolidated group" and normal mode.
*
* This is useful for those situations where the total number of items in your tableView
* could be very small or very big. When the count is small, you don't want to use sections.
* But when the count reaches a certain size, you do want to use sections.
* For these situations, you can configure the threshold to meet your requirements,
* and mappings will automatically handle everything for you.
* Including animating the changes when switching back and forth between consolidated mode and normal mode.
*
* The threshold represents the point at which the transition occurs. That is:
* - if the total number of items is less than the threshold, then consolidated mode will be used.
* - if the total number of items is equal or greater than the threshold, then normal mode will be used.
*
* If the threshold is 0, then auto consolidation is disabled.
*
* For example, imagine you're displaying a list of contacts.
* You might setup a view like this:
*
* view = @{
* @"A" : @[ @"Allison Jones" ],
* @"B" : @[ @"Billy Bob", @"Brandon Allen" ],
* @"R" : @[ @"Ricky Bobby" ]
* }
*
* The total number of contacts is only 4. So it might look better to display them without sections.
* However, you're going to want to switch to sections at some point. Perhaps when the total count reaches 10?
*
* It would be nice if there was something to handle this for you.
* And even better if that something would help you properly animate the tableView
* when switching between no-sections & sections (and vice versa).
* This is exactly what the autoConsolidateGroupsThreshold does for you!
*
* The default threshold value is 0 (disabled).
*/
- (void)setAutoConsolidateGroupsThreshold:(NSUInteger)threshold withName:(NSString *)consolidatedGroupName;
- (NSUInteger)autoConsolidateGroupsThreshold;
- (NSString *)consolidatedGroupName;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Initialization & Updates
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* You have to call this method at least once to initialize the mappings.
* This method uses the given transaction to fetch and cache the counts for each group.
*
* Mappings are implicitly tied to a databaseConnection's longLivedReadTransaction.
* That is, when you invoke [databaseConnection beginLongLivedReadTransaction] you are freezing the
* connection on a particular commit (a snapshot of the database at that point in time).
* Mappings must always be on the same snapshot as its corresponding databaseConnection.
*
* Eventually, you move the databaseConnection to the latest commit.
* You do by invoking [databaseConnection beginLongLivedReadTransaction] again.
* And when you do this you MUST ensure the mappings are also updated to match the databaseConnection's new snapshot.
*
* There are 2 ways to do this:
*
* - Invoke getSectionChanges:rowChanges:forNotifications:withMappings:.
* That method requires the 'before' & 'after' snapshot of the mappings in order to calculate the proper changeset.
* And in order to get this, it automatically invokes this method.
*
* - Invoke this method again.
* And do NOT invoke getSectionChanges:rowChanges:forNotifications:withMappings:.
* You might take this route if the viewController isn't visible,
* and you're simply planning on doing a [tableView reloadData].
*
* Please also see the example code above.
*/
- (void)updateWithTransaction:(YapDatabaseReadTransaction *)transaction;
/**
* Returns the snapshot of the last time the mappings were initialized/updated.
*
* This method is primarily for internal use.
* When the changesets are being calculated from the notifications & mappings,
* this property is consulted to ensure the mappings match the notifications.
*
* Everytime the updateWithTransaction method is invoked,
* this property will be set to transaction.abstractConnection.snapshot.
*
* If never initialized/updated, the snapshot will be UINT64_MAX.
*
* @see YapDatabaseConnection snapshot
*/
@property (nonatomic, readonly) uint64_t snapshotOfLastUpdate;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Getters
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the actual number of visible sections.
*
* This number may be less than the original count of groups passed in the init method.
* That is, if dynamic sections are enabled for one or more groups, and some of these groups have zero items,
* then those groups will be removed from the visible list of groups. And thus the section count may be less.
*/
- (NSUInteger)numberOfSections;
/**
* Returns the number of items in the given section.
* @see groupForSection
*/
- (NSUInteger)numberOfItemsInSection:(NSUInteger)section;
/**
* Returns the number of items in the given group.
*
* This is the cached value from the last time one of the following methods was invoked:
* - updateWithTransaction:
* - changesForNotifications:withMappings:
*/
- (NSUInteger)numberOfItemsInGroup:(NSString *)group;
/**
* The visibleGroups property returns the current sections setup.
* That is, it only contains the visible groups that are being represented as sections in the view.
*
* If all sections are static, then visibleGroups will always be the same as allGroups.
* However, if one or more sections are dynamic, then the visible groups may be a subset of allGroups.
*
* Dynamic groups/sections automatically "disappear" if/when they become empty.
*/
- (NSArray *)visibleGroups;
/**
* Returns YES if there are zero items in all sections/groups.
*/
- (BOOL)isEmpty;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Mapping: UI -> YapDatabaseView
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Maps from a section (in the UI) to a group (in the View).
*
* Returns the group for the given section.
* This method properly takes into account dynamic groups.
*
* If the section is out-of-bounds, returns nil.
*/
- (nullable NSString *)groupForSection:(NSUInteger)section;
/**
* Maps from an indexPath (in the UI) to a group & index (within the View).
*
* When your UI doesn't exactly match up with the View in the database, this method does all the math for you.
*
* For example, if using rangeOptions, the rows in your tableView/collectionView may not
* directly match the index in the corresponding view & group (in the database).
*
* For example, say a view in the database has a group named "elders" and contains 100 items.
* A fixed range is used to display only the last 20 items in the "elders" group (the 20 oldest elders).
* Thus row zero in the tableView is actually index 80 in the "elders" group.
*
* So you pass in an indexPath or row & section from the UI perspective,
* and it spits out the corresponding index within the database view's group.
*
* Code sample:
*
* - (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath
* {
* NSString *group = nil;
* NSUInteger groupIndex = 0;
*
* [mappings getGroup:&group index:&groupIndex forIndexPath:indexPath];
*
* __block Elder *elder = nil;
* [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
*
* elder = [[transaction extension:@"elders"] objectAtIndex:groupIndex inGroup:group];
* }];
*
* // configure and return cell...
* }
*/
- (BOOL)getGroup:(NSString * _Nonnull * _Nullable)groupPtr index:(nullable NSUInteger *)indexPtr forIndexPath:(NSIndexPath *)indexPath;
/**
* Maps from an indexPath (in the UI) to a group & index (within the View).
*
* When your UI doesn't exactly match up with the View in the database, this method does all the math for you.
*
* For example, if using rangeOptions, the rows in your tableView/collectionView may not
* directly match the index in the corresponding view & group (in the database).
*
* For example, say a view in the database has a group named "elders" and contains 100 items.
* A fixed range is used to display only the last 20 items in the "elders" group (the 20 oldest elders).
* Thus row zero in the tableView is actually index 80 in the "elders" group.
*
* So you pass in an indexPath or row & section from the UI perspective,
* and it spits out the corresponding index within the database view's group.
*
* Code sample:
*
* - (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath
* {
* NSString *group = nil;
* NSUInteger groupIndex = 0;
*
* [mappings getGroup:&group index:&groupIndex forIndexPath:indexPath];
*
* __block Elder *elder = nil;
* [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
*
* elder = [[transaction extension:@"elders"] objectAtIndex:groupIndex inGroup:group];
* }];
*
* // configure and return cell...
* }
*/
- (BOOL)getGroup:(NSString * _Nonnull * _Nullable)groupPtr
index:(nullable NSUInteger *)indexPtr
forRow:(NSUInteger)row
inSection:(NSUInteger)section;
/**
* Maps from a row & section (in the UI) to an index (within the View).
*
* This method is shorthand for getGroup:index:forIndexPath: when you already know the group.
* @see getGroup:index:forIndexPath:
*
* Returns NSNotFound if the given row & section are invalid.
*/
- (NSUInteger)indexForRow:(NSUInteger)row inSection:(NSUInteger)section;
/**
* Maps from a row & section (in the UI) to an index (within the View).
*
* This method is shorthand for getGroup:index:forIndexPath: when you already know the group.
* @see getGroup:index:forIndexPath:
*
* Returns NSNotFound if the given row & group are invalid.
*/
- (NSUInteger)indexForRow:(NSUInteger)row inGroup:(NSString *)group;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Mapping: YapDatabaseView -> UI
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Maps from a group (in the View) to the corresponding section (in the UI).
*
* Returns the visible section number for the visible group.
* Returns NSNotFound if the group is NOT visible (or invalid).
*/
- (NSUInteger)sectionForGroup:(NSString *)group;
/**
* Maps from an index & group (in the View) to the corresponding row & section (in the UI).
*
* Returns YES if the proper row & section were found.
* Returns NO if the given index is NOT visible (or out-of-bounds).
* Returns NO if the given group is NOT visible (or invalid).
*/
- (BOOL)getRow:(nullable NSUInteger *)rowPtr
section:(nullable NSUInteger *)sectionPtr
forIndex:(NSUInteger)index
inGroup:(NSString *)group;
/**
* Maps from an index & group (in the View) to the corresponding indexPath (in the UI).
*
* Returns the indexPath with the proper section and row.
* Returns nil if the given index & group is NOT visible (or out-of-bounds).
*/
- (nullable NSIndexPath *)indexPathForIndex:(NSUInteger)index inGroup:(NSString *)group;
/**
* Maps from an index & group (in the View) to the corresponding row (in the UI).
*
* This method is shorthand for getRow:section:forIndex:inGroup: when you already know the section.
* @see getRow:section:forIndex:inGroup:
*
* Returns NSNotFound if the given index & group is NOT visible (or out-of-bounds).
*/
- (NSUInteger)rowForIndex:(NSUInteger)index inGroup:(NSString *)group;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Getters + Consolidation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Whether or not the groups have been automatically consolidated due to the configured autoConsolidateGroupsThreshold.
*/
- (BOOL)isUsingConsolidatedGroup;
/**
* Returns the total number of items by summing up the totals across all groups.
*/
- (NSUInteger)numberOfItemsInAllGroups;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Getters + Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The YapDatabaseViewRangePosition struct represents the range window within the full group.
* For example:
*
* You have a section in your tableView which represents a group that contains 100 items.
* However, you've setup rangeOptions to only display the first 20 items:
*
* YapDatabaseViewRangeOptions *rangeOptions =
* [YapDatabaseViewRangeOptions fixedRangeWithLength:20 offset:0 from:YapDatabaseViewBeginning];
* [mappings setRangeOptions:rangeOptions forGroup:@"sales"];
*
* The corresponding rangePosition would be: (YapDatabaseViewRangePosition){
* .offsetFromBeginning = 0,
* .offsetFromEnd = 80,
* .length = 20
* }
*/
- (YapDatabaseViewRangePosition)rangePositionForGroup:(NSString *)group;
/**
* This is a helper method to assist in maintaining the selection while updating the tableView/collectionView.
* In general the idea is this:
* - yapDatabaseModified is invoked on the main thread
* - at the beginning of the method, you grab some information about the current selection
* - you update the database connection, and then start the animation for the changes to the table
* - you reselect whatever was previously selected
* - if that's not possible (row was deleted) then you select the closest row to the previous selection
*
* The last step isn't always what you want to do. Maybe you don't want to select anything at that point.
* But if you do, then this method can simplify the task for you.
*
* For example:
*
* - (void)yapDatabaseModified:(NSNotification *)notification {
*
* // Grab info about current selection
*
* NSString *selectedGroup = nil;
* NSUInteger selectedRow = 0;
* __block NSString *selectedWidgetId = nil;
*
* NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
* if (selectedIndexPath) {
* selectedGroup = [mappings groupForSection:selectedIndexPath.section];
* selectedRow = selectedIndexPath.row;
*
* [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
* selectedWidgetId = [[transaction ext:@"widgets"] keyAtIndex:selectedRow inGroup:selectedGroup];
* }];
* }
*
* // Update the database connection (move it to the latest commit)
*
* NSArray *notifications = [databaseConnection beginLongLivedReadTransaction];
*
* // Process the notification(s),
* // and get the changeset as it applies to me, based on my view and my mappings setup.
*
* NSArray *sectionChanges = nil;
* NSArray *rowChanges = nil;
*
* [[databaseConnection ext:@"order"] getSectionChanges:§ionChanges
* rowChanges:&rowChanges
* forNotifications:notifications
* withMappings:mappings];
*
* if ([sectionChanges count] == 0 & [rowChanges count] == 0)
* {
* // Nothing has changed that affects our tableView
* return;
* }
*
* // Update the table (animating the changes)
*
* [self.tableView beginUpdates];
*
* for (YapDatabaseViewSectionChange *sectionChange in sectionChanges)
* {
* // ... (see https://github.com/yapstudios/YapDatabase/wiki/Views )
* }
*
* for (YapDatabaseViewRowChange *rowChange in rowChanges)
* {
* // ... (see https://github.com/yapstudios/YapDatabase/wiki/Views )
* }
*
* [self.tableView endUpdates];
*
* // Try to reselect whatever was selected before
*
* __block NSIndexPath *indexPath = nil;
*
* if (selectedIndexPath) {
* [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
* indexPath = [[transaction ext:@"widgets"] indexPathForKey:selectedWidgetId
* withMappings:mappings];
* }];
* }
*
* // Otherwise select the nearest row to whatever was selected before
*
* if (!indexPath && selectedGroup) {
* indexPath = [mappings nearestIndexPathForRow:selectedRow inGroup:selectedGroup];
* }
*
* if (indexPath) {
* [self.tableView selectRowAtIndexPath:indexPath
* animated:NO
* scrollPosition:UITableViewScrollPositionMiddle];
* }
* }
*/
- (nullable NSIndexPath *)nearestIndexPathForRow:(NSUInteger)row inGroup:(NSString *)group;
@end
Undocumented
-
Initializes a new mappings object. Use this initializer when the groups, and their order, are known at initialization time.
Declaration
Objective-C
+ (nonnull instancetype) mappingsWithGroups:(nonnull NSArray<NSString *> *)allGroups view:(nonnull NSString *)registeredViewName;
Parameters
allGroups
The ordered array of group names. From the example above, this would be @[ @
wine
, @liquor
, @beer
]registeredViewName
This is the name of the view, as you registered it with the database system.
-
Initializes a new mappings object with a static list of groups. Use this initializer when the groups, and their order, are known at initialization time.
Declaration
Objective-C
- (nonnull id)initWithGroups:(nonnull NSArray<NSString *> *)allGroups view:(nonnull NSString *)registeredViewName;
Swift
init(groups allGroups: [String], view registeredViewName: String)
Parameters
allGroups
The ordered array of group names. From the example above, this would be @[ @
wine
, @liquor
, @beer
]registeredViewName
This is the name of the view, as you registered it with the database system.
-
Initializes a new mappings object that uses a filterBlock and a sortBlock to dynamically construct sections from view.
Declaration
Objective-C
- (nonnull id) initWithGroupFilterBlock: (nonnull YapDatabaseViewMappingGroupFilter)filterBlock sortBlock:(nonnull YapDatabaseViewMappingGroupSort)sortBlock view:(nonnull NSString *)registeredViewName;
Swift
init(groupFilterBlock filterBlock: @escaping YapDatabaseViewMappingGroupFilter, sortBlock: @escaping YapDatabaseViewMappingGroupSort, view registeredViewName: String)
Parameters
filterBlock
Block that takes a string and returns a BOOL. returning YES will include the group in the sections of the mapping.
sortBlock
Block used to sort group names for groups that pass the filter.
registeredViewName
This is the name of the view, as you registered it with the database system.
-
The allGroups property returns the groups that were passed in the init method. That is, all groups, whether currently visible or non-visible.
See
visibleGroupsDeclaration
Objective-C
@property (readonly, copy, nonatomic) NSArray<NSString *> *_Nonnull allGroups;
Swift
var allGroups: [String] { get }
-
The registeredViewName that was passed in the init method.
Declaration
Objective-C
@property (readonly, copy, nonatomic) NSString *_Nonnull view;
Swift
var view: String { get }
-
A group/section can either be
static
ordynamic
.A dynamic section automatically disappears if it becomes empty. A static section is always visible, regardless of its item count.
By default all groups/sections are static. You can enable dynamic sections on a per-group basis (just for certain sections) or for all groups (all sections).
If you enable dynamic sections, be sure to use the helper methods available in this class. This will drastically simplify things for you. For example:
Let’s say you have 3 groups: @[ @
wine
, @liquor
, @beer
] You’ve enabled dynamic sections for all groups. Section 0 refers to what group?The answer depends entirely on the item count per section. If
wine
is empty, butliquor
isn’t, then section zero isliquor
. Ifwine
andliquor
are empty, butbeer
isn’t, then section zero isbeer
.But you can simply do this to get the answer:
NSString *group = [mappings groupForSection:indexPath.section];
See
numberOfSectionsSee
groupForSection:See
visibleGroupsAlso note that there’s an extremely helpful category: YapDatabaseViewTransaction(Mappings). Methods in this category will be of great help as you take advantage of advanced mappings configurations. For example:
id object = [[transaction ext:@
myView
] objectAtIndexPath:indexPath withMappings:mappings];These methods are extensively unit tested to ensure they work properly with all kinds of mappings configurations. Translation: You can use them without thinking, and they’ll just work everytime.
The mappings object is also used to assist with tableView/collectionView change animations:
- YapDatabaseViewConnection getSectionChanges:rowChanges:forNotifications:withMappings:
If all your sections are static, then you won’t ever get any section changes. But if you have one or more dynamic sections, then be sure to process the section changes. As the dynamic sections disappear & re-appear, the proper section changes will be emitted.
By DEFAULT, all groups/sections are STATIC.
You can configure this however you want to meet your needs. This includes per-group configuration, all-at-once, and even overrides.
ORDER MATTERS.
If you invoke setIsDynamicSectionForAllGroups, this sets the configuration for every group. Including future groups if using dynamic groups via initWithGroupFilterBlock:sortBlock:view:.
Once the configuration is set for all groups, you can then choose to provide overriden settings for select groups. That is, if you then invoke setIsDynamicSection:forGroup: is will override the
global
setting for this particular group.Declaration
Objective-C
- (void)setIsDynamicSection:(BOOL)isDynamic forGroup:(nonnull NSString *)group;
Swift
func setIsDynamicSection(_ isDynamic: Bool, forGroup group: String)
-
Undocumented
Declaration
Objective-C
- (BOOL)isDynamicSectionForGroup:(NSString *)group;
Swift
func isDynamicSection(forGroup group: String) -> Bool
-
Undocumented
Declaration
Objective-C
- (void)setIsDynamicSectionForAllGroups:(BOOL)isDynamic;
Swift
func setIsDynamicSectionForAllGroups(_ isDynamic: Bool)
-
Undocumented
Declaration
Objective-C
- (BOOL)isDynamicSectionForAllGroups;
Swift
func isDynamicSectionForAllGroups() -> Bool
-
You can use the YapDatabaseViewRangeOptions class to configure a
range
that you would like to restrict your tableView / collectionView to.Two types of ranges are supported:
Fixed ranges. This is similar to using a LIMIT & OFFSET in a typical sql query.
Flexible ranges. These allow you to specify an initial range, and allow it to grow and shrink.
The YapDatabaseViewRangeOptions header file has a lot of documentation on setting up and configuring range options.
One of the best parts of using rangeOptions is that you get animations for free. For example:
Say you have view that sorts items by sales rank. You want to display a tableView that displays the top 20 best-sellers. Simple enough so far. But you want the tableView to automatically update throughout the day as sales are getting processed. And you want the tableView to automatically animate any changes. (No wimping out with reloadData!) You can get this with only a few lines of code using range options.
Note that if you’re using range options, then the indexPaths in your UI might not match up directly with the indexes in the view’s group. But don’t worry. Just use the methods in
YapDatabaseViewTransaction (Mappings)
to automatically handle it all for you. Or, if you want to be advanced, the various mapping methods in this class.The rangeOptions you pass in are copied, and YapDatabaseViewMappings keeps a private immutable version of them. So if you make changes to the rangeOptions, you need to invoke this method again to set the changes.
Declaration
Objective-C
- (void)setRangeOptions:(nullable YapDatabaseViewRangeOptions *)rangeOpts forGroup:(nonnull NSString *)group;
Swift
func setRangeOptions(_ rangeOpts: YapDatabaseViewRangeOptions?, forGroup group: String)
-
Undocumented
Declaration
Objective-C
- (nullable YapDatabaseViewRangeOptions *)rangeOptionsForGroup:(NSString *)group;
Swift
func rangeOptions(forGroup group: String) -> YapDatabaseViewRangeOptions?
-
Undocumented
Declaration
Objective-C
- (void)removeRangeOptionsForGroup:(NSString *)group;
Swift
func removeRangeOptions(forGroup group: String)
-
There are some times when the drawing of one cell depends somehow on a neighboring cell. For example:
Apple’s SMS messaging app draws a timestamp if more than a certain amount of time has elapsed between a message and the previous message. The timestamp is actually drawn at the top of a cell. So cell-B would draw a timestamp at the top of its cell if cell-A represented a message that was sent/received say 3 hours ago.
We refer to this as a
cell drawing dependency
. For the example above, the timestamp drawing is dependent upon the cell at offset -1. That is, the drawing of cell at index 5 is dependent upon the cell at index (5-1).This method allows you to specify if there are cell drawing dependecies. For the example above you could simply do the following:
[mappings setCellDrawingDependencyForNeighboringCellWithOffset:-1 forGroup:@“”]
This will inject extra YapDatabaseViewChangeUpdate’s for cells that may have been affected by other Insert/Delete/Update/Move operations (and thus need to be redrawn).
Continuing the example above, if the item at index 7 is deleted, then changeset processing will automatically emit and update change for the item that was previously at index 8. This is because, as specified by the
cell drawing offset
configuration, the drawing of index 8 was dependent upon the item before it (offset=-1). The item before it has changed, so it gets an update emitted for it automatically.Using this configuration makes it exteremely simple to handle various
cell drawing dependencies
. You can just ask for changesets as you would if there weren’t any dependencies, perform the boiler-plate updates, and everything just works.Note that if a YapDatabaseViewChangeUpdate is emitted due to a cell drawing dependeny, AND there were no actual updates for the corresponding item, and you’d like to detect these changes for whatever reason (optimizing, etc), then you can do so by checking to see if the rowChange.changes == YapDatabaseViewChangedDependency.
If you have multiple cell drawing dependencies (e.g. +1 & -1), then you can pass in an NSSet of NSNumbers.
Declaration
Objective-C
- (void)setCellDrawingDependencyForNeighboringCellWithOffset:(NSInteger)offset forGroup: (nonnull NSString *) group;
Swift
func setCellDrawingDependencyForNeighboringCellWithOffset(_ offset: Int, forGroup group: String)
-
Undocumented
Declaration
Objective-C
- (void)setCellDrawingDependencyOffsets:(NSSet<NSNumber *> *)offsets forGroup:(NSString *)group;
Swift
func setCellDrawingDependencyOffsets(_ offsets: Set<NSNumber>, forGroup group: String)
-
Undocumented
Declaration
Objective-C
- (NSSet<NSNumber *> *)cellDrawingDependencyOffsetsForGroup:(NSString *)group;
Swift
func cellDrawingDependencyOffsets(forGroup group: String) -> Set<NSNumber>
-
You can tell mappings to reverse a group/section if you’d like to display it in your tableView/collectionView in the opposite direction in which the items actually exist within the database.
For example:
You have a database view which sorts items by sales rank. The best-selling item is at index 0. Sometimes you use the view to display the top 20 best-selling items. But other times you use the view to display the worst-selling items (perhaps to dump these items in order to make room for new inventory). You want to display the worst-selling item at index 0. And the second worst-selling item at index 1. Etc. This happens to be the opposite sorting order from how the items are in stored in the database. So you simply use the reverse option in mappings to handle the math for you.
It’s important to understand the relationship between reversing a group and the other mapping options (such as ranges and cell-drawing-dependencies):
Once you reverse a group (setIsReversed:YES forGroup:group) you can visualize the view as reversed in your head, and set all other mappings options as if it was actually reversed.
ORDER MATTERS <<<<<
To be more precise:
- After reversing a group, you can pass in rangeOptions as if the group were actually reversed in the database. This makes it easier to configure, as your mental model can match how you configure it.
rangeOptions = [YapDatabaseViewRangeOptions fixedRangeWithLength:20 offset:0 from:YapDatabaseViewEnd]; [mappings setRangeOptions:rangeOptions forGroup:@
books
]; [mappings setIsReversed:YES forGroup:@books
];is EQUIVALENT to:
[mappings setIsReversed:YES forGroup:@
books
]; rangeOptions = [YapDatabaseViewRangeOptions fixedRangeWithLength:20 offset:0 from:YapDatabaseViewBeginning]; [mappings setRangeOptions:rangeOptions forGroup:@books
];- In terms of cell-drawing-dependencies, its a similar effect.
[mappings setCellDrawingDependencyForNeighboringCellWithOffset:+1 forGroup:@
books
]; // <– Positive one [mappings setIsReversed:YES forGroup:@books
];is EQUIVALENT to:
[mappings setCellDrawingDependencyForNeighboringCellWithOffset:-1 forGroup:@
books
]; // <– Negative one [mappings setIsReversed:YES forGroup:@books
];In general, if you wish to visualize other configuration options in terms of how they’re going to be displayed in your user interface, you should reverse the group BEFORE you make other configuration changes. Alternatively you might visualize it differently. Perhaps you’re imaging the database view, applying range options first, and then reversing the final product for dispaly in a tableView. So in this case you should reverse the group AFTER you make other configuration changes.
It’s simply a matter of how you visualize it. Either order is fine, but one likely makes more sense in your head.
Declaration
Objective-C
- (void)setIsReversed:(BOOL)isReversed forGroup:(nonnull NSString *)group;
Swift
func setIsReversed(_ isReversed: Bool, forGroup group: String)
-
Undocumented
Declaration
Objective-C
- (BOOL)isReversedForGroup:(NSString *)group;
Swift
func isReversed(forGroup group: String) -> Bool
-
This configuration allows you to take multiple groups in a database view, and display them in a single section in your tableView / collectionView.
It’s called a
consolidated group
.Further, you can configure a threshold where the mappings will automatically switch between using a
consolidated group
and normal mode.This is useful for those situations where the total number of items in your tableView could be very small or very big. When the count is small, you don’t want to use sections. But when the count reaches a certain size, you do want to use sections. For these situations, you can configure the threshold to meet your requirements, and mappings will automatically handle everything for you. Including animating the changes when switching back and forth between consolidated mode and normal mode.
The threshold represents the point at which the transition occurs. That is:
- if the total number of items is less than the threshold, then consolidated mode will be used.
- if the total number of items is equal or greater than the threshold, then normal mode will be used.
If the threshold is 0, then auto consolidation is disabled.
For example, imagine you’re displaying a list of contacts. You might setup a view like this:
view = @{ @
A
: @[ @Allison Jones
], @B
: @[ @Billy Bob
, @Brandon Allen
], @R
: @[ @Ricky Bobby
] }The total number of contacts is only 4. So it might look better to display them without sections. However, you’re going to want to switch to sections at some point. Perhaps when the total count reaches 10?
It would be nice if there was something to handle this for you. And even better if that something would help you properly animate the tableView when switching between no-sections & sections (and vice versa). This is exactly what the autoConsolidateGroupsThreshold does for you!
The default threshold value is 0 (disabled).
Declaration
Objective-C
- (void)setAutoConsolidateGroupsThreshold:(NSUInteger)threshold withName: (nonnull NSString *)consolidatedGroupName;
Swift
func setAutoConsolidateGroupsThreshold(_ threshold: UInt, withName consolidatedGroupName: String)
-
Undocumented
Declaration
Objective-C
- (NSUInteger)autoConsolidateGroupsThreshold;
Swift
func autoConsolidateGroupsThreshold() -> UInt
-
Undocumented
Declaration
Objective-C
- (NSString *)consolidatedGroupName;
Swift
func consolidatedGroupName() -> String
-
You have to call this method at least once to initialize the mappings. This method uses the given transaction to fetch and cache the counts for each group.
Mappings are implicitly tied to a databaseConnection’s longLivedReadTransaction. That is, when you invoke [databaseConnection beginLongLivedReadTransaction] you are freezing the connection on a particular commit (a snapshot of the database at that point in time). Mappings must always be on the same snapshot as its corresponding databaseConnection.
Eventually, you move the databaseConnection to the latest commit. You do by invoking [databaseConnection beginLongLivedReadTransaction] again. And when you do this you MUST ensure the mappings are also updated to match the databaseConnection’s new snapshot.
There are 2 ways to do this:
Invoke getSectionChanges:rowChanges:forNotifications:withMappings:. That method requires the ‘before’ & ‘after’ snapshot of the mappings in order to calculate the proper changeset. And in order to get this, it automatically invokes this method.
Invoke this method again. And do NOT invoke getSectionChanges:rowChanges:forNotifications:withMappings:. You might take this route if the viewController isn’t visible, and you’re simply planning on doing a [tableView reloadData].
Please also see the example code above.
Declaration
Objective-C
- (void)updateWithTransaction:(nonnull YapDatabaseReadTransaction *)transaction;
Swift
func update(with transaction: YapDatabaseReadTransaction)
-
Returns the snapshot of the last time the mappings were initialized/updated.
This method is primarily for internal use. When the changesets are being calculated from the notifications & mappings, this property is consulted to ensure the mappings match the notifications.
Everytime the updateWithTransaction method is invoked, this property will be set to transaction.abstractConnection.snapshot.
If never initialized/updated, the snapshot will be UINT64_MAX.
See
YapDatabaseConnection snapshotDeclaration
Objective-C
@property (readonly, nonatomic) uint64_t snapshotOfLastUpdate;
Swift
var snapshotOfLastUpdate: UInt64 { get }
-
Returns the actual number of visible sections.
This number may be less than the original count of groups passed in the init method. That is, if dynamic sections are enabled for one or more groups, and some of these groups have zero items, then those groups will be removed from the visible list of groups. And thus the section count may be less.
Declaration
Objective-C
- (NSUInteger)numberOfSections;
Swift
func numberOfSections() -> UInt
-
Returns the number of items in the given section.
See
groupForSectionDeclaration
Objective-C
- (NSUInteger)numberOfItemsInSection:(NSUInteger)section;
Swift
func numberOfItems(inSection section: UInt) -> UInt
-
Returns the number of items in the given group.
This is the cached value from the last time one of the following methods was invoked:
- updateWithTransaction:
- changesForNotifications:withMappings:
Declaration
Objective-C
- (NSUInteger)numberOfItemsInGroup:(nonnull NSString *)group;
Swift
func numberOfItems(inGroup group: String) -> UInt
-
The visibleGroups property returns the current sections setup. That is, it only contains the visible groups that are being represented as sections in the view.
If all sections are static, then visibleGroups will always be the same as allGroups. However, if one or more sections are dynamic, then the visible groups may be a subset of allGroups.
Dynamic groups/sections automatically
disappear
if/when they become empty.Declaration
Objective-C
- (nonnull NSArray *)visibleGroups;
Swift
func visibleGroups() -> [Any]
-
Returns YES if there are zero items in all sections/groups.
Declaration
Objective-C
- (BOOL)isEmpty;
Swift
func isEmpty() -> Bool
-
Maps from a section (in the UI) to a group (in the View).
Returns the group for the given section. This method properly takes into account dynamic groups.
If the section is out-of-bounds, returns nil.
Declaration
Objective-C
- (nullable NSString *)groupForSection:(NSUInteger)section;
Swift
func group(forSection section: UInt) -> String?
-
Maps from an indexPath (in the UI) to a group & index (within the View).
When your UI doesn’t exactly match up with the View in the database, this method does all the math for you.
For example, if using rangeOptions, the rows in your tableView/collectionView may not directly match the index in the corresponding view & group (in the database).
For example, say a view in the database has a group named
elders
and contains 100 items. A fixed range is used to display only the last 20 items in theelders
group (the 20 oldest elders). Thus row zero in the tableView is actually index 80 in theelders
group.So you pass in an indexPath or row & section from the UI perspective, and it spits out the corresponding index within the database view’s group.
Code sample:
(UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *group = nil; NSUInteger groupIndex = 0;
[mappings getGroup:&group index:&groupIndex forIndexPath:indexPath];
__block Elder *elder = nil; [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
elder = [[transaction extension:@"elders"] objectAtIndex:groupIndex inGroup:group];
}];
// configure and return cell… }
Declaration
Objective-C
- (BOOL)getGroup:(NSString *_Nonnull *_Nullable)groupPtr index:(nullable NSUInteger *)indexPtr forIndexPath:(nonnull NSIndexPath *)indexPath;
Swift
func getGroup(_ groupPtr: AutoreleasingUnsafeMutablePointer<NSString>?, index indexPtr: UnsafeMutablePointer<UInt>?, for indexPath: IndexPath) -> Bool
-
Maps from an indexPath (in the UI) to a group & index (within the View).
When your UI doesn’t exactly match up with the View in the database, this method does all the math for you.
For example, if using rangeOptions, the rows in your tableView/collectionView may not directly match the index in the corresponding view & group (in the database).
For example, say a view in the database has a group named
elders
and contains 100 items. A fixed range is used to display only the last 20 items in theelders
group (the 20 oldest elders). Thus row zero in the tableView is actually index 80 in theelders
group.So you pass in an indexPath or row & section from the UI perspective, and it spits out the corresponding index within the database view’s group.
Code sample:
(UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *group = nil; NSUInteger groupIndex = 0;
[mappings getGroup:&group index:&groupIndex forIndexPath:indexPath];
__block Elder *elder = nil; [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
elder = [[transaction extension:@"elders"] objectAtIndex:groupIndex inGroup:group];
}];
// configure and return cell… }
Declaration
Objective-C
- (BOOL)getGroup:(NSString *_Nonnull *_Nullable)groupPtr index:(nullable NSUInteger *)indexPtr forRow:(NSUInteger)row inSection:(NSUInteger)section;
Swift
func getGroup(_ groupPtr: AutoreleasingUnsafeMutablePointer<NSString>?, index indexPtr: UnsafeMutablePointer<UInt>?, forRow row: UInt, inSection section: UInt) -> Bool
-
Maps from a row & section (in the UI) to an index (within the View).
This method is shorthand for getGroup:index:forIndexPath: when you already know the group.
See
getGroup:index:forIndexPath:Returns NSNotFound if the given row & section are invalid.
Declaration
Objective-C
- (NSUInteger)indexForRow:(NSUInteger)row inSection:(NSUInteger)section;
Swift
func index(forRow row: UInt, inSection section: UInt) -> UInt
-
Maps from a row & section (in the UI) to an index (within the View).
This method is shorthand for getGroup:index:forIndexPath: when you already know the group.
See
getGroup:index:forIndexPath:Returns NSNotFound if the given row & group are invalid.
Declaration
Objective-C
- (NSUInteger)indexForRow:(NSUInteger)row inGroup:(nonnull NSString *)group;
Swift
func index(forRow row: UInt, inGroup group: String) -> UInt
-
Maps from a group (in the View) to the corresponding section (in the UI).
Returns the visible section number for the visible group. Returns NSNotFound if the group is NOT visible (or invalid).
Declaration
Objective-C
- (NSUInteger)sectionForGroup:(nonnull NSString *)group;
Swift
func section(forGroup group: String) -> UInt
-
Maps from an index & group (in the View) to the corresponding row & section (in the UI).
Returns YES if the proper row & section were found. Returns NO if the given index is NOT visible (or out-of-bounds). Returns NO if the given group is NOT visible (or invalid).
Declaration
Objective-C
- (BOOL)getRow:(nullable NSUInteger *)rowPtr section:(nullable NSUInteger *)sectionPtr forIndex:(NSUInteger)index inGroup:(nonnull NSString *)group;
Swift
func getRow(_ rowPtr: UnsafeMutablePointer<UInt>?, section sectionPtr: UnsafeMutablePointer<UInt>?, for index: UInt, inGroup group: String) -> Bool
-
Maps from an index & group (in the View) to the corresponding indexPath (in the UI).
Returns the indexPath with the proper section and row. Returns nil if the given index & group is NOT visible (or out-of-bounds).
Declaration
Objective-C
- (nullable NSIndexPath *)indexPathForIndex:(NSUInteger)index inGroup:(nonnull NSString *)group;
Swift
func indexPath(for index: UInt, inGroup group: String) -> IndexPath?
-
Maps from an index & group (in the View) to the corresponding row (in the UI).
This method is shorthand for getRow:section:forIndex:inGroup: when you already know the section.
See
getRow:section:forIndex:inGroup:Returns NSNotFound if the given index & group is NOT visible (or out-of-bounds).
Declaration
Objective-C
- (NSUInteger)rowForIndex:(NSUInteger)index inGroup:(nonnull NSString *)group;
Swift
func row(for index: UInt, inGroup group: String) -> UInt
-
Whether or not the groups have been automatically consolidated due to the configured autoConsolidateGroupsThreshold.
Declaration
Objective-C
- (BOOL)isUsingConsolidatedGroup;
Swift
func isUsingConsolidatedGroup() -> Bool
-
Returns the total number of items by summing up the totals across all groups.
Declaration
Objective-C
- (NSUInteger)numberOfItemsInAllGroups;
Swift
func numberOfItemsInAllGroups() -> UInt
-
The YapDatabaseViewRangePosition struct represents the range window within the full group. For example:
You have a section in your tableView which represents a group that contains 100 items. However, you’ve setup rangeOptions to only display the first 20 items:
YapDatabaseViewRangeOptions *rangeOptions = [YapDatabaseViewRangeOptions fixedRangeWithLength:20 offset:0 from:YapDatabaseViewBeginning]; [mappings setRangeOptions:rangeOptions forGroup:@
sales
];The corresponding rangePosition would be: (YapDatabaseViewRangePosition){ .offsetFromBeginning = 0, .offsetFromEnd = 80, .length = 20 }
Declaration
Objective-C
- (YapDatabaseViewRangePosition)rangePositionForGroup:(nonnull NSString *)group;
Swift
func rangePosition(forGroup group: String) -> YapDatabaseViewRangePosition
-
This is a helper method to assist in maintaining the selection while updating the tableView/collectionView. In general the idea is this:
- yapDatabaseModified is invoked on the main thread
- at the beginning of the method, you grab some information about the current selection
- you update the database connection, and then start the animation for the changes to the table
- you reselect whatever was previously selected
- if that’s not possible (row was deleted) then you select the closest row to the previous selection
The last step isn’t always what you want to do. Maybe you don’t want to select anything at that point. But if you do, then this method can simplify the task for you.
For example:
(void)yapDatabaseModified:(NSNotification *)notification {
// Grab info about current selection
NSString *selectedGroup = nil; NSUInteger selectedRow = 0; __block NSString *selectedWidgetId = nil;
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; if (selectedIndexPath) { selectedGroup = [mappings groupForSection:selectedIndexPath.section]; selectedRow = selectedIndexPath.row;
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { selectedWidgetId = [[transaction ext:@"widgets"] keyAtIndex:selectedRow inGroup:selectedGroup]; }];
}
// Update the database connection (move it to the latest commit)
NSArray *notifications = [databaseConnection beginLongLivedReadTransaction];
// Process the notification(s), // and get the changeset as it applies to me, based on my view and my mappings setup.
NSArray *sectionChanges = nil; NSArray *rowChanges = nil;
[[databaseConnection ext:@
order
] getSectionChanges:§ionChanges rowChanges:&rowChanges forNotifications:notifications withMappings:mappings];if ([sectionChanges count] == 0 & [rowChanges count] == 0) { // Nothing has changed that affects our tableView return; }
// Update the table (animating the changes)
[self.tableView beginUpdates];
for (YapDatabaseViewSectionChange *sectionChange in sectionChanges) { // … (see https://github.com/yapstudios/YapDatabase/wiki/Views ) }
for (YapDatabaseViewRowChange *rowChange in rowChanges) { // … (see https://github.com/yapstudios/YapDatabase/wiki/Views ) }
[self.tableView endUpdates];
// Try to reselect whatever was selected before
__block NSIndexPath *indexPath = nil;
if (selectedIndexPath) { [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { indexPath = [[transaction ext:@
widgets
] indexPathForKey:selectedWidgetId withMappings:mappings]; }]; }// Otherwise select the nearest row to whatever was selected before
if (!indexPath && selectedGroup) { indexPath = [mappings nearestIndexPathForRow:selectedRow inGroup:selectedGroup]; }
if (indexPath) { [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionMiddle]; } }
Declaration
Objective-C
- (nullable NSIndexPath *)nearestIndexPathForRow:(NSUInteger)row inGroup:(nonnull NSString *)group;
Swift
func nearestIndexPath(forRow row: UInt, inGroup group: String) -> IndexPath?