Search code examples
swiftuicollectionviewprotocolsuigesturerecognizeruicollectionviewcell

CollectionView delegate error


I have made a collection view programmatically but when set collectionView.delegate = self and collectionView.dataSource = self I get a nil while unwrapping an optional. I don't know what I did wrong here.

class MainFeedViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, UIViewControllerTransitioningDelegate, UIGestureRecognizerDelegate, MyCollectionCell {

    let transition = CircularAnimation()
    var collectionView: UICollectionView!
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))

    func MyCollectionCell() {
        let vc = DescriptionViewController()
        self.present(vc, animated: true, completion: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationController?.navigationBar.isHidden = true
        collectionView.delegate = self
        collectionView.dataSource = self

        view.backgroundColor = .white
        UIApplication.shared.isStatusBarHidden = false
        UIApplication.shared.statusBarStyle = .default
        view.addSubview(Settings)
        view.addSubview(topBar)
        view.addSubview(separatorView)
        view.addSubview(separatorView2)

        Settings.translatesAutoresizingMaskIntoConstraints = false
        Settings.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15).isActive = true
        Settings.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        Settings.widthAnchor.constraint(equalToConstant: 50).isActive = true
        Settings.heightAnchor.constraint(equalToConstant: 50).isActive = true

        separatorView.translatesAutoresizingMaskIntoConstraints = false
        separatorView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40).isActive = true
        separatorView.heightAnchor.constraint(equalToConstant: 1).isActive = true
        view.addConstraint(NSLayoutConstraint(item: separatorView, attribute: .left, relatedBy: .equal, toItem: Settings, attribute: .right, multiplier: 1, constant: 15))
        view.addConstraint(NSLayoutConstraint(item: separatorView, attribute: .right, relatedBy: .equal, toItem: topBar, attribute: .right, multiplier: 1, constant: 0))

        separatorView2.translatesAutoresizingMaskIntoConstraints = false
        separatorView2.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40).isActive = true
        separatorView2.heightAnchor.constraint(equalToConstant: 1).isActive = true
        view.addConstraint(NSLayoutConstraint(item: separatorView2, attribute: .right, relatedBy: .equal, toItem: Settings, attribute: .left, multiplier: 1, constant: -15))
        view.addConstraint(NSLayoutConstraint(item: separatorView2, attribute: .left, relatedBy: .equal, toItem: topBar, attribute: .left, multiplier: 1, constant: 0))

        topBar.translatesAutoresizingMaskIntoConstraints = false
        topBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        topBar.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        topBar.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        view.addConstraint(NSLayoutConstraint(item: topBar, attribute: .bottom, relatedBy: .equal, toItem: separatorView, attribute: .top, multiplier: 1, constant: 0))
        view.insertSubview(topBar, belowSubview: Settings)

        let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        let height = (view.frame.width - 16 - 16) * 9/16
        layout.sectionInset = UIEdgeInsets(top: 80, left: 0, bottom: 0, right: 0)
        layout.itemSize = CGSize(width: view.frame.width, height: height + 16 + 80)

        collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(80, 0, 0, 0)
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(Cell.self, forCellWithReuseIdentifier: "cellId")
        collectionView.backgroundColor = UIColor.clear
        collectionView.addGestureRecognizer(tapGesture)
        tapGesture.delegate = self
        self.view.addSubview(collectionView)
        view.insertSubview(collectionView, belowSubview: topBar)

    }

    let Settings : UIButton = {
        let btn = UIButton()
        btn.setTitle("Clotho", for: .normal)
        btn.setTitleColor(.white, for: .normal)
        btn.layer.cornerRadius = 25
        btn.backgroundColor = UIColor.rgb(displayP3Red: 255, green: 165, blue: 0)
        btn.titleLabel?.font = UIFont(name: "Pacifico-Regular", size: 16)
        btn.addTarget(self, action:#selector(settingsTab), for: .touchUpInside)
        return btn
    }()

    let topBar: UIView = {
        let bar = UIView()
        bar.backgroundColor = .white
        return bar
    }()

    let separatorView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.rgb(displayP3Red: 211, green: 211, blue: 211)
        return view
    }()

    let separatorView2: UIView = {
        let view2 = UIView()
        view2.backgroundColor = UIColor.rgb(displayP3Red: 211, green: 211, blue: 211)
        return view2
    }()

    @objc func tapEdit(recognizer: UITapGestureRecognizer)  {
        if recognizer.state == UIGestureRecognizerState.ended {
            let tapLocation = recognizer.location(in: self.collectionView)
            if let tapIndexPath = self.collectionView.indexPathForItem(at: tapLocation) {
                if (self.collectionView.cellForItem(at: tapIndexPath) as? Cell) != nil {
                    //do what you want to cell here

                }
            }
        }
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 4
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! Cell
        cell.Delegate = self
        return cell
    }

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        transition.transitionMode = .present
        transition.startingPoint = Settings.center
        transition.circleColor = Settings.backgroundColor!

        return transition
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        transition.transitionMode = .dismiss
        transition.startingPoint = Settings.center
        transition.circleColor = Settings.backgroundColor!

        return transition
    }

    @objc func settingsTab(){
        let secondVC = SettingsViewController()
        secondVC.transitioningDelegate = self
        secondVC.modalPresentationStyle = UIModalPresentationStyle.custom
        self.present(secondVC, animated: true, completion: nil)
    }
}

I set a var Delegate: MyCollectionCell? in my cell with a protocol

      import UIKit

        protocol MyCollectionCell {
            func MyCollectionCell()
        }

        class Cell: UICollectionViewCell {

            var Delegate: MyCollectionCell?

            override init(frame: CGRect) {
                super.init(frame: frame)
                setupViews()

                let TapGesture = UITapGestureRecognizer(target: self, action: #selector(Cell.tapEdit(sender:)))
                addGestureRecognizer(TapGesture)
                TapGesture.delegate = MainFeedViewController()
            } 
    //other setup code...

@objc func tapEdit(sender: UITapGestureRecognizer) {
        Delegate?.MyCollectionCell()
    }

Solution

  • You haven't actually created the collection view anywhere.

    This line:

    var collectionView: UICollectionView!
    

    creates a variable ready to hold the collection view (and the ! character indicates you should populate the variable in the viewDidLoad method) but you don't actually create the instance of the UICollectionView and assign it to that variable.

    So when you try to set the delegate and data source the collection view variable is still nil and hence you get an error.

    You need to actually create the instance of a UICollectionView which is also going to involve creating an instance of a UICollectionViewLayout (or a subclass of it like UICollectionViewFlowLayout).

    At the most basic level you should do something like this:

    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: 100, height: 100)
    collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 500, height: 500), collectionViewLayout: layout)
    

    although of course you should adjust the frame and the parameters of the layout to suit your usage requirements.