On selecting a row in an NSFetchedResultsController
(FRC), my app allows the details to be changed by segueing to a detail view controller. After saving changes, the edited row is returned and inserted in the FRC.
The .update
code is triggered in the following switch
:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
configureCell(cell: tableView.cellForRow(at: indexPath! as IndexPath)!, indexPath: indexPath! as NSIndexPath)
tableView.reloadRows(at: [indexPath!], with: .automatic)
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
case .move:
tableView.moveRow(at: indexPath! as IndexPath, to: newIndexPath! as IndexPath)
}
}
The configureCell()
method is:
func configureCell(cell: UITableViewCell, indexPath: NSIndexPath) {
var handicap: Float
let cellIdentifier = "PlayerTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath as IndexPath) as? PlayerTableViewCell else {
fatalError("The dequeued cell is not an instance of PlayerTableViewCell")
}
// Populate cell from the NSManagedObject instance
guard let player = fetchedResultsController.object(at: indexPath as IndexPath) as? Golfer else {
fatalError("Unexpected Object in FetchedResultsController")
}
let firstName = player.value(forKey: "firstName") as! String?
let lastName = player.value(forKey: "lastName") as! String?
let fullName = firstName! + " " + lastName!
handicap = player.value(forKey: "exactHandicap") as! Float
cell.playerFullName?.text = fullName
cell.playerPhoto.image = UIImage(data: player.value(forKey: "photo") as! Data)
cell.numExactHandicap.text = "\(Int(handicap))"
cell.numStrokesReceived.text = "\(handicap.cleanValue)"
print("Object for configuration: \(player)")
}
And the cell class definition is:
class PlayerTableViewCell: UITableViewCell {
//MARK: Properties
@IBOutlet weak var playerFullName: UILabel!
@IBOutlet weak var playerPhoto: UIImageView!
@IBOutlet weak var exactHandicapLabel: UILabel!
@IBOutlet weak var strokesReceivedLabel: UILabel!
@IBOutlet weak var numExactHandicap: UILabel!
@IBOutlet weak var numStrokesReceived: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
-- EDIT #1 --
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier
let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerTableViewCell", for: indexPath)
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell
}
The behaviour I get is unexpected in a number of ways.
The on-screen data does not update following the processing of the
.update
code, retaining the pre-edited values in the FRC.Only when I select the same row in the table does the row refresh. I.e. it shows the old values but then changes to the new at the instant I select the row again, prior to launching the edited row again.
When I select any row, except row 0, for the first time it sets all values in the cell to the default values shown in the storyboard. However, row 0 never changes prior to the segue under any circumstances.
Any insights very welcome!
-- EDIT #3 [Apr' 20, 2017] --
I've been chipping away at this issue for some time. The core issue remains - The on-screen data does not refresh following the processing of the .update
code, retaining the pre-edited values in the FRC.
The key code segments now look like this:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
golferView.reloadRows(at: [indexPath!], with: .automatic)
case .insert:
golferView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
golferView.deleteRows(at: [indexPath!], with: .automatic)
case .move:
golferView.moveRow(at: indexPath! as IndexPath, to: newIndexPath! as IndexPath)
}
}
func configureCell(cell: UITableViewCell, indexPath: NSIndexPath) {
var handicap: Float
var player: Golfer
let cellIdentifier = "PlayerTableViewCell"
guard let cell = golferView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath as IndexPath) as? PlayerTableViewCell else {
fatalError("The dequeued cell is not an instance of PlayerTableViewCell")
}
// Populate cell from the NSManagedObject instance
player = fetchedResultsController.object(at: indexPath as IndexPath)
let firstName = player.value(forKey: "firstName") as! String?
let lastName = player.value(forKey: "lastName") as! String?
let fullName = firstName! + " " + lastName!
handicap = player.value(forKey: "exactHandicap") as! Float
cell.playerFullName?.text = fullName
cell.playerPhoto.image = UIImage(data: player.value(forKey: "photo") as! Data)
cell.numExactHandicap.text = "\(Int(handicap))"
cell.numStrokesReceived.text = "\(handicap.cleanValue)"
print("Object for configuration: \(player). And cell values are = \(String(describing: cell.playerFullName?.text)), \(String(describing: cell.numExactHandicap.text)), \(String(describing: cell.numStrokesReceived.text))")
}
I have verified that the controller()
method is firing correctly and that the .update
switch is firing reloadRows
. This in turn fires configureCell()
where the contents of the returned row from the FRC are correct and the values inserted into the cell match that returned from the FRC. It just won't refresh the UI.