Realm results object with incorrect results (iOS, Swift) - swift

I am having issues getting Realm's result object to be accessed correctly using UITableView's cellForRowAt.
Here's the setup:
UITableViewController is divided into sections based on the Objects's category (a string defined in the object).
UITableViewController has a segue to a UIViewController which takes form input. That view controller writes to Realm and then makes a call back via delegation to refresh the table view data.
When that screen dismisses and returns to the UITableViewController, as I try to add the row via category, I am getting empty objects. However, when I use a for loop within cellForRowAt, I can access the data.
Here is what I'm running in this section:
func loadItems() {
itemsList = try! Realm().objects(Items.self).filter("list_id = \(list_id)").sorted(byKeyPath: "item_category")
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemListCell", for: indexPath)
let categoryName = categories.categories[indexPath.section]
let currItem = itemsList[indexPath.row]
if currItem.item_category == categoryName {
cell.textLabel!.text = currItem.item_name
}
return cell
}
It seems to be evaluating category correctly and stepping into that block, but the object's item_name and item_category is null. Here is a screenshot of the debugger inside the if statement:
Debugger Image
Is there something I need to change with how I'm using the object, pulling data, etc, to get the data correct in the object?

Found my answer here: UITableView with Multiple Sections using Realm and Swift
This is the change I made to cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemListCell", for: indexPath)
let currItem = itemsList.filter("item_category = '\(categories[indexPath.section])'")[indexPath.row]
cell.textLabel!.text = currItem.item_name
return cell
}
The issue I was having was I was constantly pulling the first position results object and not the first position within a section. I needed to narrow down to my section and then pull the first row.

I think it may have to do with your filters predicate. Try changing
itemsList = try! Realm().objects(Items.self).filter("list_id = \(list_id)").sorted(byKeyPath: "item_category"
to this
itemsList = try! Realm().objects(Items.self).filter("list_id = '\(list_id)'").sorted(byKeyPath: "item_category"
Add single quotations around '(list_id)'.
Realm Filtering

Related

Reusable UITableView for Varying Data Input - Swift/Xcode

I have a TableView that I want to reuse for different categories of data (essentially as plugins.. the tableView being a skeleton and being filled with whatever I want it to be). The TableView is filled with different categories of data (and related actions) depending on essentially what ViewController the user came from. I understand how to make it display the various data (just send it whatever array I want it to display), but I can't figure out how I could control the actions for the data at the specific index selected in didSelectRowAtIndexPath.
How could I do this? How could I create an array that has both Strings and executable actions associated with each indices? For example:
arrayOneNames = ["Tigris", "Leo", "Barko"]
arrayOneActions = [displayTheTigers, displayTheCats, displayTheDogs]
If "Leo" is selected in the tableView, then "displayTheCats" is executed. Again, I want each array to be a separate Class that I can use as a plugin, so that I can fill the tableView with whichever Class of data I want it to display and execute, depending on which ViewController the user came from previously. Please answer in Swift.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=UITableViewCell() // cell you've created
cell.data = arrayOneNames[indexPath.row] // passing relevant data
cell.tag = indexPath.row // the tag you want to pass for particular data
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)! as UITableViewCell // take selected cell
if(cell.tag == 0) { // call relevant function accordingly.
self.tigrisTouch()
} else if (cell.tag == 1) {
self.leoTouch()
} else {
self.barkoTouch()
}
}
private func tigrisTouch() {
}
private func leoTouch() {
}
private func barkoTouch() {
}

Table view cell get initial value after scrolling

I have a table view and 10 different prototype cell. I used storyboard and created custom UITableCell class for each cell.
There is a checkbox in CheckBoxCell. I created these checkboxes in loop according to options count.
Problem is that after I checked a checkbox, checkbox value changes but when I scroll up or down the table view, checkbox value changes with inital value.
I investigated some questions in stackoverflow. I live this problem, because of after every scroll dequeReusebleCell works and re-create the cell in the queue. I tried to use these solutions, but I cannot succeeded.
I am new for Swift and I don't know how can I solve this problem.
May someone tell me how can I solve this problem and what is the correct approach?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let object = surveyDetailArray.first!.elements[indexPath.row]
switch object.type {
case CellConfig.checkbox.rawValue:
let cell = tableView.dequeueReusableCell(withIdentifier: "CheckboxCell", for: indexPath) as! CheckboxCell
let object = surveyDetailArray.first!.elements[indexPath.row]
for label in object.options {
cell.checkbox = BEMCheckBox()
cell.checkbox.onAnimationType = .bounce
cell.checkbox.offAnimationType = .bounce
cell.checkbox.boxType = .square
cell.checkbox.onFillColor = .red
cell.checkbox.offFillColor = .white
cell.checkbox.onCheckColor = .white
cell.checkbox.delegate = self
cell.checkbox.tag = label.id
cell.contentView.addSubview(cell.checkbox)
}
return cell
}
In your problem the cell is dequeuing and is getting back to the old state, that's will obviously happen because of dequeReusableCell,
now for the solution, you should use a model to store the states of different checkboxes and on cellforRowAt
add the code for checkbox persistance according to the model, when you enable a checkbox,
change the value of the variable in the model also and leave rest it on your cellForRowAt code. I'm adding a small example for your understanding, hope it helps.
CODE
Struct ButtonsStates {
var isButtonEnabled : Bool = false
}
// In your ViewController use the above model for saving buttonValues
var buttonStates : [ButtonStates]? // initialize as many as the rows
// in cellForRowAt
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ExampleCell", for: indexPath) as? ExampleCell else { return fatalError("") }
// here if the cell is dequeued still when cell will again be visible then this condition will be checked
cell.customButton.isSelected = buttonStates[indexPath.row].isButtonEnabled
return cell

Better performance events inside TableView cell

I am developing a Facebook like application, where if the post is too long, it gets cut down and at the end of it "See more" appears that has some click events. (I use FRHyperLabel for it)
Everything works fine, but I've got a question: If the formatting+event is decleared inside tableView: cellForRowAt indexPath: is it bad for the performance?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var message : message
var cell : MessageCell
let message = self.messages[indexPath.row]
// World Message
cell = tableView.dequeueReusableCell(withIdentifier: "essageCell", for: indexPath) as! MessageCell
cell.messageLabel.attributedText = message.limitMessageMax250char()
//Add link if needed
if message.count > appSettings.maxCharacters.message {
let handler = {
(hyperLabel: FRHyperLabel!, range: NSRange!) -> Void in
self.alert(title: "Test", message: "Alert: because clicked on link")
}
let text = cell.messageLabel.text!
let nsRange = text.calculateUnicodeScalar(start: text.unicodeScalars.count-8, len: 8) // See more is 8 characters
cell.messageLabel.setLinkFor(nsRange, withLinkHandler: handler)
}
return cell
}
Or should I do it in the Cell's file, when it's awakeFromNib + with delegate
IMO you don't want to do this in the for cellForRowAt call mostly because you want to make that call return quickly and also because you are only returning the formatted cell.
I would argue/recommend that you set your handler for the row when it is being initialized and that way the handler value is only being set once.
Remember that cellForRowAt is called multiple times as the user scrolls through and as cells come into view.
review the https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614861-tableview for more information as to how cells are reused.

Capturing multiple values from UITableView with Swift

I'm trying to use a uitableview as a picker to allow the user to make multiple selections. I've set the tableview up to allow this and my coreData has a one-to-many relationship to allow for multiple values. My problem is that I cannot figure out how to capture the selected rows as values in my coreData.
I populate the table by fetching data from my 'Players' entity and storing it in this variable:
var playerPickerData = [NSManagedObject]()
I am guessing that I need to do something with the selectedrowsatindexpaths function to then extract the values back out of playerPickerData to be able to then add them to my 'Match' entity (which is linked to my players entity with a one-to-many relationship).
The code I have to populate my table cells is:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = playersPicker.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.accessoryType = cell.isSelected ? .checkmark : .none
cell.selectionStyle = .none
let fName = playerPickerData[indexPath.row].value(forKey: "firstName") as! String
let lName = playerPickerData[indexPath.row].value(forKey: "lastName") as! String
let playersNameData = "\(fName) \(lName)"
cell.textLabel?.text = playersNameData
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
selectedIndexPaths.append(indexPath as NSIndexPath)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
I have a UIPickerView that is used to select one value ('Game Name') and I can capture what I need to from this without issue by using the following on my SavePressed function:
let newMatch = NSEntityDescription.insertNewObject(forEntityName: "Match", into: context)
let selectedGame = pickerData[gamePicker.selectedRow(inComponent: 0)]
newMatch.setValue(selectedGame, forKey: "game")
I just need to know how i can save multiple values forKey = "players". Any advice on how/where to do this in my code would be very much appreciated.
If I can provide any more information then please let me know.
I genuinely cannot find anything that answers these questions here or elsewhere online. I think this might relate but I cannot translate it to my needs: Saving multiple values from a UITableView
I just need to know how i can save multiple values forKey = "players".
Always read the documentation of methods you are using when questions arise. The documentation of NSManagedObject.setValue(:forKey:) states:
If key identifies a to-one relationship, relates the object specified by value to the receiver, unrelating the previously related object if there was one. Given a collection object and a key that identifies a to-many relationship, relates the objects contained in the collection to the receiver, unrelating previously related objects if there were any.
So as long as you can construct and maintain a Set (Core Data uses sets to store to-many relationships) of your selected NSManagedObjects, you can simply do something along the lines of:
selectedObjects = [NSManagedObject]() // Fill this Array
parent.setValue(Set(selectedObjects), forKey: "key")
and it will automatically reset the to-many relationship, adding any new items and subtracting any that are not contained in the new collection.
It looks like you are maintaining a selectedIndexPaths array. In that case, you could do:
selectedIndexPaths.map { playerPickerData[$0.row] }
to get the selected objects, assuming your table view has a single section. You'll then need to turn that into a Set as displayed above. You could also collect the selected index paths as a set in the first place, but then after you map it it will be an Array again anyway.
PS
Note that in your didSelectRow you add the selected index paths to selectedIndexPaths, but you never remove them from selectedIndexPaths in didDeselectRow.

How to show only filtered data in a table

How to show the data thats decks.status == true, and ignore those objects set to false?
data:
var decks: [DeckOfCards]
What I've got now:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell
if (thedeck.decks[indexPath.row].status == true) {
cell.label.text = "\(thedeck.decks[indexPath.row].card.name)"
}
}
You're going about this the wrong way. By the time you get to cellForRowAtIndexPath, you're already stated that a cell should be dequeued for this index path (and therefore at this index in your data array). The right place to be doing this filtering is in your data source.
For example, in addition to your decks array, you could make a computed property (filteredDecks) that gets its value by filtering the decks array.
var decks = [DeckOfCards]
var filteredDecks: [DeckOfCards] {
return decks.filter { $0.status }
}
You can then use this property as the data source for your table view.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredDecks.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.label.text = "\(filteredDecks[indexPath.row].card.name)"
return cell
}
Now since this solution computes the filteredDecks array on each property access, it may not be the best approach if decks is a large array, or if you're reloading the table view frequently. If this is the case, and it's possible to do so, you should prefer filtering the decks array ahead of time using the same method shown in the computed property above.
You could use the filter function on the decks
let filteredDecks = decks.filter({$0.status})
Filter your array as
self.decks = self.decks.filter {
(d: DeckOfCards) -> Bool in
return d.status == true
}
Now your array will have the filtered values. you dont need to check for status inside cellForRowAtIndexPath function.