These changes can happen from your own device or a replication. There are two parameters for the callback handler, the current snapshot of documents and an event that gives a bit more information about how snapshots evolve after subsequent calls.

Swift
let liveQuery = myCollection.find("store_id == 'abc123'").observeLocal { [weak self] documents, event in
    print("current snapshot of documents", documents)
    print("event information", event)
}

The event parameter gives you information about how the live query has evolved from callback fire. It can tell you which document indices were inserted, updated, moved, or removed. The following example below shows a common pattern to parse the event parameter and apply tableView.performBatchUpdates using the indexes.

The following code snippet assumes that you only have one UITableView section.

Swift
import UIKit
import DittoSwift

class ViewController: UIViewController: {

    var liveQuery: DittoLiveQuery?
    @IBOutlet weak var tableView!
    var documents: [DittoDocument] = []

    override func viewDidLoad(){
        super.viewDidLoad();
        tableView.dataSource = self

        liveQuery = collection.find("store_id == 'abc123'").observeLocal { [weak self] docs, event in
            guard let `self` = self else { return }
            switch event {
            case .update(let changes):
                guard changes.insertions.count > 0 || changes.deletions.count > 0 || changes.updates.count > 0  || changes.moves.count > 0 else { return }
                DispatchQueue.main.async {
                    self.tableView.beginUpdates()
                    self.tableView.performBatchUpdates({
                        let deletionIndexPaths = changes.deletions.map { idx -> IndexPath in
                            return IndexPath(row: idx, section: 0)
                        }
                        self.tableView.deleteRows(at: deletionIndexPaths, with: .automatic)
                        let insertionIndexPaths = changes.insertions.map { idx -> IndexPath in
                            return IndexPath(row: idx, section: 0)
                        }
                        self.tableView.insertRows(at: insertionIndexPaths, with: .automatic)
                        let updateIndexPaths = changes.updates.map { idx -> IndexPath in
                            return IndexPath(row: idx, section: 0)
                        }
                        self.tableView.reloadRows(at: updateIndexPaths, with: .automatic)
                        for move in changes.moves {
                            let from = IndexPath(row: move.from, section: 0)
                            let to = IndexPath(row: move.to, section: 0)
                            self.tableView.moveRow(at: from, to: to)
                        }
                    }) { _ in }
                    // Set the tasks array backing the UITableView to the new documents
                    self.tasks = docs
                    self.tableView.endUpdates()
                }
            case .initial:
                // Set the tasks array backing the UITableView to the new documents
                self.tasks = docs
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            default: break
            }
        }
    }
}

extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return documents.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let document = documents[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
        cell.textLabel?.text = document["text"].stringValue
        return cell
    }
}