idlefingers
idlefingers

Reputation: 32037

How to properly use Realm objects in a UITableViewController?

I'm trying to use Realm in my UITableViewController and I'm running into issues whenever I try to find the object at a row index if I cast the object to its class (forgive me if I'm using the wrong terminology, I'm still pretty new to Swift, Realm ans iOS dev!)...

I have a Site class which looks like this, and the database has a few thousand entries:

class Site: RLMObject {
    var id: String = ""
    var name: String = ""
}

In my table view controller, when I try to fetch a Site based on its index in the result set to load into a cell, if I try to cast it to a Site object it's always nil! If I let it be set using AnyObject, then I can see that the correct site at that index has indeed been found.

I'm guessing the .name call on AnyObject is only working because AnyObject responds to .name, but it helps me to see that the index is correct and that the site does exist...

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
    let allSites = Site.allObjects()
    var any: AnyObject = allSites.objectAtIndex(UInt(indexPath.row))
    var site = allSites.objectAtIndex(UInt(indexPath.row)) as? Site
    println("With AnyObject: \(any.name)")
    println("With casting: \(site?.name)")

    return cell
}

The result of the print statements above look like this (for example on a site which is named 'Addeboda'):

With AnyObject: Addeboda
With casting: Optional("")

Am I doing something wrong? I've googled around a bit, and all the tutorials and answers I can find along similar lines suggest that results.objectAtIndex(index) as Class should be the right approach.

Upvotes: 4

Views: 4143

Answers (2)

yoshyosh
yoshyosh

Reputation: 14086

Here is some code from the tableview sample project:

class TableViewController: UITableViewController {

    var array = DemoObject.allObjects().sortedResultsUsingProperty("date", ascending: true)
    ...

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as Cell

        let object = array[UInt(indexPath.row)] as DemoObject
        cell.textLabel?.text = object.title
        cell.detailTextLabel?.text = object.date.description

        return cell
    }
}

You should be able to cast on the line you are storing that indexPath.row object

Upvotes: 2

Erik Rothoff
Erik Rothoff

Reputation: 5123

No cast needed

It seems that casting to Site is not needed. This works fine:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
    let allSites = Site.allObjects()
    let site: AnyObject! = allSites[UInt(indexPath.row)]

    cell.textLabel!.text = site.name

    println("Site is: \(site.id)")

    return cell
}

Seems to be a bug with either Swift or Realm. I'm guessing one of them gets confused when downcasting AnyObject! to something.

Initializing a new instance with correct type

However, if you really need to use the Site model class you can initialize a new RLMObject from the result:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
    let allSites = Site.allObjects()
    let site = Site(object: allSites[UInt(indexPath.row)])

    cell.textLabel!.text = site.name

    println("Site is: \(site.id)")

    return cell
}

First try

It is unfortunate to hear that you are having issues with Realm and Swift. I am by no means a Swift pro, but it looks like you are casting site to an optional, and the result of using the optional cast operator site?.name is also an optional. Hence getting Optional("").

Can you try to see if you have any better luck with the following?

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
    let allSites = Site.allObjects()

    if var site = allSites[UInt(indexPath.row)] as? Site {
        println("Site is: \(site.name)")
    } else {
        println("it not castable to Site. It is: \(toString(allSites[UInt(indexPath.row)].dynamicType))")
    }

    return cell
}

Also, you can use yourObject.dynamicType to get a reference to the objects real class type.

Best of luck

Upvotes: 3

Related Questions