{"id":19252225,"url":"https://github.com/bellapplab/blresultscontroller","last_synced_at":"2026-02-13T22:02:21.527Z","repository":{"id":54255672,"uuid":"144487932","full_name":"BellAppLab/BLResultsController","owner":"BellAppLab","description":"BLResultsController is not a drop-in replacement for the `NSFetchedResultsController` to be used with Realm. It's better.","archived":false,"fork":false,"pushed_at":"2023-02-15T18:08:52.000Z","size":5052,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-17T06:25:59.493Z","etag":null,"topics":["background-realm","carthage","cocoapods","ios","macos","osx","realm","realmswift","swift","tvos"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BellAppLab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-12T17:41:43.000Z","updated_at":"2025-06-23T12:13:04.000Z","dependencies_parsed_at":"2024-11-09T18:27:12.510Z","dependency_job_id":"a79e84b8-f607-4c41-aca1-d4b7c1db5420","html_url":"https://github.com/BellAppLab/BLResultsController","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/BellAppLab/BLResultsController","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BellAppLab%2FBLResultsController","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BellAppLab%2FBLResultsController/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BellAppLab%2FBLResultsController/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BellAppLab%2FBLResultsController/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BellAppLab","download_url":"https://codeload.github.com/BellAppLab/BLResultsController/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BellAppLab%2FBLResultsController/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271035462,"owners_count":24688415,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-18T02:00:08.743Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["background-realm","carthage","cocoapods","ios","macos","osx","realm","realmswift","swift","tvos"],"created_at":"2024-11-09T18:26:02.766Z","updated_at":"2026-02-13T22:02:21.442Z","avatar_url":"https://github.com/BellAppLab.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BLResultsController [![Version](https://img.shields.io/badge/Version-3.0.0-black.svg?style=flat)](#installation) [![License](https://img.shields.io/cocoapods/l/BLResultsController.svg?style=flat)](#license)\n\n[![Platforms](https://img.shields.io/badge/Platforms-iOS|tvOS|macOS-brightgreen.svg?style=flat)](#installation)\n[![Swift support](https://img.shields.io/badge/Swift-5.0%20%7C%205.3-red.svg?style=flat)](#swift-versions-support)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/BLResultsController.svg?style=flat\u0026label=CocoaPods)](https://cocoapods.org/pods/BLResultsController)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-red.svg?style=flat)](https://github.com/apple/swift-package-manager)\n[![Twitter](https://img.shields.io/badge/Twitter-@BellAppLab-blue.svg?style=flat)](http://twitter.com/BellAppLab)\n\n![BLResultsController](./Images/BLResultsController.png)\n\nContrary to popular belief, BLResultsController is **not** a drop-in replacement for the `NSFetchedResultsController` to be used with Realm. Oh no. It's _better_.\n\nA `ResultsController` takes a `Realm.Results` and divides its objects into sections based on the `sectionNameKeyPath` and the first `sortDescriptor`. It then calculates the relative positions of those objects and generates section indices and `IndexPath`s that are ready to be passed to `UITableView`s and `UICollectionView`s.\n\nBut **no expensive calculations are made on the main thread**. That's right. Everything is done in the background, so your UI will remain as smooth and responsive as always. \n\nAs with `Realm.Results`, the `ResultsController` is a live, auto-updating container that will keep notifying you of changes in the dataset for as long as you hold a strong reference to it. You register to receive those changes by calling `setChangeCallback(_:)` on your controller.\n\nChanges to the underlying dataset are calculated on a background queue, therefore the UI thread is not impacted by the `ResultsController`'s overhead.\n\n**Note**: As with `Realm` itself, the `ResultsController` is **not** thread-safe. You should only call most of its methods from the main thread.\n\n## Features\n\n- [X] Calculates everything on a **background thread**. 🏎\n- [X] No objects are retained, so memory footprint is minimal. 👾\n- [X] Calculates section index titles. 😲\n- [X] Allows for user-initiated search. 🕵️‍♀️🕵️‍♂️\n- [X] Most methods return in O(1). 😎\n- [X] Well documented. 🤓\n- [X] Well tested. 👩‍🔬👨‍🔬\n\n## Specs\n\n* RealmSwift 10.0.0+\n* iOS 13+\n* tvOS 13+\n* macOS 10.13+\n* Swift 5.0+\n\n`BLResultsController` also uses the amazing [BackgroundRealm](https://github.com/BellAppLab/BackgroundRealm). Have a look!\n\n## Example\n\n![ResultsController](./Images/results_controller.gif)\n\nInstall the `ResultsControllerElement` protocol on your `RealmObject` subclass:\n\n```swift\npublic final class Foo: Object, ResultsControllerElement\n{\n    //If your class doesn't have a unique identifier yet, do this\n    public dynamic var resultsControllerId: String = UUID().uuidString\n    \n    //If it does, you can do this\n    public var resultsControllerId: String {\n        return \u003c#id#\u003e\n    }\n}\n```\n\nThen:\n\n```swift\nimport UIKit\nimport RealmSwift\nimport BLResultsController\n\nclass ViewController: UITableViewController {\n    let controller: ResultsController\u003c\u003c#SectionType#\u003e\u003e, \u003c#ElementType#\u003e\u003e = {\n        do {\n            let realm = \u003c#instantiate your realm#\u003e\n            let keyPath = \u003c#the key path to your Element's property to be used as a section#\u003e\n            return try ResultsController(\n                realm: realm,\n                sectionNameKeyPath: keyPath,\n                sortDescriptors: [\n                    SortDescriptor(keyPath: keyPath)\n                ]\n            )\n        } catch {\n            assertionFailure(\"\\(error)\")\n            //do something about the error\n        }\n    }()\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        controller.setChangeCallback { [weak self] change in\n            switch change {\n            case .reload(_):\n                self?.tableView.reloadData()\n            case .sectionUpdate(_, let insertedSections, let deletedSections):\n                self?.tableView.beginUpdates()\n                insertedSections.forEach { self?.tableView.insertSections($0, with: .automatic) }\n                deletedSections.forEach { self?.tableView.deleteSections($0, with: .automatic) }\n                self?.tableView.endUpdates()\n            case .rowUpdate(_, let insertedItems, let deletedItems, let updatedItems):\n                self?.tableView.beginUpdates()\n                self?.tableView.insertRows(at: insertedItems, with: .automatic)\n                self?.tableView.deleteRows(at: deletedItems, with: .automatic)\n                self?.tableView.reloadRows(at: updatedItems, with: .automatic)\n                self?.tableView.endUpdates()\n            }\n        }\n        \n        controller.setFormatSectionIndexTitleCallback { (section, _) -\u003e String in\n            return section\n        }\n        \n        controller.setSortSectionIndexTitles { (sections, _) in\n            sections.sort(by: { $0 \u003c $1 })\n        }\n\n        controller.start()\n    }\n\n    override func numberOfSections(in tableView: UITableView) -\u003e Int {\n        return controller.numberOfSections()\n    }\n\n    override func tableView(_ tableView: UITableView,\n                            numberOfRowsInSection section: Int) -\u003e Int\n    {\n        return controller.numberOfItems(in: section)\n    }\n\n    override func tableView(_ tableView: UITableView,\n                            cellForRowAt indexPath: IndexPath) -\u003e UITableViewCell\n    {\n        guard let cell = tableView.dequeueReusableCell(withIdentifier: \u003c#identifier#\u003e) else {\n            fatalError(\"Did we configure the cell correctly on IB?\")\n        }\n        \u003c#code#\u003e\n        return cell\n    }\n    \n    override func tableView(_ tableView: UITableView,\n                            titleForHeaderInSection section: Int) -\u003e String?\n    {\n        return controller.section(at: section)\n    }\n    \n    override func sectionIndexTitles(for tableView: UITableView) -\u003e [String]? {\n        return controller.indexTitles()\n    }\n    \n    override func tableView(_ tableView: UITableView,\n                            sectionForSectionIndexTitle title: String,\n                            at index: Int) -\u003e Int\n    {\n        return controller.indexPath(forIndexTitle: title).section\n    }\n}\n```\n\nBoom 💥\n\n## Installation\n\n### Cocoapods\n\n```ruby\npod 'BLResultsController', '~\u003e 3.0'\n```\n\nThen `import BLResultsController` where needed.\n\n### Carthage\n\n```swift\ngithub \"BellAppLab/BLResultsController\" ~\u003e 3.0\n```\n\nThen `import BLResultsController` where needed.\n\n### Swift Package Manager\n\n```swift\n.package(url: \"https://github.com/BellAppLab/BLResultsController.git\", from: \"3.0\")\n```\n\n### Git Submodules\n\n```shell\ncd toYourProjectsFolder\ngit submodule add -b submodule --name BLResultsController https://github.com/BellAppLab/BLResultsController.git\n```\n\nThen drag the `BLResultsController` folder into your Xcode project.\n\n## Author\n\nBell App Lab, apps@bellapplab.com\n\n### Contributing\n\nCheck [this out](./CONTRIBUTING.md).\n\n### Credits\n\n- [Logo image](https://thenounproject.com/search/?q=controller\u0026i=316262#) by [Andres Flores](https://thenounproject.com/aflores158) from [The Noun Project](https://thenounproject.com/)\n- [Differ](https://github.com/tonyarnold/Differ) by [Tony Arnold](https://github.com/tonyarnold)\n\n## License\n\nBLResultsController is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbellapplab%2Fblresultscontroller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbellapplab%2Fblresultscontroller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbellapplab%2Fblresultscontroller/lists"}