Skip to content
Robbie Hanson edited this page Nov 11, 2019 · 1 revision

I can animate that.

The primary use case for Views is to be the data-source for a tableView or collectionView. And there are tools that make it easy to animate changes to your tableView or collectionView when the underlying YapDatabaseView changes.

Prerequisites

Show me the code

override func viewDidLoad() {

  // ...

  // Freeze our databaseConnection on the current commit.
  // This gives us a snapshot-in-time of the database,
  // and thus a stable data source for our UI thread.
  dbConnection.beginLongLivedReadTransaction()
    
  // Initialize our mappings.
  // Note that we do this AFTER we've started our database longLived transaction.
  initializeMappings()

  // And register for notifications when the database changes.
  // Our method will be invoked on the main-thread,
  // and will allow us to move our stable data-source from
  // our existing commit to an updated commit.
  let nc = NotificationCenter.default
  nc.addObserver( self,
	      selector: #selector(self.yapDatabaseModified(notification:)),
	          name: Notification.Name.YapDatabaseModified,
	        object: nil)
}

func initializeMappings() {
  
  dbConnection.read {(transaction) in
    guard
      let _ = transaction.ext("SalesRank") as? YapDatabaseViewTransaction
    else {
      // The underlying View isn't ready yet.
      // Delay creating mappings until it's ready.
      return
    }
    
    // The view may have a whole bunch of groups.
    // In this example, we just want to look at a single group (1 section).
    let groups = ["fantasy", "sci-fi", "horror"]
    mappings = YapDatabaseViewMappings(groups:groups, view:"SalesRank")
    
    // One-time initialization
    mappings?.update(with: transaction)
  }
}

func numberOfSections(in tableView: UITableView) -> Int {
  if let mappings = self.mappings {
    return Int(mappings.numberOfSections())
  } else {
    return 0
  }
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  if let mappings = self.mappings {
    return Int(mappings.numberOfItems(inSection: UInt(section)))
  } else {
    return 0
  }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  
  var obj: Any? = nil
  dbConnection.read {(transaction) in
	  if let viewTransaction = transaction.ext("SalesRank") as? YapDatabaseViewTransaction,
       let mappings = self.mappings
    {
      obj = viewTransaction.object(at: indexPath, with: mappings)
    }
  }
  
  // configure and return cell...
}

@objc func yapDatabaseModified(notification: Notification) {

  // Jump to the most recent commit.
  // End & Re-Begin the long-lived transaction atomically.
  // Also grab all the notifications for all the commits that I jump.
  // If the UI is a bit backed up, I may jump multiple commits.
  let notifications = dbConnection.beginLongLivedReadTransaction()

  guard let mappings = self.mappings {
    initializeMappings()
    self.tableView.reloadData()
    return
  }
  
  guard let ext = dbConnection.ext("SalesRank") as? YapDatabaseViewConnection else {
    return
  }
  
  // Process the notification(s),
  // and get the change-set(s),
  // as applies to my view and mappings configuration.
  let (sectionChanges, rowChanges) = ext.getChanges(forNotifications: notifications, withMappings: mappings)
  
  if (sectionChanges.count == 0) && (rowChanges.count == 0) {
    // There aren't any changes that affect our tableView!
    return
  }

  // Animate tableView updates !
  tableView.beginUpdates()
  for change in sectionChanges {
    switch change.type {
      case .delete:
        tableView.deleteSections(IndexSet(integer: Int(change.index)), with: .automatic)

      case .insert:
        tableView.insertSections(IndexSet(integer: Int(change.index)), with: .automatic)
      
      default: break
    }
  }
  for change in rowChanges {
    switch change.type {
      case .delete:
        tableView.deleteRows(at: [change.indexPath!], with: .automatic)

      case .insert:
        tableView.insertRows(at: [change.newIndexPath!], with: .automatic)

      case .move:
        tableView.moveRow(at: change.indexPath!, to: change.newIndexPath!)
      
      case .update:
        tableView.reloadRows(at: [change.indexPath!], with: .automatic)

      default: break
    }
  }
  tableView.endUpdates()
}