UITableView with Multiple Sections using Realm and Swift
Here's some sample code that does exactly what you're looking for:
import UIKit
import RealmSwift
class Dog: Object {
dynamic var name = ""
dynamic var race = ""
dynamic var age = 0
dynamic var owner = ""
dynamic var dogID = ""
override static func primaryKey() -> String? {
return "dogID"
}
convenience init(name: String, race: String, dogID: String) {
self.init()
self.name = name
self.race = race
self.dogID = dogID
}
}
class TableViewController: UITableViewController {
let items = try! Realm().objects(Dog.self).sorted(["race", "name"])
var sectionNames: [String] {
return Set(items.valueForKeyPath("race") as! [String]).sort()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
let realm = try! Realm()
if realm.isEmpty {
try! realm.write {
realm.add(Dog(name: "Bailey", race: "Golden Retrievers", dogID: "0"))
realm.add(Dog(name: "Bella", race: "German Shepherds", dogID: "1"))
realm.add(Dog(name: "Max", race: "Bulldogs", dogID: "2"))
realm.add(Dog(name: "Lucy", race: "Yorkshire Terriers", dogID: "3"))
realm.add(Dog(name: "Charlie", race: "Bulldogs", dogID: "4"))
realm.add(Dog(name: "Molly", race: "German Shepherds", dogID: "5"))
realm.add(Dog(name: "Buddy", race: "German Shepherds", dogID: "6"))
realm.add(Dog(name: "Daisy", race: "Siberian Huskies", dogID: "7"))
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionNames.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionNames[section]
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return items.filter("race == %@", sectionNames[section]).count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = items.filter("race == %@", sectionNames[indexPath.section])[indexPath.row].name
return cell
}
}
Which looks like this:
What to do when object on realm gets invalidated
Problem
As I understand, you want to access object's properties when the object is invalidated. Correct me if I'm wrong :)
First of all, let take a look at isInvalidated property
Indicates if the object can no longer be accessed because it is now invalid.
An object can no longer be accessed if the object has been deleted from the Realm that manages it, or if invalidate() is called on that Realm.
It means that an object can be invalidated only when it's managed by a Realm.
My solution
Detach object from the Realm which managers it. If object isn't managed by any Realm, of course it will never be invalidated and you can access properties as you want.
How to do
Whenever you fetch an object from Realm, detach it (create a cloned object).
Add below code to your project. It's used to detach every object in
Result
after fetching from Realm. I found hereprotocol DetachableObject: AnyObject {
func detached() -> Self
}
extension Object: DetachableObject {
func detached() -> Self {
let detached = type(of: self).init()
for property in objectSchema.properties {
guard let value = value(forKey: property.name) else { continue }
if property.isArray == true {
//Realm List property support
let detachable = value as? DetachableObject
detached.setValue(detachable?.detached(), forKey: property.name)
} else if property.type == .object {
//Realm Object property support
let detachable = value as? DetachableObject
detached.setValue(detachable?.detached(), forKey: property.name)
} else {
detached.setValue(value, forKey: property.name)
}
}
return detached
}
}
extension List: DetachableObject {
func detached() -> List<Element> {
let result = List<Element>()
forEach {
if let detachable = $0 as? DetachableObject {
let detached = detachable.detached() as! Element
result.append(detached)
} else {
result.append($0) //Primtives are pass by value; don't need to recreate
}
}
return result
}
func toArray() -> [Element] {
return Array(self.detached())
}
}
extension Results {
func toArray() -> [Element] {
let result = List<Element>()
forEach {
result.append($0)
}
return Array(result.detached())
}
}Instead of keep
events
asRLMResults<RLMMyEvent>
, keep it as[RLMMyEvent]
.After fetching result, detach objects and convert to an array
events = RLMMyEvent.objects(with: predicate).toArray()
Now you can access properties without being afraid of invalidated objects and crash.
Note that detached objects will not be updated if the original objects or their values inside Realm are changed.
Display NSProgressIndicator while filtering Realm
This is a perfect use case for Realm's collection notifications. That'd look something like:
var notificationToken: NotificationToken? = nil
var results: Results<MyClass>? = nil
func performFilterAction() {
filterProgressIndicator.isHidden = false
filterProgressIndicator.startAnimation(nil)
let realm = try! Realm()
results = realm.objects(MyClass.self).filter("location = %@", "US")
notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
self.filterProgressIndicator.isHidden = true
break
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
break
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
break
}
}
}
deinit {
notificationToken?.stop()
}
This lets you display a progress indicator while the initial filtering is performed, and also provides animated updates as changes are made to the data in the collection. If you don't care about the latter you could instead just call reloadData()
in the .update
case as well.
Related Topics
In Swift, Does Resetting the Property Inside Didset Trigger Another Didset
How to Animate Transition Between Views in Swiftui
Evaluate Bool Property of Optional Object in If Statement
How to Use .Svg Images in Swiftui
Round Top Corners of a Uiview in Swift
(Swift) Nstimer Stop When Scrolling
Swift Realm, Load the Pre-Populated Database the Right Way
Failed to Get Descriptors for Extensionbundleid
Add Custom Header to Collection View Swift
Weak VS Unowned in Swift. What Are the Internal Differences
What Is the "@Exported" Attribute in Swift
Po Swift String "Unresolved Identifier"
Delete All Characters After a Certain Character from a String in Swift
How to Repeat Animation (Using Uiviewpropertyanimator) Certain Number of Times
Swift - How to Create a View with a Shape Cropped in It
Swift 4 - Notification Center Addobserver Issue
Autofocus Textfield Programmatically in Swiftui
How to Add External .Vtt Subtitle File to Avplayerviewcontroller in Tvos