How to properly use Realm objects in a UITableViewController? - ios

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.

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

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

Related

Swift: Strange downcasting failure

I have a rather strange case of downcasting failure that I cannot understand.
I have almost identical code in two UITableViewControllers. ProjectTableViewController displays list of Project, and its datasource is [Project]. NewsfeedsTableViewController displays list of Newsfeed, but Newsfeed can contain different types of source data, including Project.
Depending on the type of the source data, each cell of NewsfeedsTableViewController is downcasted to appropriate subclass of UITableViewCell.
ProjectTableViewCell a subclass of UITableViewCell, and is used in both ProjectTableViewController and NewsfeedsTableViewController.
Now the interesting thing is, the code works without issue in ProjectTableViewController but crashes in NewsfeedsTableViewController, giving following error message:
Could not cast value of type 'UITableViewCell' (0x102e68128) to 'sample_app.ProjectTableViewCell' (0x1010ce6d0).
I have following codes in each class:
ProjectTableViewController.swift
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let project = projects[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("ProjectTableViewCell") as! ProjectTableViewCell
cell.projectTitle.text = project.title
cell.projectKeywords.text = project.keywords
return cell
}
NewsfeedsTableViewController.swift
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let newsfeed = self.newsfeeds[indexPath.row]
switch newsfeed.newsfeedableType {
case "Project":
let cell = tableView.dequeueReusableCellWithIdentifier("NewsfeedTableViewCell") as! ProjectTableViewCell
let source = newsfeed.newsfeedable as! Project
cell.projectTitle.text = source.title
cell.projectKeywords.text = source.keywords
return cell
default:
let cell = tableView.dequeueReusableCellWithIdentifier("NewsfeedTableViewCell")!
cell.textLabel!.text = newsfeed.newsfeedableType + String(newsfeed.id)
return cell
}
}
I would love to understand what's causing this issue.
The issue was solved by adding another prototype cell of ProjectTableViewCell to the NewsfeedsTableViewController, and using it to display Newsfeed with Project as source.
Which means I will have to add prototype cells for all the source data types, and then configure them on the storyboard. I'm not looking forward to doing that.
I thought I could duck-type through this all, but I guess Swift does not work like that.

xcode - Thread 1: EXC_BAD_ACCESS

I get this error when the ViewController loads.
I don't know, what I can do
The loadDataFromDb function is this
func loadDataFromDb() {
let fetchRequest = NSFetchRequest(entityName: "Chats")
daten = self.context!.executeFetchRequest(fetchRequest, error: nil) as! [Model]
tableView.reloadData()
}
I googled that error but nothing helped me
dequeueReusableCellWithIdentifier returns an optional that might be nil.
Your force casting to UITableViewCell, by using as! makes it so that the nil gets casted to UITableViewCell and you end up calling a method on nil, which leads to the exception.
Try unwrapping the cell, by using if let before using it or better yet use dequeueReusableCellWithIdentifier(_ identifier: String,
forIndexPath indexPath: NSIndexPath) -> AnyObject , which doesn't return an optional.
Also as a whole, try staying away from ! and refrain from using it, unless you absolutely have to.
Edit:
Here's one way to do it
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? UITableViewCell {
cell.textLabel?.text = "Text"
return cell
}
return UITableViewCell()
}

(Swift) tableView application iOS fails because of "Bound value in a conditional binding must be of Optional type"

I am trying to create a database-based application for iOS and I am, more or less, forced to use Xcode 6.2 Beta 3, because I started the project with 6.2 and run iOS 8.2 on my testdevice. I have by the way figured out that the beta forces me to use the standard keypad, instead of decimal pad, otherwise the app stops working.
I canĀ“t figure out what is wrong. And when I try to comment the function out something is still wrong in this file. Are there any missing connections between the VC-swift-file, tableVC-swift-file and datamodel-file?
I changed the name of the entity list once, and then changed back, because of another error I had before. But after I did it seems like the debug area doesnt catch the data I try to save to the database either...
The error says "Bound value in a conditional binding must be of Optional type"
Here is the code that is marked red on the initial line:
if let ip = indexPath {
var data: NSManagedObject = tankningslista[ip.row] as NSManagedObject
cell.textLabel?.text = data.valueForKey("datum") as String
}
The whole function looks like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let CellID: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
if let ip = indexPath {
var data: NSManagedObject = tankningslista[ip.row] as NSManagedObject
cell.textLabel?.text = data.valueForKey("datum") as String
}
return cell
}
I have followed this tutorial: https://www.youtube.com/watch?v=4ymz6i07DRM
try this way without using condition:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let CellID: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
var data: NSManagedObject = tankningslista[indexPath.row] as NSManagedObject
cell.textLabel.text = data.valueForKey("datum") as? String
return cell
}

Switching between custom cells in Swift

I'm trying to switch between two custom cell-classes in swift, but I can't seem to figure out how to return the cell.
My code looks like this, and the error is in the last line:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
if istrue{
var cell: CustomTableCell = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell
let data = myList[indexPath.row] as Model
cell.customLabel.text = data.username
cell.dateLabel.text = printDate(data.date)
return cell
}else{
var cell: CustomTableCell2 = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell2
let data = myList[indexPath.row] as Model
cell.titleLabel.text = data.username
cell.dateLabel2.text = printDate(data.date)
return cell
}
}return nil
I've also tried to "return cell" in the last line and to delete the other two lines of "return cell" in the if- and else-statements but that didn't work, it just gives me the error saying "cell" is an unresolved identifier.
I've never done this before so I'm not sure if this is the right way of tackling the problem either.
Any suggestions on how to proceed would be appreciated.
Define a variable of UITableViewCell type and initialize it in both the if and the else branches, then use it as the return value:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var retCell: UITableViewCell
if istrue{
var cell: CustomTableCell = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell
let data = myList[indexPath.row] as Model
cell.customLabel.text = data.username
cell.dateLabel.text = printDate(data.date)
retCell = cell
}else{
var cell: CustomTableCell2 = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell2
let data = myList[indexPath.row] as Model
cell.titleLabel.text = data.username
cell.dateLabel2.text = printDate(data.date)
retCell = cell
}
return retCell
}
Note that you cannot return nil because the return type of this method is a non-optional UITableViewCell, so it must always be an instance of (a class derived from) UITableViewCell.
Alternatively, you can just return the cell as you do on each of the if and else branches, but remove the ending return out of the if/else scope - it's not needed. Moreover, in your code it is also misplaced because out of the method scope.
Personal note: in functions I usually avoid return statements in the middle of the body, preferring a single exit path at the end - that's just a personal preference, so feel free to choose the one that you like more.

EXC_BAD_INSTRUCTION breakpoint when starting app xCode

I have a tableview in my app and when I start my app it crashes on the following function.
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell
}
It crashes on the line of var cell
It gives the following error:
I can't figure out what's wrong with my code.
The whole function:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell
let data: NSManagedObject = mylist[ip.row] as NSManagedObject
cell.textLabel.text = data.valueForKeyPath("voornaam") as String
cell.detailTextLabel.text = data.valueForKeyPath("achternaam") as String
return cell
}
EDIT:
What I got now:(Still gives the same error)
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell? = tableView?.dequeueReusableCellWithIdentifier(cellId) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
}
let data: NSManagedObject = mylist[indexPath.row] as NSManagedObject
cell!.textLabel.text = data.valueForKey("voornaam") as String
cell!.detailTextLabel.text = data.valueForKey("achternaam") as String
//cell!.textLabel.text = "Hoi"
return cell
}
This is happening because the as operator is defined to cast an object to a given type and crash if the conversion fails. In this case, the call to dequeue returns nil the first time you call it. You need to use the as? operator, which will attempt to cast the given object to a type, and return an optional that has a value only if the conversion succeeded:
var cell: UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellId) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
}
...
Because cell is now an optional value, use cell! when you want to call methods on it to force-unwrap the UITableViewCell inside it.
Additionally, your code had a second problem: it never created a fresh cell. dequeue will return nil the first time it's called on your table view. You need to instantiate a new UITableViewCell as in my code sample and then return it from the cellFor... method. The table view will then save the cell and return it on future calls to dequeue.
First off, why are you doing an optional binding on line if let ip = indexPath? This argument is not optional and you don't need to do optional binding or unwrap it. But this shouldn't cause your code to crash.
Remove your let data line and assign literal strings to your cells and see if it still crashes.
May I suggest that you check to see if you set the tableview's delegates? I made that mistake once in the flurry of setting everything else up.
Perhaps it is too late but I like to share my experience. I had similar error as I copied the entire code from another project. So I think the variables and functions won't be recognised so I had to drag them (cntr+drag) then it is solved.
Sorry if I couldn't explain better. I am new this.

Resources