Search code examples
iosswiftuiviewcontrolleruicollectionviewsegue

How to add item to a UICollectionViewController from a separate UIViewController without using Storyboard segue?


I have a UICollectionViewController displaying an array of data called countdowns. I have a UIViewController that is presented modally where the user can create a new countdown. When the user taps "Add" (a UIBarButtonItem), I want to add the new countdown from UIViewController to the array (and thus the collection view) on UICollectionViewController.

I am building my app completely programmatically (no storyboard files). Every article/stack overflow post/YouTube tutorial I can find involves using a UIStoryBoardSegue and/or is about pushing data from a CollectionView to another ViewController, but not the other way around. I have not found a way to do this 100% programmatically.

During my hunt I've read is generally bad to create and use segues purely programmatically. Is this true? If so, do I just need to use a delegate? How would I implement that?

Here's some code:

Countdown Class: (has other properties, but for sake of simplicity I'm only including the title property)

class Countdown {

    var title: String!

    init?(title: String!) {

        // Fail initialization if string is blank
        if title.isEmpty {
            return nil
        }

        // Initializing property
        self.title = title

    }

}

CountdownCollectionViewController: (this is the main view of the app)

class CountdownCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    var countdowns = [Countdown]()

    override func viewDidLoad() {
        super.viewDidLoad()

        loadSampleCountdowns()

    }

    private func loadSampleCountdowns() {

        // created 3 sample countdowns here

        countdowns += [countdown1, countdown2, countdown3]
    }

UIViewController: (presented modally from CountdownsCollectionViewController)

class ViewController: UIViewController {

    var countdown: Countdown?

    // here I have a textfield property where the user enters a title for a new countdown 

    override func viewDidLoad() {
        super.viewDidLoad()


        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .done, target: self, action: #selector(addTapped))

    }


    @objc func addTapped(_ sender: UIBarButtonItem) {

        let title = titleTextField.text ?? ""

        countdown = Countdown(title: title)

        // here is where I want an action to send the newly created countdown to CountdownCollectionViewController

}

I have tried using this function in CountdownCollectionViewController that uses a segue, but since I do not have storyboards it of course does not work. However, I think I'm headed in the right direction with the code that is inside of it:

func unwindToCountdownList(_ sender: UIStoryboardSegue) {

    if let sourceViewController = sender.source as? ViewController, let countdown = sourceViewController.countdown {

        // Add a new countdown:
        let newIndexPath = IndexPath(row: countdowns.count, section: 0)

        // Append the new countdown from UIViewController to the array:
        countdowns.append(countdown) 

        // Add new item to collectionView:
        collectionView.insertItems(at: [newIndexPath])
}

Solution

  • I would use a delegate here.

    Create a ViewControllerDelegate:

    protocol ViewControllerDelegate: class {
        func didCreateCountDown(vc: ViewController, countDown: CountDown)
    }
    

    Add a delegate property to ViewController and call it at the appropriate time:

    weak var delegate: ViewControllerDelegate?
    @objc func addTapped(_ sender: UIBarButtonItem) {
    
        let title = titleTextField.text ?? ""
    
        countdown = Countdown(title: title)
    
        delegate.didCreateCountDown(vc: self, countDown: countDown)
    
        dismiss(animated: true, completion: nil)
    
    }
    

    Make CountdownCollectionViewController conform to ViewControllerDelegate:

    extension CountdownCollectionViewController : ViewControllerDelegate {
        func didCreateCountDown(vc: ViewController, countDown: CountDown) {
            // Add a new countdown:
            let newIndexPath = IndexPath(row: countdowns.count, section: 0)
    
            // Append the new countdown from UIViewController to the array:
            countdowns.append(countdown) 
    
            // Add new item to collectionView:
            collectionView.insertItems(at: [newIndexPath])
        }
    }
    

    Although you did not show this, there should be a place in CountdownCollectionViewController that you call present to present ViewController:

    let vc = ViewController()
    ...
    present(vc, animated: true, completion: nil)
    

    Just before present, you can set self as the delegate:

    vc.delegate = self