Skip to content

Calling filter inside method for appending items to data snapshot causes snapshot to append items multiple times #82778

Open
@asaadjaber

Description

@asaadjaber

Description

I've encountered an odd situation where I am filtering some items from a data structure inside a snapshot.append() items method call, and this appears to be causing the the append method to get called multiple times. Yet when I separate the filtering logic into a separate line, this issue appears to not be visible.

Reproduction

   enum Section: String, CaseIterable {
        case lifeEvents = "Life Events"
        case physicalSensations = "Physical Sensations"
        case mindThoughts = "Mind/Thoughts"
        case bodyLocation = "Body Location"
    }

    func applySnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, EmotionExplorerViewController.FilterItem>()
        snapshot.appendSections(Section.allCases)
                
        for section in Section.allCases {
            switch section {
                case .lifeEvents:
                    snapshot.appendItems(filterItems.filter({$0.category == section}), toSection: section)
                case .physicalSensations:
                    snapshot.appendItems(filterItems.filter({$0.category == section}), toSection: section)
                case .mindThoughts:
                    snapshot.appendItems(filterItems.filter({$0.category == section}), toSection: section)
                case .bodyLocation:
                    snapshot.appendItems(filterItems.filter({$0.category == section}), toSection: section)
            }
        }
        
        emotionFiltersDataSource.apply(snapshot, animatingDifferences: true)
    }

The following modification appears to not cause any problems:

func applySnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, EmotionExplorerViewController.FilterItem>()
        snapshot.appendSections(Section.allCases)
                
        for section in Section.allCases {
            print("section cases,", Section.allCases)
            print("section,", section)
            if section == .lifeEvents {
                let lifeEventItems = filterItems.filter({$0.category == .lifeEvents})
                print("life event items count", lifeEventItems.count)
                snapshot.appendItems(lifeEventItems, toSection: .lifeEvents)
            } else if section == .physicalSensations {
                let physicalSensationsItems = filterItems.filter({$0.category == .physicalSensations})
                print("physical sensations items count", physicalSensationsItems.count)
                snapshot.appendItems(physicalSensationsItems, toSection: .physicalSensations)
            } else if section == .mindThoughts {
                let mindThoughtsItems = filterItems.filter({$0.category == .mindThoughts})
                print("mind thoughts items count", mindThoughtsItems.count)
                snapshot.appendItems(mindThoughtsItems, toSection: .mindThoughts)
            } else if section == .bodyLocation {
                let bodyLocationItems = filterItems.filter({$0.category == .bodyLocation})
                print("body location items count", bodyLocationItems.count)
                snapshot.appendItems(bodyLocationItems, toSection: .bodyLocation)
            }
        
        }
        
        emotionFiltersDataSource.apply(snapshot, animatingDifferences: true)
    }

This is the rest of the code on how I am constructing my data structure:

    var filterItems: [EmotionExplorerViewController.FilterItem] = []
func setupInitialData() {
        let physicalSensationsFilterNames = ["Tired/Fatigued", "Energetic/Alert", "Calm/Relaxed", "Restless/Fidgety", "Heavy/Weighted Down", "Light/Buzzing", "Tense/Tight", "Numb/Tingling"]
        
        let lifeFilterNames = ["Work", "Family/Relationships", "Social", "Health/Physical Wellbeing"]
                
        let bodyLocationFilterNames = ["Head", "Neck/Throat", "Chest/Heart", "Shoulders", "Arms/Hands", "Solar Plexus", "Stomach/Gut", "Lower Abdomen/Core", "Legs/Feet", "Whole Body"]
        
        let mindThoughtsFilterNames = ["Racing/Fast", "Focused/Clear", "Confused/Foggy", "Overthinking/Spiralling", "Blank", "Reflective/Meditative"]

        do {
            try filterItems.append(contentsOf: bodyLocationFilterNames.map { name in
               if name == "Head" {
                   return EmotionExplorerViewController.FilterItem.head(isSelected: false)
               } else if name == "Neck/Throat" {
                   return EmotionExplorerViewController.FilterItem.neck(isSelected: false)
               } else if name == "Chest/Heart" {
                   return EmotionExplorerViewController.FilterItem.chest(isSelected: false)
               } else if name == "Shoulders" {
                   return EmotionExplorerViewController.FilterItem.shoulders(isSelected: false)
               } else if name == "Arms/Hands" {
                   return EmotionExplorerViewController.FilterItem.arms(isSelected: false)
               } else if name == "Solar Plexus" {
                   return EmotionExplorerViewController.FilterItem.solarPlexus(isSelected: false)
               } else if name == "Stomach/Gut" {
                   return EmotionExplorerViewController.FilterItem.stomach(isSelected: false)
               } else if name == "Lower Abdomen/Core" {
                   return EmotionExplorerViewController.FilterItem.lowerAbdomen(isSelected: false)
               } else if name == "Legs/Feet" {
                   return EmotionExplorerViewController.FilterItem.legs(isSelected: false)
               } else if name == "Whole Body" {
                   return EmotionExplorerViewController.FilterItem.wholeBody(isSelected: false)
               } else {
                   throw MapFilterError.filterNotFound("filter name not found in body location category.")
               }
           })
        } catch let error {
            print("map filters in body location category error,", error)
        }
        
        do {
            try filterItems.append(contentsOf: physicalSensationsFilterNames.map { name in
                if name == "Tired/Fatigued" {
                    return EmotionExplorerViewController.FilterItem.tired(isSelected: false)
                } else if name == "Energetic/Alert" {
                    return EmotionExplorerViewController.FilterItem.energetic(isSelected: false)
                } else if name == "Calm/Relaxed" {
                    return EmotionExplorerViewController.FilterItem.calm(isSelected: false)
                } else if name == "Restless/Fidgety" {
                    return EmotionExplorerViewController.FilterItem.restless(isSelected: false)
                } else if name == "Heavy/Weighted Down" {
                    return EmotionExplorerViewController.FilterItem.heavy(isSelected: false)
                } else if name == "Light/Buzzing" {
                    return EmotionExplorerViewController.FilterItem.light(isSelected: false)
                } else if name == "Tense/Tight" {
                    return EmotionExplorerViewController.FilterItem.tense(isSelected: false)
                } else if name == "Numb/Tingling" {
                    return EmotionExplorerViewController.FilterItem.numb(isSelected: false)
                } else {
                    throw MapFilterError.filterNotFound("filter name not found in physical sensations category.")
                }
            })
        } catch let error {
            print("map filters in physical sensations category error,", error)
        }
        
        do {
            try filterItems.append(contentsOf: lifeFilterNames.map { name in
                switch name {
                    case "Work":
                        return EmotionExplorerViewController.FilterItem.work(isSelected: false)
                    case "Family/Relationships":
                        return EmotionExplorerViewController.FilterItem.family(isSelected: false)
                    case "Social":
                        return EmotionExplorerViewController.FilterItem.social(isSelected: false)
                    case "Health/Physical Wellbeing":
                        return EmotionExplorerViewController.FilterItem.health(isSelected: false)
                    default:
                        throw MapFilterError.filterNotFound("filter name not found in life filter names category")
                }
            })
        } catch let error {
            print("map filters in life filter names category error,", error)
        }
        
        do {
            try filterItems.append(contentsOf: mindThoughtsFilterNames.map { name in
                switch name {
                    case "Racing/Fast":
                        return EmotionExplorerViewController.FilterItem.racing(isSelected: false)
                    case "Focused/Clear":
                        return EmotionExplorerViewController.FilterItem.focused(isSelected: false)
                    case "Confused/Foggy":
                        return EmotionExplorerViewController.FilterItem.confused(isSelected: false)
                    case "Overthinking/Spiralling":
                        return EmotionExplorerViewController.FilterItem.overthinking(isSelected: false)
                    case "Blank":
                        return EmotionExplorerViewController.FilterItem.blank(isSelected: false)
                    case "Reflective/Meditative":
                        return EmotionExplorerViewController.FilterItem.reflective(isSelected: false)
                    default:
                        throw MapFilterError.filterNotFound("filter name not found in mind thoughts filter names category")
                }
            })
        } catch let error {
            print("map filters in mind thought filters category error,", error)
        }
    }

enum FilterItem: Hashable {
        case work(isSelected: Bool)
        case family(isSelected: Bool)
        case health(isSelected: Bool)
        case social(isSelected: Bool)
        case energetic(isSelected: Bool)
        case tired(isSelected: Bool)
        case restless(isSelected: Bool)
        case heavy(isSelected: Bool)
        case numb(isSelected: Bool)
        case calm(isSelected: Bool)
        case tense(isSelected: Bool)
        case light(isSelected: Bool)
        case racing(isSelected: Bool)
        case focused(isSelected: Bool)
        case confused(isSelected: Bool)
        case overthinking(isSelected: Bool)
        case blank(isSelected: Bool)
        case reflective(isSelected: Bool)
        case head(isSelected: Bool)
        case neck(isSelected: Bool)
        case chest(isSelected: Bool)
        case shoulders(isSelected: Bool)
        case arms(isSelected: Bool)
        case solarPlexus(isSelected: Bool)
        case stomach(isSelected: Bool)
        case lowerAbdomen(isSelected: Bool)
        case legs(isSelected: Bool)
        case wholeBody(isSelected: Bool)
        
        var filterName: String {
            switch self {
                case .work: return "Work"
                case .family: return "Family/Relationships"
                case .health: return "Health/Physical Wellbeing"
                case .social: return "Social"
                case .energetic: return "Energetic/Alert"
                case .tired: return "Tired/Fatigued"
                case .restless: return "Restless/Fidgety"
                case .heavy: return "Heavy/Weighted Down"
                case .numb: return "Numb/Tingling"
                case .calm: return "Calm/Relaxed"
                case .tense: return "Tense/Tight"
                case .light: return "Light/Buzzing"
                case .racing: return "Racing/Fast"
                case .focused: return "Focused/Clear"
                case .confused: return "Confused/Foggy"
                case .overthinking: return "Overthinking/Spiralling"
                case .blank: return "Blank"
                case .reflective: return "Reflective/Meditative"
                case .head: return "Head"
                case .neck: return "Neck/Throat"
                case .chest: return "Chest/Heart"
                case .shoulders: return "Shoulders"
                case .arms: return "Arms/Hands"
                case .solarPlexus: return "Solar Plexus"
                case .stomach: return "Stomach/Gut"
                case .lowerAbdomen: return "Lower Abdomen/Core"
                case .legs: return "Legs/Feet"
                case .wholeBody: return "Whole Body"
            }
        }
        
        var category: EmotionFiltersViewController.Section {
            switch self {
                case .work: return .lifeEvents
                case .family: return .lifeEvents
                case .health: return .lifeEvents
                case .social: return .lifeEvents
                    
                case .energetic: return .physicalSensations
                case .tired: return .physicalSensations
                case .restless: return .physicalSensations
                case .heavy: return .physicalSensations
                case .calm: return .physicalSensations
                case .tense: return .physicalSensations
                case .light: return .physicalSensations
                case .numb: return .physicalSensations
                    
                case .racing: return .mindThoughts
                case .focused: return .mindThoughts
                case .confused: return .mindThoughts
                case .overthinking: return .mindThoughts
                case .blank: return .mindThoughts
                case .reflective: return .mindThoughts
                    
                case .head: return .bodyLocation
                case .neck: return .bodyLocation
                case .chest: return .bodyLocation
                case .shoulders: return .bodyLocation
                case .arms: return .bodyLocation
                case .solarPlexus: return .bodyLocation
                case .stomach: return .bodyLocation
                case .lowerAbdomen: return .bodyLocation
                case .legs: return .bodyLocation
                case .wholeBody: return .bodyLocation
            }
        }
        
        var isSelected: Bool {
            switch self {
                case .work(let isSelected): return isSelected
                case .family(let isSelected): return isSelected
                case .health(let isSelected): return isSelected
                case .social(let isSelected): return isSelected
                    
                case .energetic(let isSelected):
                    return isSelected
                case .tired(let isSelected):
                    return isSelected
                case .restless(let isSelected):
                    return isSelected
                case .heavy(let isSelected):
                    return isSelected
                case .calm(let isSelected):
                    return isSelected
                case .tense(let isSelected):
                    return isSelected
                case .light(let isSelected):
                    return isSelected
                case .numb(let isSelected):
                    return isSelected
                    
                case .racing(let isSelected):
                    return isSelected
                case .focused(let isSelected):
                    return isSelected
                case .confused(let isSelected):
                    return isSelected
                case .overthinking(let isSelected):
                    return isSelected
                case .blank(let isSelected):
                    return isSelected
                case .reflective(let isSelected):
                    return isSelected
                    
                case .head(let isSelected):
                    return isSelected
                case .neck(let isSelected):
                    return isSelected
                case .chest(let isSelected):
                    return isSelected
                case .shoulders(let isSelected):
                    return isSelected
                case .arms(let isSelected):
                    return isSelected
                case .solarPlexus(let isSelected):
                    return isSelected
                case .stomach(let isSelected):
                    return isSelected
                case .lowerAbdomen(let isSelected):
                    return isSelected
                case .legs(let isSelected): return isSelected
                case .wholeBody(let isSelected): return isSelected
            }
        }
    }

Expected behavior

I expect each of the statements in the switch statement to execute exactly once, however they are executing multiple times.

Environment

swift version 5.0

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.triage neededThis issue needs more specific labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions