Search code examples
iosswiftcollectionview

CollectionView losing reference to data model


Kind of stuck on the same problem now for a couple of days.

I have a ViewControllerA class that serves as a CollectionView delegate and datasource. The CollectionView is hooked up through a storyboard. ViewControllerA class contains the data model that feeds the CollectionView.

Whenever ViewControllerA loads for the first time, it connects to a database, and loads information as needed into a data model (userDictionary) . Once the controller loads all data, it reloads the CollectionView, and fires syncing methods that listen for any changes in the database, to update the userDictionary accordingly, and reload the respective items in the collectionView.

Everything works fine up to this point.

When I transition to any different ViewController class, say ViewControllerB class, I’m passing a copy of the userDictionary from ViewControllerA to ViewControllerB in prepareForSenderA, and passing it back to ViewControllerA through prepareForSenderB.

Here’s the strange behavior. When I transition back to ViewControllerA class, the CollectionView loads fine with the same data that was passed to ViewControllerB, but fails to load any new changes that the syncing method observes in ViewControllerA.

I know that the syncing methods are still working fine, because any new data is showing up in the debugger when I print it out as it’s loading in ViewControllerA. And I know that my userDictionary data model in the same controller class is receiving those updates because of a didSet observer that’s printing out the most up-to-date state of userDictionary.

And the odd thing is that whenever I print out the contents of the data model within ViewControllerA class, it prints out the old state of the model as it existed when it was passed to ViewControllerB class. Even though the didSet observer just proved that the model was actually updated!

It’s almost as if ViewControllerA class is somehow retaining a reference to the data model as it existed when it was passed over to ViewControllerB, and somehow “loses” its reference to the data model in “self” when that model gets passed back.

Another note: If I stay in ViewControllerA the whole time, and don’t pass the userDictionary back and forth, I don’t get this problem any more.

The code below sums up how I'm passing data back and forth:

View Controller A Class:

class ViewControllerA: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

//MARK: Data Structures
var userDictionary = [[String: String]]() { didSet { print("userDictionary.count from DIDSET,", userDictionary.count)}}
//prints out: 9

//MARK: Initialization
override func viewDidLoad() {
    self.loadUserDictionaryFromDatabase()
}

func loadUserDictionaryFromDatabase() {
    //code that loads information from a database
    //var objectInstance["name"] = firstName
    //self.userDictionary.append(objectInstance)
    print("loader observed numberOfItems", numberOfItems)
    //...once data is fully loaded
    self.syncDataFromDatabase()
}

func syncDataFromDatabase() {
    //sync new data from database
    //var newObjectInstance["newName"] = newFirstName
    //self.userDictionary.append(newName)
    print("syncer observed newNumberOfItems", newNumberOfItems)
}

//MARK: View Controller Transitions
@IBAction func segueAtoB(_ sender: Any) {
    self.performSegue(withIdentifier: "segueAtoB", sender: self)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    //Segue from VCA to VCB
    if segue.identifier == "segueAtoB" {

        let controller = segue.destination as! ViewControllerA

        //Pass initialized userDictionary from VCA to VCB
        controller.userDictionary = self.userDictionary
    }
}

//MARK: Dummy Test Number of Items in UserDictionary
@IBAction func printMostUpToDateNumberofItemsInDictionary(_ sender: Any) {
    print("userDictionary.count from TEST,", userDictionary.count)
}

}

View Controller B Class:

class ViewControllerB: UIViewController {

//MARK: Data Structures
var userDictionary = [[String: String]]()

//MARK: View Controller Transitions
@IBAction func segueBtoA(_ sender: Any) {
    self.performSegue(withIdentifier: "segueBtoA", sender: self)
}


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    //1. Segue to Home-Screen Controller
    if segue.identifier == "segueBtoA" {

        let controller = segue.destination as! ViewControllerB
        controller.userDictionary = self.userDictionary
    }
}

}

Result of Prints:

Step 1: Start in View Controller A

  1. loader observed numberOfItems = 9
  2. userDictionary.count from DIDSET, 9
  3. syncer observed newNumberOfItems = 1
  4. userDictionary.count from DIDSET = 10 (10 = 9 + 1) -- PASS
  5. userDictionary.count from TEST = 10 (10 = 9 + 1) -- PASS

Step 2: Segue From A to B

viewControllerB.userDictArray = viewControllerA.userDictArray

Step 3: Segue From B to A

viewControllerA.userDictArray = viewControllerB.userDictArray

Step 4: Observe Debugger Output

  1. userDictionary.count from DIDSET, 10
  2. syncer observed newNumberOfItems = 1
  3. userDictionary.count from DIDSET = 11 (10 = 10 + 1) -- PASS
  4. userDictionary.count from TEST = 10 (10 != 10 + 1) -- FAIL (How is this line happening when diSet just updated userDictionary.count?)

Debugger output using UUID:

Step 1: Start in View Controller A

  1. loader observed numberOfItems, A74593E1-1231-41BE-A5DF-693591F998E4
  2. userDictionary.count from DIDSET, A74593E1-1231-41BE-A5DF-693591F998E4
  3. syncer observed newNumberOfItems, A74593E1-1231-41BE-A5DF-693591F998E4
  4. userDictionary.count from DIDSET, A74593E1-1231-41BE-A5DF-693591F998E4
  5. userDictionary.count from TEST, A74593E1-1231-41BE-A5DF-693591F998E4

Step 4: Observe Debugger Output

  1. userDictionary.count from DIDSET, A74593E1-1231-41BE-A5DF-693591F998E4
  2. syncer observed newNumberOfItems, A74593E1-1231-41BE-A5DF-693591F998E4
  3. userDictionary.count from DIDSET, A74593E1-1231-41BE-A5DF-693591F998E4
  4. userDictionary.count from TEST, 28D817D9-B53D-47D8-A3EF-2F7DDE6460FC

Solution

  • I really think you need / want to use an "unwind" segue.

    In ViewControllerA add this function:

    @IBAction func returnFromB(_ segue: UIStoryboardSegue) {
        if let vcDest = segue.destination as? ViewControllerA,
            let vcSource = segue.source as? ViewControllerB {
                // pass userDictionary from VCB back to VCA
                vcDest.userDictionary = vcSource.userDictionary
            print("unwind segue from B")
        }
    }
    

    Then, connect it by Ctrl-Drag from the View Controller icon to the Exit icon at the top of ViewControllerA in your storyboard. Give it a segue identifier.

    Now, in ViewControllerB, you can call:

    self.performSegue(withIdentifier: "unwindSegueBtoA", sender: self)
    

    and the returnFromB() function in ViewControllerA will handle transferring back your data.

    Here is a basic example: https://github.com/DonMag/DataPass

    Another (probably better) option would be to create a "data manager" class, and set/get your data from that class from each controller, as opposed to passing it back and forth.