code involving indexPath & NSIndexPath works - but why is it redundant? - swift

I'm getting back to using CloudKit and in the project I'm revisiting, I have a query fetch performed and I am left with an array of CKRecords. I'm setting this array to be displayed via TableController. Anyways, I have this one line of code (which works)... but I am just unsure why I am setting the indexPath as NSIndexPath.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dining") as! table1cell
let restaurant: CKRecord = categories[(indexPath as NSIndexPath).row]
cell.Name.text = restaurant.value(forKey: "Name") as? String
return cell
With my other non-CKRecord TableController projects, I know I don't have to set the indexPath to itself, in essence. What am I missing here?

The use of the cast to NSIndexPath is pointless. Simply change the line to:
let restaurant: CKRecord = categories[indexPath.row]

If indexPath is not stored in an explicitly typed var, you probably casted it to avoid a compiler message. If the compiler does not know indexPath is of type NSIndexPath, accessing the .row property would likely cause an error.
Where are you declaring/storing indexPath? What happens when you remove the as NSIndexPath cast?
Edit: re-reading your question, I believe the answer is:
"You are not storing indexPath as itself, you are casting whatever is stored in indexPath to be of type NSIndexPath"

Related

Realm results object with incorrect results (iOS, 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

Get dynamic type of cell during runtime not working

I got this example of using custom cells in my Swift project :
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
as! HeadlineTableViewCell
But in my project, I actually have an array of Custom cells called mycells.
So I thought I could simply change it to :
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
as! type(of:allCells[indexPath.row])
But no. The compiler complains about this :
Cannot create a single-element tuple with an element label
Maybe this is just dumb but I cant get why it wont work. Can someone help me and clarify whats going on?
I use something similar in my apps, this is how I resolved this problem
extension UITableViewCell {
#objc func configure(_ data: AnyObject) {}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = info.sectionInfo[indexPath.section].data[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: data.identifier.rawValue, for: indexPath)
cell.configure(data as AnyObject)
return cell
}
class DefaultCell: UITableViewCell {
override func configure(_ data: AnyObject) {
guard let data = data as? MyDesiredClass
else {
return
}
// do smth
}
}
in this case you don't need to pass cell type directly, because any cell contains configure func where you can fill all fields
You issue is some kind of syntax mistake that is not visible in the code you provided.
But Use generic instead:
Define something like this:
protocol Reusable: UIView {
static var identifier: String { get }
}
extend it for UITableViewCell:
extension Reusable where Self: UITableViewCell {
static var identifier: String {
return String(describing: self)
}
}
Conform cell to it:
extension HeadlineTableViewCell: Reusable {}
add this extension to UITableView:
extension UITableView {
func dequeueReusableCell<T: UITableViewCell & Reusable>(type cellType: T.Type, for indexPath: IndexPath) -> T {
return dequeueReusableCell(withIdentifier: cellType.identifier, for: indexPath) as! T
}
}
and use it like this:
myTableView.dequeueReusableCell(type: HeadlineTableViewCell.self, for: indexPath)
This will dequeue and cast at the same time
I'm assuming that allCells is an array that contains a list of table view cells, but the cells are of different class types.
This line here that you're using can not be used the way that you're trying to use it.
type(of:allCells[indexPath.row])
That's why you're getting the error. This function returns the objects Metatype and you can't use that result in the way you're trying above. You should probably look into how optionals work too and how to unwrap them because the way you're trying to do this isn't going to work. This line you had here below would work fine, but unwrapping using type(of:) syntax is not going to work:
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath) as! HeadlineTableViewCell
Honestly, the entire architecture of using arrays to store tableViewCells is wrong, and I'm not even sure what you're trying to accomplish by doing that, but I can almost 100% say it's a very bad idea. Instead the array should store the data that the tableViewCell is going to display.
Honestly if I were you I would look up a tutorial on table views because I feel like there's a lot of misunderstanding here of how table views work which has led to the code that you wrote and the problems that you're having.
Check out this tutorial here. It should help you get a better understanding of how things work.

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.

Dictionary value when deleting cell - unexpectedly found nil while unwrapping an Optional value

Im working in Swift/Xcode and I am new to app development, i have a tableView with 2 labels. The issue is when deleting a cell of my tableView. I need to get the data of one of the cell labels but the app crashes with the fatal error - 'unexpectedly found nil while unwrapping an Optional value'
I have set up break points so that i can check if the dictionary has anything stored in it, which it does, there is an element stored in it.
Appreciate any help
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! myCustomCell
self.cost = self.total
// This line gives me the error** self.total = self.cost - self.dict[cell.cellPrice.text!]!
self.totalLabel.text = "Total: \(self.total)"
self.dict.removeValueForKey(cell.cellTitle!.text!)
tableView.reloadData()
saveState()
}
This my code to set up the cell:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: myCustomCell = tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! myCustomCell
var myDataSource : [String] = Array(self.dict.keys)
let key = myDataSource[indexPath.row] as String
cell.cellTitle?.text = myDataSource[indexPath.row]
cell.cellPrice?.text = "£(self.dict[key]!)"
self.saveState()
return cell
}
I'd have to see more of your code to know how you are setting properties of myCustomCell, but it looks like either cellPrice.text is nil or self.dict[cell.cellPrice.text] is nil. You should just unwrap those optionals safely rather than trying to force them. That'd look something like this:
if let price = cell.cellPrice.text, let priceValue = self.dict[price] {
self.totalLabel.text = "Total: \(priceValue)"
...
}
In general you should stay away from force unwrapping (using !) unless you're just writing really quickly for testing or something. It makes debugging harder.
You are trying to force unwrap. I guess it's cellPrice label or it text but one of those things. Figure out which one is nil.

Swift 2 to 3 Migration Core Data NSManagedObject Class

I have the following code for one of my tables.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InventoryTableCell", for: indexPath as IndexPath) as! InventoryTableViewCell
let inventory : Inventory = fetchedResultsController.object(at: indexPath as IndexPath) as! Inventory
cell.inventoryItem = inventory
cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.
return cell
}
I'm getting a runtime error on cell.inventoryItem = inventory
It says Thread 1: EXC_BAD_ACCESS (error: CoreData: error: Failed to call designated initializer on NSManagedObject class )
It didn't do this in swift 2 so not sure what the error is. I'll take a screenshot of my model and created class.
I have no idea how to fix the error since this worked before. What changed?
Looks like I solved my issue, when you define the items you have to give a context to it now or it won't work.
On code that I did not show here for my InventoryTableViewCell I had defined inventoryItem incorrectly.
Here is the correct way to do it.
//Set inventory item as an InventoryType within the correct context
var inventoryItem = Inventory(context: (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext)