iOS 13 reusable cells - have these changed?

Hi,


Is anyone else seeing issues with reusable cells in iOS 13 only?


I have an app that has several custom cells dequeued in cellForRowAt function. Each cell dequeued is selected based on the datasource and works correctly in previous versions of iOS. I am aware of how reuse works and reset/assign all values to each cell type as it is created.


There are no repeated values when scrollling the exact same data on same devices running iOS 11 or 12, but iOS13 is showing reused cells with data that isn't in the data source as though I have not reset it correctly.


Does anyone of any changes in iOS13 that would cause this behaviour? Any help would be appreciated.


---------


This is the code from my function. The POSNEGLR cell has a name label and 2 segmented controls on the cell that are set based on results in the data source.


The datasource entry retreived into testToShow defines which cell type to use (ie testToShow["Cell"] is the cell identifier that tells me which custom cell to use.


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


//get the Group key from the title of the section, and then find the appropriate array entry for this group

let testToShow = getTestDetailsForIndexPath(indexPath:indexPath)

if testToShow["Cell"] == "POSNEGLR" {

let cell = tableView.dequeueReusableCell(withIdentifier: "POSNEGLR", for: indexPath) as! TestPositiveNegativeCellLR

let testName = testToShow["Name"]

cell.testName.text = testName


//this has been. added to explicitly reset the values of all segments to -1 in case we don't find any (but code below also sets it)

cell.testPosNegLeft.selectedSegmentIndex = -1

cell.testPosNegRight.selectedSegmentIndex = -1

//Set values for the segmented controls based on any results already recorded for this test

//Check if we have an array entry that has a key with the testName in it, and look for results recorded

let resultLeft = filterTestResultsForSearchText(testName: testName!, searchText: "L -")

print("Result left is \(String(describing: resultLeft)) for testName \(String(describing: testName))")

if resultLeft != nil {

if (resultLeft?.contains("NEG"))! {

cell.testPosNegLeft.selectedSegmentIndex = 0

} else if (resultLeft?.contains("POS"))! {

cell.testPosNegLeft.selectedSegmentIndex = 1

} else {

cell.testPosNegLeft.selectedSegmentIndex = -1

}

} else {. //no result for this test - make the segment unselected

cell.testPosNegLeft.selectedSegmentIndex = -1

}

//Get the result for second segmented control

let resultRight = filterTestResultsForSearchText(testName: testName!, searchText: "R -")

print("Result right is \(String(describing: resultRight)) for testName \(String(describing: testName))")

if resultRight != nil {

if (resultRight?.contains("NEG"))! {

cell.testPosNegRight.selectedSegmentIndex = 0

} else if (resultRight?.contains("POS"))! {

cell.testPosNegRight.selectedSegmentIndex = 1

} else {

cell.testPosNegRight.selectedSegmentIndex = -1

}

} else {

cell.testPosNegRight.selectedSegmentIndex = -1

}

return cell


} else if testToShow["Cell"] == "POSNEG" {

let cell = tableView.dequeueReusableCell(withIdentifier: "POSNEG", for: indexPath) as! TestPositiveNegativeCell

let testName = testToShow["Name"]

cell.testName.text = testName

//GET TEST RESULTS FOR THIS TEST IF EXIST AND CHECK RESULT TO MATCH SEGMENT

let testResults = filterTestResultsForMatchingResults(testName:testName!)

if testResults != nil {

if (testResults?.contains("NEG"))! {

cell.testPosNeg.selectedSegmentIndex = 0

} else if (testResults?.contains("POS"))! {

cell.testPosNeg.selectedSegmentIndex = 1

} else {

cell.testPosNeg.selectedSegmentIndex = -1

}

} else {

cell.testPosNeg.selectedSegmentIndex = -1


}


return cell

} else if testToShow["Cell"] == "None" {


let cell = tableView.dequeueReusableCell(withIdentifier: "None", for: indexPath)

cell.textLabel?.text = testToShow["Name"]

return cell

} else {

let cell = tableView.dequeueReusableCell(withIdentifier: "None", for: indexPath)

cell.textLabel?.text = "No tests to show"

return cell


}


There are other cell types (with similar logic to first two examples shown above) in between the last two fallback cases that point to a cell with identifier "None" in the storyboard. None is just a Basic cell type (name only).


When I debug this and scroll the list of tests, there are cells/tests that have results showing with result = nil in the logs. These cells then go ahead. and set the segmentedControl to -1 as per the code, but the coresponding entries show incorrectly in the app with selected segments.


The exact same data does NOT do this in iOS11 and iOS12 no matter how much I scroll.


Does anyone have any ideas on why this would be occuring even though I am resetting the segments to unselected each time after they are dequeued? Any help/suggestions would be much appreciated.

Replies

Could you post the cellForRowAt function, so that we can try to reproduce ?

@Claude31 I've posted some code and further explanation above.

OK thanks. I've not seen anything abnormal in what you posted.


So, could you also post getTestDetailsForIndexPath func as well as filterTestResultsForMatchingResults ?

Maybe there is an async issue where you do not yet get the correct data from the datasource when you set the cell.


Could you print a log after

        let testToShow = getTestDetailsForIndexPath(indexPath:indexPath)
        print("indexPath", indexPath, "testToShow", testToShow)



            let testResults = filterTestResultsForMatchingResults(testName:testName!)
            print(indexPath, , "testName", testName, "testResults", testResults)


Thanks to tell for which type of cell you get problem and post the logs.

@Claude31 - Apologies for the lenghty reply. Thanks for taking the time to help solve the issue.


I have added the logging you suggested. The logging confirms that the data source correctly gets the testToShow and the corresponding testResults. What does NOT match is what then shows on the tableView output screen.


1. Here are the tests with results in string format. If a user taps into this field, the tableView with list of tests shows with the corresponding segments/information selected based on the test results retreived in the cellForRowAtIndex function posted above.


Painful Arc + L - POS 45-120, R - NEG; Morton''s Test (Neuroma) + L - NEG, R - NEG; FABERE's Test + L - POS, R - POS; Bakody''s Sign + NEG


So this would show four tests with results in the tableView - Painful Arc is a L/R test with degrees if positive, Morton's & Fabere's are POS/NEG tests for L/R side (so two segments, one for a Left result and one for Right result) and Bakody's SIgn is just a simple POS/NEG test (just one segment)


2. Here is the direct logging of tests as I scroll through the list outputting the name, result retrieved and index for each cell as it is processed in cellForRowAtIndex function. The problem is that actual tableView I can see on a device/simulator shows the data differently than the logging. It selects segmentedControl segments when the results are nil and skips some of the actual ons with . I'm pretty sure the code setting the segment values works correctly as there is no output problem in earlier versions of iOS using the same code.


Log output as I scroll the whole tableView

Result left is nil for testName Optional("Thumb abduction perpendicular to plane of palm - C8/T1") at indexPath [0, 0]

Result right is nil for testName Optional("Thumb abduction perpendicular to plane of palm - C8/T1") at indexPath [0, 0]

Result left is nil for testName Optional("Flexion at distal interphalangeal joints digits 2, 3 - C7/C8") at indexPath [0, 1]

Result right is nil for testName Optional("Flexion at distal interphalangeal joints digits 2, 3 - C7/C8") at indexPath [0, 1]

Result left is nil for testName Optional("Flexion at distal interphalangeal joints digits 4, 5 - C7/C8") at indexPath [0, 2]

Result right is nil for testName Optional("Flexion at distal interphalangeal joints digits 4, 5 - C7/C8") at indexPath [0, 2]

Result left is nil for testName Optional("Wrist flexion and hand abduction - C6/C7") at indexPath [0, 3]

Result right is nil for testName Optional("Wrist flexion and hand abduction - C6/C7") at indexPath [0, 3]

Result left is nil for testName Optional("Babinski") at indexPath [1, 0]

Result right is nil for testName Optional("Babinski") at indexPath [1, 0]

Result left is nil for testName Optional("Maignes") at indexPath [2, 0]

Result right is nil for testName Optional("Maignes") at indexPath [2, 0]

Result is nil for testName Optional("Compression") at indexPath [2, 1]

Result is nil for testName Optional("Soto Hall Test") at indexPath [2, 2]

Result is Optional(["NEG"]) for testName Optional("Bakody\'s Sign") at indexPath [2, 3]

Result is nil for testName Optional("Soto Hall Test") at indexPath [2, 2]

Result is Optional(["NEG"]) for testName Optional("Bakody\'s Sign") at indexPath [2, 3]

Result left is nil for testName Optional("Valsalva\'s Manoeuvre") at indexPath [2, 4]

Result right is nil for testName Optional("Valsalva\'s Manoeuvre") at indexPath [2, 4]

Result left is nil for testName Optional("Tinel\'s Neck Test") at indexPath [2, 5]

Result right is nil for testName Optional("Tinel\'s Neck Test") at indexPath [2, 5]

Result left is nil for testName Optional("VAT test") at indexPath [2, 6]

Result right is nil for testName Optional("VAT test") at indexPath [2, 6]

Result left is nil for testName Optional("Trendelenburg Test") at indexPath [3, 0]

Result right is nil for testName Optional("Trendelenburg Test") at indexPath [3, 0]

Result left is nil for testName Optional("Varus Stress Test") at indexPath [3, 1]

Result right is nil for testName Optional("Varus Stress Test") at indexPath [3, 1]

Result left is nil for testName Optional("Varus Stress Test") at indexPath [3, 1]

Result right is nil for testName Optional("Varus Stress Test") at indexPath [3, 1]

Result left is nil for testName Optional("Valgus Stress Test") at indexPath [3, 2]

Result right is nil for testName Optional("Valgus Stress Test") at indexPath [3, 2]

Result left is Optional("L - POS") for testName Optional("FABERE\'s Test") at indexPath [3, 3]

Result right is Optional("R - POS") for testName Optional("FABERE\'s Test") at indexPath [3, 3]

Result left is nil for testName Optional("Posterior Sag Sign") at indexPath [3, 4]

Result right is nil for testName Optional("Posterior Sag Sign") at indexPath [3, 4]

Result left is nil for testName Optional("McMurray\'s Test ") at indexPath [3, 5]

Result right is nil for testName Optional("McMurray\'s Test ") at indexPath [3, 5]

Result left is nil for testName Optional("Fouchet\'s Test ") at indexPath [3, 6]

Result right is nil for testName Optional("Fouchet\'s Test ") at indexPath [3, 6]

Result left is nil for testName Optional("Medial Lateral Shift") at indexPath [3, 7]

Result right is nil for testName Optional("Medial Lateral Shift") at indexPath [3, 7]

Result left is nil for testName Optional("AP-Shift (Knee)") at indexPath [3, 8]

Result right is nil for testName Optional("AP-Shift (Knee)") at indexPath [3, 8]

Result left is nil for testName Optional("Lateral Shift (Ankle)") at indexPath [3, 9]

Result right is nil for testName Optional("Lateral Shift (Ankle)") at indexPath [3, 9]

Result left is nil for testName Optional("Ankle Drawer Test") at indexPath [3, 10]

Result right is nil for testName Optional("Ankle Drawer Test") at indexPath [3, 10]

Result left is Optional("L - NEG") for testName Optional("Morton\'s Test (Neuroma)") at indexPath [3, 11]

Result right is Optional("R - NEG") for testName Optional("Morton\'s Test (Neuroma)") at indexPath [3, 11]

Result left is nil for testName Optional("Tinel\'s Foot Test") at indexPath [3, 12]

Result right is nil for testName Optional("Tinel\'s Foot Test") at indexPath [3, 12]

Result is nil for testName Optional("Kemps Test") at indexPath [4, 0]

Result left is nil for testName Optional("Straight Leg Raise") at indexPath [4, 1]

Result right is nil for testName Optional("Straight Leg Raise") at indexPath [4, 1]

Result left is nil for testName Optional("Slump Test") at indexPath [4, 2]

Result right is nil for testName Optional("Slump Test") at indexPath [4, 2]

Result is nil for testName Optional("Leg Length Test") at indexPath [4, 3]

Result left is nil for testName Optional("Ely’s Heel to Buttock Test") at indexPath [4, 4]

Result right is nil for testName Optional("Ely’s Heel to Buttock Test") at indexPath [4, 4]

Result left is nil for testName Optional("Ely’s Heel to Buttock Test") at indexPath [4, 4]

Result right is nil for testName Optional("Ely’s Heel to Buttock Test") at indexPath [4, 4]

Result left is nil for testName Optional("Nachlas Test") at indexPath [4, 5]

Result right is nil for testName Optional("Nachlas Test") at indexPath [4, 5]

Result left is nil for testName Optional("Adam\'s Sign") at indexPath [4, 6]

Result right is nil for testName Optional("Adam\'s Sign") at indexPath [4, 6]

Result left is Optional("L - POS 45-120") for testName Optional("Painful Arc") at indexPath [5, 0]

Result right is Optional("R - NEG") for testName Optional("Painful Arc") at indexPath [5, 0]

Result left is nil for testName Optional("Adson\'s Test") at indexPath [5, 1]

Result right is nil for testName Optional("Adson\'s Test") at indexPath [5, 1]

Result left is nil for testName Optional("Wright\'s Test") at indexPath [5, 2]

Result right is nil for testName Optional("Wright\'s Test") at indexPath [5, 2]

Result left is nil for testName Optional("Eden\'s Test") at indexPath [5, 3]

Result right is nil for testName Optional("Eden\'s Test") at indexPath [5, 3]

Result left is nil for testName Optional("Roo\'s Test") at indexPath [5, 4]

Result right is nil for testName Optional("Roo\'s Test") at indexPath [5, 4]

Result left is nil for testName Optional("Scapular Retraction Test") at indexPath [5, 5]

Result right is nil for testName Optional("Scapular Retraction Test") at indexPath [5, 5]

Result left is nil for testName Optional("Scapular Assistance Test") at indexPath [5, 6]

Result right is nil for testName Optional("Scapular Assistance Test") at indexPath [5, 6]

Result left is nil for testName Optional("Hawkins-Kennedy Test") at indexPath [5, 7]

Result right is nil for testName Optional("Hawkins-Kennedy Test") at indexPath [5, 7]

Result left is nil for testName Optional("Supraspinatus Press Test") at indexPath [5, 8]

Result right is nil for testName Optional("Supraspinatus Press Test") at indexPath [5, 8]

Result left is nil for testName Optional("Impingement Sign Test ") at indexPath [5, 9]

Result right is nil for testName Optional("Impingement Sign Test ") at indexPath [5, 9]

Result left is nil for testName Optional("Apley\'s Scratch Test ") at indexPath [5, 10]

Result right is nil for testName Optional("Apley\'s Scratch Test ") at indexPath [5, 10]

Result left is nil for testName Optional("Lachman\'s Test") at indexPath [5, 11]

Result right is nil for testName Optional("Lachman\'s Test") at indexPath [5, 11]

Result left is nil for testName Optional("Middle Finger Extension") at indexPath [5, 12]

Result right is nil for testName Optional("Middle Finger Extension") at indexPath [5, 12]

Result left is nil for testName Optional("Mill\'s Test") at indexPath [5, 13]

Result right is nil for testName Optional("Mill\'s Test") at indexPath [5, 13]

Result left is nil for testName Optional("Reverse Mill\'s Test") at indexPath [5, 14]

Result right is nil for testName Optional("Reverse Mill\'s Test") at indexPath [5, 14]

Result left is nil for testName Optional("Phalen\'s Test") at indexPath [5, 15]

Result right is nil for testName Optional("Phalen\'s Test") at indexPath [5, 15]

Result left is nil for testName Optional("Reverse Phalen\'s Test") at indexPath [5, 16]

Result right is nil for testName Optional("Reverse Phalen\'s Test") at indexPath [5, 16]

Result left is nil for testName Optional("Tourniquet Test") at indexPath [5, 17]

Result right is nil for testName Optional("Tourniquet Test") at indexPath [5, 17]

Result left is nil for testName Optional("Watson\'s Test") at indexPath [5, 18]

Result right is nil for testName Optional("Watson\'s Test") at indexPath [5, 18]

Result left is nil for testName Optional("Finklestein\'s Test") at indexPath [5, 19]

Result right is nil for testName Optional("Finklestein\'s Test") at indexPath [5, 19]

Result left is nil for testName Optional("Tinel\'s Wrist Test") at indexPath [5, 20]

Result right is nil for testName Optional("Tinel\'s Wrist Test") at indexPath [5, 20]

Result left is nil for testName Optional("Tinel\'s Elbow Test") at indexPath [5, 21]

Result right is nil for testName Optional("Tinel\'s Elbow Test") at indexPath [5, 21]

Result left is nil for testName Optional("Middle Finger Extension") at indexPath [5, 12]

Result right is nil for testName Optional("Middle Finger Extension") at indexPath [5, 12]

(Note that I think the repetition of Bakody's Sign at index [2,3] is just due to scrolling)


3. The resulting tableView output shows the test reulsts differently than the logs from cellForRow function. For example, other tests that have result nil show with incorrectly selected segments (for example Slump Test and Leg Length tests as shown below in the screen shots - they have nil test results but are showing with selected segments). Some of the actual test results also do not show correctly



Any suggestions on where else to look to find where the cell selections are being changed? If not, I'll raise it as a bug with Apple and. see if they can shed more light on it.