YapDatabase icon indicating copy to clipboard operation
YapDatabase copied to clipboard

Updating grouping in tableView

Open onfoot opened this issue 9 years ago • 3 comments

Hi, Not quite an issue, perhaps I didn't look hard enough. I have a grouped UITableView that's fed by Yap's view. Item grouping is dependent on their optional property, say, Favorited. User can remove or add items to favorites and based on that the items are put in the first or second group. When the updated object is being sent to the database, the change notification is generated only for an item update, not delete-and-insert or a move. The updated grouping only comes into effect next time I launch the app (since the tableview is used in app's initial view controller).

Grouping block doesn't seem to be run upon the object update, so how do I trigger the grouping update?

onfoot avatar Jun 21 '16 15:06 onfoot

Are you handling rowChanges / sectionChanges as demonstrated in the wiki: https://github.com/yapstudios/YapDatabase/wiki/Views#animating_updates_in_tableviews_collectionviews

When the updated object is being sent to the database, the change notification is generated only for an item update, not delete-and-insert or a move. The updated grouping only comes into effect next time I launch the app (since the tableview is used in app's initial view controller).

Can you show me some example code so I can see what you're doing ?

Grouping block doesn't seem to be run upon the object update,

When you make changes to the object, are you saving it back to the database ?

robbiehanson avatar Jun 21 '16 16:06 robbiehanson

Yep, I'm doing that exactly like in the wiki.

Moving items from one group to another is a new thing that results from the functionality I'm adding. Until recently the items were being only removed from and added to the tableView, updated, and moved within a single group (in cases where the property used for sorting changed) and all that worked perfectly.

View's grouping block looks like this.

    if collection != currenciesCollection {
        return nil
    }

    if let currency = knownCurrencies[key] {
        if currency.isLocal() || currency.isDisabled() {
            return nil
        }

        if currency.favoriteOrder == nil {
            return otherCurrenciesGroupName
        } else {
            return mainCurrenciesGroupName
        }
    }

    return nil

Somewhere else in the code a currency is added to favorites (some boilerplate is removed)

    func addCurrencyToFavorites(currency: Currency) {
        self.connection.readWriteWithBlock { transaction in
           // fetching all current favorites, use the place after the last one as the insertion point
           // ...

            if let lastFavorite = currencies.last {
                currency.favoriteOrder = lastFavorite.favoriteOrder! + 1
            } else {
                currency.favoriteOrder = currency.order
            }

            transaction.setObject(currency, forKey: currency.identifier, inCollection: currenciesCollection)
        }
    }

The relevant part of the listening code:

        self.databaseListener = ScopedNotificationListener(name: YapDatabaseModifiedNotification, object: self.database, callback: { [weak self] note in

            guard let me = self else {
                return
            }

            let notifications = me.connection.beginLongLivedReadTransaction()

            var sectionChanges : NSArray? = nil
            var rowChanges : NSArray? = nil

            (me.connection.ext(self!.viewName) as! YapDatabaseViewConnection).getSectionChanges(&sectionChanges, rowChanges: &rowChanges, forNotifications: notifications, withMappings: self!.viewMappings)

            let sectionIndexesToDelete = NSMutableIndexSet()
            let sectionIndexesToInsert = NSMutableIndexSet()

            var rowIndexesToDelete : [NSIndexPath] = []
            var rowIndexesToInsert : [NSIndexPath] = []
            var rowIndexesToUpdate : [NSIndexPath] = []
            var rowIndexesToMove : [ItemMoveOperation] = []
            // I just realized that this part of code was written before Swift introduced Sets ;)

            // It's not updating the tableView directly

            if let changes = sectionChanges as? [YapDatabaseViewSectionChange] {
                for sectionChange in changes {
                    switch sectionChange.type {
                    case .Delete:
                        sectionIndexesToDelete.addIndex(Int(sectionChange.index))
                    case .Insert:
                        sectionIndexesToInsert.addIndex(Int(sectionChange.index))
                    default:
                        break
                    }
                }
            }

            if let changes = rowChanges as? [YapDatabaseViewRowChange] {

                for rowChange in changes {
                    switch rowChange.type {
                    case .Delete:
                        rowIndexesToDelete.append(rowChange.indexPath)
                    case .Insert:
                        rowIndexesToInsert.append(rowChange.newIndexPath)
                    case .Move:
                        rowIndexesToMove.append(ItemMoveOperation(from: rowChange.indexPath, to: rowChange.newIndexPath))
                    case .Update:
                        rowIndexesToUpdate.append(rowChange.indexPath)
                    }
                }
            }

    // ...

The view change listening part receives only a single row change: <YapDatabaseViewRowChange: Update indexPath(1, 0) group(other currencies) collectionKey(<YapCollectionKey collection(currencies) key(EUR)>.

Edit, after some additional digging:

One vital thing I didn't mention, that may albo lead to solving my "problem". In order to speed up view population, I set up grouping with key block, instead of object block. As the docs say:

If you'd like to get more advanced, you can specify exactly what should trigger an invocation of the block. For example:

If you use a 'GroupingWithObjectBlock', then normally the view will assume that any changes to the 'object' in the database means that the groupingBlock should be re-invoked (to check for changes to the group).

So if I switch to GroupingWithObjectBlock the favorites feature should start working. The issue with this is that it's likely more expensive to invoke grouping on every object change. In my case, the objects change every few seconds, since new currency rates come in. And the user will move the items between favorite/non-favorite states much less often, so it seems like a waste of resources. Would removing the object and adding it again within a single transaction invoke grouping? Or maybe I could trigger grouping manually?

rubber duck mode Hmm, replaceObject?

onfoot avatar Jun 22 '16 08:06 onfoot

In the end I indeed switched to GroupingWithObjectBlock and expected move notifications are now sent.

onfoot avatar Jun 24 '16 14:06 onfoot