How to carry selectedChapterIndex across multiple view controllers?

In this app, I have three view controllers:

  1. ContentsVC
  2. ChapterVC
  3. RecipeVC

When a user taps a Chapter, the screen segues to the ChapterViewController. The collection of Chapters then displays in the left-hand column, and a Table of Recipes will appear in the body of the screen, and the label of the chapter will appear in the header.

The intention is that when the user taps a recipe the screen segues to the RecipeViewController. The collection of Chapters is not displayed, and the Table of Recipes will now appear in the left-hand column, and the Recipe details will display in the body of the screen. The label of the chapter will appear atop the Table of Recipes, and the label of the Recipe will appear in the Header.

UPDATED
I can’t figure out how to handle the selectedChapterIndex across VCs. The problem appears to be that in the segue from the ContVC

let chapter = chapters[selectedChapterIndex]

is getting the initialised index from

var selectedChapterIndex:Int = 0

but not from the UICollectionViewDelegate method didSelectItemAt

selectedChapterIndex = indexPath.row

So I am confounded as to how to get the segue to pick up the index from didSelectItemAt

I have pasted the code of the ContentsViewController below.

Alternately I have a repo in GitHub, so if you would like to help, let me know your GitHub username and I can add you as a collaborator

//
//  ContentsViewController.swift
//  CookAlpha
//
//  Created by Lachlan McKerrow on 20/12/19.
//  Copyright © 2019 Lachlan McKerrow. All rights reserved.
//

import UIKit

class ContentsViewController: UIViewController {
    
    // 1g Create the property for the CookbookModel
    var model = CookbookModel()
    
    // 1h Declare a cookbook property so we can store a reference to chapters and recipes that come back, and initialise it to an empty array
    var cookbook = [Cookbook]()
    
    // 4c Declare a chapter property so we can store a reference to chapters that come back, and initialise it to an empty array
    var chapters = [Chapter]()
    
    // 4a Drag in the Chapters View's Collection View outlet
    @IBOutlet weak var chapterCollection: UICollectionView!
    
    // 7b Store the index of the default chapter, and initialize it to 0
    var selectedChapterIndex:Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        // 4b Conform to the collectionView protocols
        chapterCollection.delegate = self
        chapterCollection.dataSource = self
        
        // 1m Assign the SVC as delegate
        model.delegate = self
        
        // 1i Call getCookbook from the CookbookModel
        model.getCookbook()
        
    }
    
    // 11c Before the segue occurs, this gives us access to the destination ChapVC so we can set properties
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // 11d Detect the index path of the chapter which the user tapped on for Collection View
        let indexPath = chapterCollection.indexPathsForSelectedItems
        
        guard indexPath != nil else {
            print("User didn't select a chapter!")
            return
        }
        
        // 11e Get the selected chapter. If it is not nil, then we set the item, which is unwrapped, as it has been verified that it is not nil
        let chapter = chapters[selectedChapterIndex]
        
        // 11f Get a reference to the ChapVC
        let chapterVC = segue.destination as! ChapterViewController
        
        // 11g Set the chapter properties (or URL) to be segued into ChapVC
        chapterVC.selectedChapter = chapter.chapterName
        chapterVC.selectedChapterIndex = chapter.chapterIndex
        
    }
    
}

// 4j Add the UICollectionViewDelegate
extension ContentsViewController: UICollectionViewDelegate {
    
    // 4k Add the didSelectItemAt Chapter Collection
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        // 11h Update the selectedChapterIndex
        selectedChapterIndex = indexPath.row
        
        // 11i Reload the chapterCollection to display recipes in the newly selected chapter
        // chapterCollection.reloadData()
    }
    
}

// 4d Add the UICollectionViewDataSource
extension ContentsViewController: UICollectionViewDataSource {
    
    // 4e Add in the numberOfItemsInSection stub
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        // 4f Return the number of chapters
        return chapters.count
        
    }
    
    // 4g Add in the cellForItemAt stub
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        // 4h Get the cell
        // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ChapterCell", for: indexPath)
        
        // 5k After commenting out 4h We can now cast the following call as a ChapterCell by adding 'as! ChapterCell'
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.Views.ChapterCell, for: indexPath) as? ChapterCell
            else {
                fatalError("Wrong cell class dequed")
        }
        
        // 5l TEST: Get chapter that the collectionview is trying to display
        let chapter = chapters[indexPath.row]
        
        // 5m TEST: For the cell at row get the chapters
        cell.displayChapter(chapter)
        
        // 5o Add Chapter Images into the Assets, then test the app
        
        // 4i Return that cell
        return cell
        
    }
    
}

// 1n Create the extension and have the ContVC conform to the CookbookModelProtocol in an extension
extension ContentsViewController: CookbookModelProtocol {

    // 1o Get the sections and recipes from the CookbookModel
    func cookbookRetrieved(_ cookbook: Cookbook) {
        
        // 1p TEST (needs 1q over in CookbookModel.swift) print a statement to verify that the comms have been set up correctly
        // print("Test that Cookbook comes back")
        
        
        // 1r Now we are pulling back JSON feed, assign the chapters that come back to the property at 2a nee 1q in CookbookModel, which provides a reference for when we want to display the in the collection view.  Here's the thing to remember: Given the block of code, "chapters" mean nothing in this block, so we must use dot notation to join "cookbook" to "chapters" in order to bring in the parsed JSON data
        self.chapters = cookbook.chapters
        
        // 4l Reload collection view chapterCollection, then run the app to see if it works :)
        chapterCollection.reloadData()
        
    }
}

The reason for the problem was that I had added a segue from collectionview cell to chapterviewcontroller - which gives higher priority to prepareSegue method instead of didselectrowat index path.

The solution was to delete the existing segue, then create a new one between the ContentsVC and the ChapterVC, with identifier “toChapters” and then:

  1. Delete from override func prepare(for segue: UIStoryboardSegue, sender: Any ?) the following:
let indexPath = chapterCollection.indexPathsForSelectedItems
guard indexPath != nil else {
          print("User didn't select a chapter!")
          return
  1. Add into extension ContentsViewController: UICollectionViewDelegate
performSegue(withIdentifier: "toChapters", sender: nil )
1 Like