2

We want to show another controller by replacing main controller when clicking on item (i.e ImageView) inside a cell collection. But we are not able to get main collection view and can't able to navigate targeted controller. We are using following approach -

enter image description here

HomeViewComtroller.swift

import LBTAComponents

class HomeViewController: DatasourceController {

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Home"
        collectionView?.contentInset = UIEdgeInsetsMake(50, 0, 0, 0)
        collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, 0)
        collectionView?.backgroundColor = UIColor(r: 232, g: 236, b: 241, a: 1)
        let homeViewDatasource = HomeViewDatasource()
        self.datasource = homeViewDatasource
    }


    override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        if indexPath.section == 0 {
            return CGSize(width: view.frame.width, height: 120)
        } else if indexPath.section == 6 {
            return CGSize(width: view.frame.width, height: 120)
        } else {
             return CGSize(width: view.frame.width, height: 200)
            }


    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
            return CGSize(width: view.frame.width, height: 33)
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width: view.frame.width, height: 10)
    }


//    lazy var homeMainCatgCell: HomeMainCatgCell = {
//        let homemain = HomeMainCatgCell ()
//        homemain.homeViewcontroller = self
//        return homemain
//    }()


    func handleCtgClick (ctgname: String ,ctgId: String) {

        let dummySettingViewController = UIViewController()
        dummySettingViewController.view.backgroundColor = UIColor.white
        dummySettingViewController.navigationItem.title = ctgname
        navigationController?.navigationBar.tintColor = UIColor.white
        navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
        navigationController?.pushViewController(dummySettingViewController, animated: true)

    }

}

HomeViewDatasource

import LBTAComponents

class HomeViewDatasource: Datasource {

    override func headerClasses() -> [DatasourceCell.Type]? {
         return [HomeMainCatgCellHeader.self,HomeSaleCatgCellHeader.self,HomeNewArrivalCellHeader.self,HomeBestSallingCellHeader.self,HomeBigDiscountCellHeader.self,HomeSpecialOfferCellHeader.self,HomeBrandCellHeader.self]
    }
    override func footerClasses() -> [DatasourceCell.Type]? {
        return[HomeCellsFooter.self]
    }
    override func cellClasses() -> [DatasourceCell.Type] {
        return [HomeMainCatgCell.self,HomeSaleCtagCell.self,HomeNewArrivalsCell.self,HomeBestSallingCatgCell.self,HomeBigDiscountCatgCell.self,HomeSpecialOfferCatgCell.self,HomeBrandVIewCell.self]
    }
    override func numberOfItems(_ section: Int) -> Int {
        return 1
    }
    override func numberOfSections() -> Int {
        return 7
    }
}

HomeMainCatgCell.swift

import LBTAComponents

class HomeMainCatgCell: DatasourceCell {
    var homeViewcontroller: HomeViewController?
    let personalcareCatgImageView: UIImageView = {
        let iv = UIImageView()
        iv.image = #imageLiteral(resourceName: "personalcareimage")
        iv.tag = 0
        iv.contentMode = .scaleToFill
        return iv
    }()

    let healthcareCatgImageView: UIImageView = {
        let iv = UIImageView()
        iv.image = #imageLiteral(resourceName: "healthcareimage")
        iv.contentMode = .scaleToFill
        iv.tag = 1
        return iv
    }()

    let homecareCatgImageView: UIImageView = {
        let iv = UIImageView()
        iv.image = #imageLiteral(resourceName: "homecareimage")
        iv.contentMode = .scaleToFill
        iv.tag = 2
        return iv
    }()

    let kitchencareCatgImageView: UIImageView = {
        let iv = UIImageView()
        iv.image = #imageLiteral(resourceName: "kitchencareiamge")
        iv.contentMode = .scaleToFill
        iv.tag = 3
        return iv
    }()


    override func setupViews() {
        super.setupViews()
        let personalcareCatgImageContainerView = UIView()
        personalcareCatgImageContainerView.backgroundColor = .white

        let homecareCatgImageContainerView = UIView()
        homecareCatgImageContainerView.backgroundColor = .white


        let healthcareCatgImageContainerView = UIView()
        healthcareCatgImageContainerView.backgroundColor = .white


        let kitchencareCatgImageContainerView = UIView()
        kitchencareCatgImageContainerView.backgroundColor = .white


        let imageStackView = UIStackView(arrangedSubviews: [personalcareCatgImageContainerView,homecareCatgImageContainerView,healthcareCatgImageContainerView,kitchencareCatgImageContainerView])
        imageStackView.axis = .horizontal
        imageStackView.distribution = .fillEqually
        addSubview(imageStackView)
        imageStackView.anchor(topAnchor, left: leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: frame.width, heightConstant: frame.height)

        imageStackView.addSubview(personalcareCatgImageView)
        imageStackView.addSubview(healthcareCatgImageView)
        imageStackView.addSubview(homecareCatgImageView)
        imageStackView.addSubview(kitchencareCatgImageView)

        personalcareCatgImageView.anchor(personalcareCatgImageContainerView.topAnchor, left: personalcareCatgImageContainerView.leftAnchor, bottom: personalcareCatgImageContainerView.bottomAnchor, right: personalcareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: personalcareCatgImageContainerView.frame.width , heightConstant: personalcareCatgImageContainerView.frame.width)

          healthcareCatgImageView.anchor(healthcareCatgImageContainerView.topAnchor, left: healthcareCatgImageContainerView.leftAnchor, bottom: healthcareCatgImageContainerView.bottomAnchor, right: healthcareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: healthcareCatgImageContainerView.frame.width , heightConstant: healthcareCatgImageContainerView.frame.width)

          homecareCatgImageView.anchor(homecareCatgImageContainerView.topAnchor, left: homecareCatgImageContainerView.leftAnchor, bottom: homecareCatgImageContainerView.bottomAnchor, right: homecareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: homecareCatgImageContainerView.frame.width , heightConstant: homecareCatgImageContainerView.frame.width)

          kitchencareCatgImageView.anchor(kitchencareCatgImageContainerView.topAnchor, left: kitchencareCatgImageContainerView.leftAnchor, bottom: kitchencareCatgImageContainerView.bottomAnchor, right: kitchencareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: kitchencareCatgImageContainerView.frame.width , heightConstant: kitchencareCatgImageContainerView.frame.width)

        personalcareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
        personalcareCatgImageView.isUserInteractionEnabled = true
        healthcareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
        healthcareCatgImageView.isUserInteractionEnabled = true
        homecareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
        homecareCatgImageView.isUserInteractionEnabled = true
        kitchencareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
        kitchencareCatgImageView.isUserInteractionEnabled = true
    }

    func handleClickCtg(gestureRecognizer: UITapGestureRecognizer) {
        var CtgName: String? = nil
        var CtgId: String? = nil
        guard let tag = gestureRecognizer.view?.tag else {return}
        print(tag)
        switch tag {
        case 0:
            CtgName = "Personal Care"
            CtgId = "121"
        case 1:
            CtgName = "Health Care"
            CtgId = "122"
        case 2:
            CtgName = "Home Care"
            CtgId = "123"
        case 3:
            CtgName = "Kitchen Care"
            CtgId = "124"
        default:
            return
        }
        self.homeViewController?.handleCtgClick(ctgname: CtgName! ,ctgId: CtgId!)
    }  
}

Main Problem

first of all i am not binding the click of entire cell, I only want to bing click on ImageView inside cell. So for that i bind GestureRecognizer on image view like -

kitchencareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))

Click event works fine in terms of it's print the tag (added on image view) via print(tag) inside click function. But when call function self.homeViewcontroller.handleCtgClick(ctgname: CtgName! ,ctgId: CtgId!) (function exist in HomeViewcontroller class). it's not changing the view controller.

I also debug handleCtgClick function and i found i am getting the object of HomeViewcontroller is nil.

thanks in Advance

kishu mewara
  • 2,434
  • 11
  • 27

2 Answers2

1

The problem is that your "homeViewcontroller" variable is not set up correctly.

This code:

var homeViewcontroller = HomeViewController()

Creates an instance of a HomeViewController class. But this instance is in no way related to the screen you think it is. The screen that presented your current view is a completely different instance.

To be honest the way you are chaining everything is pretty weird and its not very intuitive. But if you want to fix your problem without altering your code too much you have to pass the REAL HomeViewController downwards.

So on the HomeViewController.swift you have this code:

let homeViewDatasource = HomeViewDatasource()
self.datasource = homeViewDatasource

Pass the "handleCtgClick" as a closure to it.

Something like

homeViewDataSource.presenterClosure = {(ctgname: String ,ctgId: String) in 
        let dummySettingViewController = UIViewController()
        dummySettingViewController.view.backgroundColor = UIColor.white
        dummySettingViewController.navigationItem.title = ctgname
        navigationController?.navigationBar.tintColor = UIColor.white
        navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
        navigationController?.pushViewController(dummySettingViewController, animated: true)
}

(Note that you have to create this closure variable declaration within your HomeViewDatasource.swift so that you can assign it from the HomeViewController.swift)

Then repeat this step again to pass it to the HomeMainCatgCell.swift

Then you will be able to call this function referencing the local closure variable you created.

ALTERNATIVELY:

You can just pass the reference to the HomeViewController itself. But if you decide to do this make sure you keep passing it as an "optional" variable with a "weak" property or you will create a retain loop.

Pochi
  • 13,198
  • 2
  • 58
  • 99
1

If you declare a global instance of your controller in your cell class this will create a Reference cycle and create a leak in your app. Your cell will have a strong reference to your controller and your controller will have a strong reference to your tableview which in turn keeps a strong reference to the cell, and second you might not be able to tell the index path of the clicked cell if you allow editing. You can try the following method to overcome both problems.

First of all create an enum in your cell class with cases as named according to your image views so your controller knows which image view is clicked and you can link your tags to it too. Something like:

enum CategoryViews:Int{
    case personal = 0,healthcare = 1,homecare = 2,kitchencare = 3
}

Then create a protocol in your cell class like:

protocol HomeMainCatgCellDelegate:class{
   func clicked(view:CategoryViews,forCell cell:HomeMainCatgCell)
}

Then create a weak variable in your cell class and call the protocol method in your action handler method

class HomeMainCatgCell: DatasourceCell {
     weak var delegate:HomeMainCatgCellDelegate?
     //Remove the variable var homeViewcontroller: HomeViewController? to avoid reference cycle and we don't need it anyway.
 ...
func handleClickCtg(gestureRecognizer: UITapGestureRecognizer) {
    guard let tag = gestureRecognizer.view?.tag else {return}
    print(tag)
    if let clickedImageView = CategoryViews(rawValue:tag){
        self.delegate?.clicked(view:clickedImageView,forCell:self)
    }
}  
}

Then in your view controller class you can you have to confirm to the HomeMainCatgCellDelegate protocol and set the cell's delegate to self.

class HomeViewController: DatasourceController, HomeMainCatgCellDelegate{
...
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//Dequeue your cell here
 cell.delegate = self
 ...
 return self
}
func clicked(view:CategoryViews,forCell cell:HomeMainCatgCell){
    //And you can handle your events accordingly here
    if let indexpath = self.collectionView.indexPath(for: cell){
    //Here you will get the indexpath of clicked cell and then you can put a switch condition to check which imageview is clicked
          switch view{
          case .personal:break
          case .healthcare:break
          case .homecare:break
          case .kitchencare:break
          }
    }
}
}

Hope this helps.

nishith Singh
  • 2,154
  • 1
  • 12
  • 22