Updating a collectionView after dismissing VC

I have a collection view in VC1 embedded in a navigationController. The collection view contains buttons that go to VC2. When make changes in VC2 and close that VC, I want the collectionView in VC1 to update. However, the functions that make up the collectionView aren't inside viewWillAppear; how can I make VC1 refresh after closing VC2? I have tried a few different things written at the bottom of VC2

VC1

Code Block
import UIKit
class HerdViewCell: UICollectionViewCell {
    @IBOutlet weak var horseNameOutlet: UILabel!
    @IBOutlet weak var baseLayer: UIImageView!
}
var myHorses = [
    Horse(name: "Donnerhall", idNumber: 1, gender: "Stallion", age: 1),
    Horse(name: "Celeste", idNumber: 2, gender: "Mare", age: 1),
    Horse(name: "Kara", idNumber: 3, gender: "Mare", age: 1)
]
var horseIndex = 0
import UIKit
class HerdViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    @IBOutlet weak var herdCollectionView: UICollectionView!
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        herdCollectionView.delegate = self
        herdCollectionView.dataSource = self
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return myHorses.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        var cell = UICollectionViewCell()
        if let horseCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? HerdViewCell {
            let horseTag = "\(myHorses[indexPath.row].name ?? "none") \n\(myHorses[indexPath.row].gender), \(myHorses[indexPath.row].age) years"
            horseCell.horseNameOutlet.text = horseTag
            horseCell.baseLayer.image = myHorses[indexPath.row].basePhenotype
            cell = horseCell
            horseIndex = indexPath.row
        }
        return cell
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        horseIndex = indexPath.row
        performSegue(withIdentifier: "horseViewerSegue", sender: self)
    }
    }
}

VC2
Code Block
import UIKit
class HorseViewController: UIViewController {
    @IBOutlet weak var horseNameOutlet: UILabel!
    @IBOutlet weak var horseGenderOutlet: UILabel!
    @IBOutlet weak var horseAgeOutlet: UILabel!
    @IBOutlet weak var baseLayer: UIImageView!
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        updateHorse()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
    func updateHorse(){
        horseNameOutlet.text = myHorses[horseIndex].name
        horseGenderOutlet.text = myHorses[horseIndex].gender
        horseAgeOutlet.text = "\(String(myHorses[horseIndex].age)) years"
        baseLayer.image = myHorses[horseIndex].basePhenotype
     }
    @IBAction func sellHorse(_ sender: UIButton) {
        myHorses.remove(at: horseIndex)
//dismissing won't work, because the app closes the navigationController instead of going back to the root of the navigation controller
        //dismiss(animated: true, completion: nil)
//If I navigate to the root of the navigationController, the collectionView is as I left it instead of removing a horse
//        self.navigationController?.popViewController(animated: true)
    }
}


Answered by Claude31 in 663651022
Just call

Code Block
myCollection.reloadData()

That will trigger
Code Block
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { }

using the new myHorses dataSource content.
If you navigate from VC1 to VC2 with NavController, the VCs are stacked. Which means VC1 is still present.

I see at least 2 options:
  • use delegation to call a function in VC1 from VC2

  • or

  • in VC2 viewWillDisappear, call

Code Block
let vcs = vC2.navigationController?.viewControllers
  • VC1 should be [0] ; then call

Code Block
let vc1 = vcs[0]
vc1.functionToUpdate


or if VC1 is the first in navigation
Code Block
vC2.navigationController?.topViewController.functionToUpdate

If I go with the second method and write:

Code Block
    func viewWillDisappear() {
        let vcs = vC2.navigationController?.viewControllers
        let vc1 = vcs[0]
            vc1.functionToUpdate
    }

Is "vc1" and "vc2" meant to match the class of the viewControllers in the project?
Is this written correctly or are parts supposed to be in VC1?
The navigation controller starts after the Home Screen, btw, I didn't mention that before. I have three navigation controllers branching off from the initial VC, is this bad practice or normal in IOS development?

Is "vc1" and "vc2" meant to match the class of the viewControllers in the project?

I just used the info you gave in OP:

I have a collection view in VC1 embedded in a navigationController. The collection view contains buttons that go to VC2. 

so vc1 is the instance of VC1 and vc2 instance of VC2

This is in viewWillDisappear of VC2. Exact ?

Then, you could write (I adjusted for optionals):
Code Block
func viewWillDisappear() {
if let vcs = self.navigationController?.viewControllers {
let vc1 = vcs[0] // may need to write if let vc1 = vcs[0] as? VC1 { vc1.functionToUpdate }
vc1.functionToUpdate
}
}


The navigation controller starts after the Home Screen, btw, I didn't mention that before. I have three navigation controllers branching off from the initial VC, is this bad practice or normal in IOS development?

initial VC is Home screen ?
How do you go from there to each navigation stack ?
I see nothing wrong there, as long as user can understand where he/she is navigating to.

For the code above to work, what is key is to have the following sequence (in IB):

Home Screen > NavigationController > VC1 > VC2
That would also work with a pattern as

Home Screen > NavigationController > VC1 > VC1bis > VC1ter > VC2
Another note: if you want to be sure to return to Home, just use popToRootViewController (line 31)

Code Block
        self.navigationController?.popToRootViewController(animated: true)

And in viewWillDisappear:
Code Block
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let vc1 = self.navigationController?.topViewController as? VC1 {
vc1.functionToUpdate
}
}

VC1 is HerdViewController and VC2 is HorseViewController.
The Home Screen has a button for each navigation controller, HerdViewController starts the first navigation controller

I get an error "Value of type 'HerdViewController' has no member 'functionToUpdate'"

Code Block  
override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
            if let vc1 = self.navigationController?.topViewController as? HerdViewController {
                    vc1.functionToUpdate
                }
    }


functionToUpdate is a generic name. I don't know the name of update function you want to call.

So, in HerdViewController, you should have:
Code Block
func functionToUpdate() {
    // update the collectionView here
}

And in HorseViewController
Code Block
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let vc1 = self.navigationController?.topViewController as? HerdViewController {
vc1.functionToUpdate()
}
}


May be you can pass the list of cells to update or to remove as a function parameter, to avoid reloading the full collectionView.

Note: effectively, you must not dismiss a view in a Navigation stack.
Ahh, I thought functionToUpdate didn't make sense. I have now made a function in herdViewController

Code Block    
func refreshHerd() {
        print("refresh")
    }

and then updated viewWillDisappear but get an error: "Expression resolves to an unused function"

Code Block
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
            if let vc1 = self.navigationController?.topViewController as? HerdViewController {
                    vc1.refreshHerd
                }
    }

I know how to remove cells from a stack view and then replace them programmatically, but a collectionView is different, it seems to just "be there" regardless of what happens in the lifecycle of the VC besides these two, but it doesn't make any difference if I place them in viewDidLoad or viewWillAppear:
Code Block
herdCollectionView.delegate = self
        herdCollectionView.dataSource = self


Misses ()

Code Block
vc1.refreshHerd()


So, you can update by reloading the CollectionView.
Ok, now the function is triggered, but how do you reload a collectionView? I think that will be the easiest way to do it
Accepted Answer
Just call

Code Block
myCollection.reloadData()

That will trigger
Code Block
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { }

using the new myHorses dataSource content.
Nice! It's working perfectly now :)
Updating a collectionView after dismissing VC
 
 
Q