Muhammad Kumail
Muhammad Kumail

Reputation: 1

Making a certain view tappable in CollectionView cell

I want to make certain view in cell of collectionView tappable.

Code Of ViewController

class ViewController: UIViewController {
    
    let collectionView:UICollectionView = {
        let view = UICollectionViewFlowLayout()
        view.scrollDirection = .horizontal
        let cv : UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: view)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.showsHorizontalScrollIndicator = false
        cv.isPagingEnabled = true
        return cv
    }()
    
    let arrayData:[Data] = [Data(vectorImages: "Group 3", maskImages: "", rectangleImage: "", headingLabel: "Document Reader", detailText: "Lorem ipsum dolor sit amet consectetur. Sit tortor malesuada egestas velit magna. Egestas congue nisi tincidunt urna.",view: View(background: .systemPurple, cornerRadius: 12),labelInView: "Get Started"),
                            Data(vectorImages: "Group 10", maskImages: "Mask group-2", rectangleImage: "Rectangle 144", headingLabel:"Read and View", detailText: "Lorem ipsum dolor sit amet consectetur. Sit tortor malesuada egestas velit magna. Egestas congue nisi tincidunt urna.",view:View(background: .systemPurple, cornerRadius: 12),labelInView: "Get Started"),
                            Data(vectorImages: "Group 11", maskImages: "Mask group-3", rectangleImage: "Rectangle 144", headingLabel: "Read and View", detailText: "Lorem ipsum dolor sit amet consectetur. Sit tortor malesuada egestas velit magna. Egestas congue nisi tincidunt urna.",view: View(background: .systemPurple, cornerRadius: 12),labelInView: "Get Started"),
                            Data(vectorImages: "Group 12", maskImages: "Mask group-4", rectangleImage: "Rectangle 144", headingLabel: "Read and View", detailText: "Lorem ipsum dolor sit amet consectetur. Sit tortor malesuada egestas velit magna. Egestas congue nisi tincidunt urna.",view: View(background: .systemPurple, cornerRadius: 12),labelInView: "Get Started")]
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemPurple
        
        setupView()
        setDelegatesAndDatasource()
        registerCell()
        
    }
    
    func setupView() {
        view.addSubview(collectionView)
        
        NSLayoutConstraint.activate([
            
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            
        ])
    }
    
    func setDelegatesAndDatasource() {
        collectionView.delegate = self
        collectionView.dataSource = self
    }
    
    func registerCell() {
        collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }
    
    
}

extension ViewController:

UICollectionViewDelegateFlowLayout,UICollectionViewDataSource {
 
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return arrayData.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)as! CollectionViewCell
        cell.customView.vectorImage.image = UIImage(named: "\(arrayData[indexPath.row].vectorImages)")
        cell.customView.maskImage.image = UIImage(named: "\(arrayData[indexPath.row].maskImages)")
        cell.customView.rectangleView.image = UIImage(named:"\(arrayData[indexPath.row].rectangleImage)")
        cell.customView.heading.text = arrayData[indexPath.row].headingLabel
        cell.customView.detailText.text = arrayData[indexPath.row].detailText
        cell.customView.view = arrayData[indexPath.row].view as! View
        cell.customView.labelInView.text = arrayData[indexPath.row].labelInView
        cell.viewTapped = { [weak self] aCell in
        
        let idx = self?.collectionView.indexPath(for: aCell)
        
        print("bottomView in cell at indexPath: \(idx ?? nil) was tapped")
    }
        return cell
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 393 , height: 852)
    }
    
}

In this cell of CollectioView cell.customView.view this View I want it to make it tappable

Code Of ScreenView that in this a view which I want to make tappable

import UIKit

class ScreenView: UIView {
    
    let rectangleView = ImageView(image: "")
    
    let maskImage = ImageView(image: "")
            
    let vectorImage = ImageView(image: "")
    
    let heading = Label(text: "Some Text", font: UIFont.systemFont(ofSize: 30, weight: .black), textColor: .black)
        
    
        let detailText:UILabel = {
            let lbl = UILabel()
            lbl.translatesAutoresizingMaskIntoConstraints = false
            lbl.font = .systemFont(ofSize: 14, weight: .light)
            lbl.numberOfLines = 0
            return lbl
        }()
    
    var view = View(background: .systemPurple, cornerRadius: 12)
    let labelInView = Label(text: "Get Started", font: UIFont.systemFont(ofSize: 14, weight: .black), textColor: .white)
    
    init()
        {
            super.init(frame: .zero)
            self.translatesAutoresizingMaskIntoConstraints = false
            setupViews()
        }
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
     func setupViews() {
            
            addSubview(rectangleView)
            rectangleView.addSubview(maskImage)
            rectangleView.addSubview(vectorImage)
            addSubview(heading)
            addSubview(detailText)
            addSubview(view)
            view.addSubview(labelInView)
            
            NSLayoutConstraint.activate([
                
                rectangleView.centerYAnchor.constraint(equalTo: centerYAnchor),
                rectangleView.centerXAnchor.constraint(equalTo: centerXAnchor),
                rectangleView.widthAnchor.constraint(equalToConstant: 345),
                rectangleView.heightAnchor.constraint(equalToConstant: 441),
                
                maskImage.topAnchor.constraint(equalTo: rectangleView.topAnchor),
                maskImage.widthAnchor.constraint(equalToConstant: 345),
                
                vectorImage.bottomAnchor.constraint(equalTo: rectangleView.bottomAnchor, constant: -10),
                vectorImage.centerXAnchor.constraint(equalTo: rectangleView.centerXAnchor),
                vectorImage.widthAnchor.constraint(equalToConstant: 313),
                vectorImage.heightAnchor.constraint(equalToConstant: 251),
                
                heading.topAnchor.constraint(equalTo: rectangleView.bottomAnchor, constant: 20),
                heading.centerXAnchor.constraint(equalTo: centerXAnchor),
                
                detailText.topAnchor.constraint(equalTo: heading.bottomAnchor, constant: 20),
                detailText.centerXAnchor.constraint(equalTo: centerXAnchor),
                detailText.widthAnchor.constraint(equalToConstant: 313),
                
                view.topAnchor.constraint(equalTo: detailText.bottomAnchor, constant: 74),
                view.leadingAnchor.constraint(equalTo: rectangleView.leadingAnchor ),
                view.widthAnchor.constraint(equalToConstant: 345),
                view.heightAnchor.constraint(equalToConstant: 52),
                
                labelInView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                labelInView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
            ])
        }
}

The Code of CustomCell in CollectionView import UIKit

class CollectionViewCell: UICollectionViewCell {
    
let customView = ScreenView()

var viewTapped: ((UICollectionViewCell) -> ())?

override init(frame: CGRect) {
    super.init(frame: frame)
    
    tapOnView()
    setupView()
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func tapOnView() {
    
    let tapView = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    customView.view.addGestureRecognizer(tapView)
}

func setupView() {
    
        addSubview(customView)

    NSLayoutConstraint.activate([
        customView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -140),
        customView.centerXAnchor.constraint(equalTo: centerXAnchor)

    ])
}

@objc func handleTap() {
    viewTapped?(self)
}

}

Upvotes: 0

Views: 155

Answers (1)

DonMag
DonMag

Reputation: 77682

First, a couple Tips for the future...

When posting a question here, make sure your code will run as-is. It is much easier to offer help if we can copy/paste/run without having to guess at what the missing code may be.

Also, start simple!!! Begin with the minimal code necessary to accomplish your goal.

In this case, let's use a cell class with a single subview.


View Controller class - pretty standard. We'll use an array of colors, which we'll use to set the background of the "custom view" in each cell.

class ViewController: UIViewController {

    let collectionView:UICollectionView = {
        let view = UICollectionViewFlowLayout()
        view.scrollDirection = .horizontal
        let cv : UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: view)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.showsHorizontalScrollIndicator = false
        cv.isPagingEnabled = true
        return cv
    }()
    
    let arrayData: [UIColor] = [
        .systemRed, .systemGreen, .systemBlue, .systemYellow,
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupView()
        setDelegatesAndDatasource()
        registerCell()
    }
    
    func setupView() {
        view.addSubview(collectionView)
        
        NSLayoutConstraint.activate([
            
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            
        ])
    }
    
    func setDelegatesAndDatasource() {
        collectionView.delegate = self
        collectionView.dataSource = self
    }
    
    func registerCell() {
        collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }
}

View Controller extension

extension ViewController: UICollectionViewDelegateFlowLayout,UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return arrayData.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)as! CollectionViewCell
        cell.customView.backgroundColor = arrayData[indexPath.item]
        return cell
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 393 , height: 852)
    }
}

Custom Cell class - with a single UIView as a subview, to which we'll add the UITapGestureRecognizer. When tapped, we'll print to the debug console, and we'll also "flash" the background color so we see that it responded to the tap.

class CollectionViewCell: UICollectionViewCell {

    let customView = UIView()
    
    @objc func handleTap(_ t: UITapGestureRecognizer) {
        
        print("Got Tap!")
        
        // let's "flash" the customView's background color
        //  so we have a visual indication it was tapped
        let c = customView.backgroundColor
        customView.backgroundColor = .yellow
        UIView.animate(withDuration: 0.75, animations: {
            self.customView.backgroundColor = c
        })
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        
        // create and add Tap Gesture Recognizer to customView
        let t = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        customView.addGestureRecognizer(t)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupView() {
        
        customView.layer.cornerRadius = 12
        customView.translatesAutoresizingMaskIntoConstraints = false
        
        // make sure we addSubview to contentView !!
        contentView.addSubview(customView)
        
        NSLayoutConstraint.activate([
            customView.widthAnchor.constraint(equalToConstant: 300.0),
            customView.heightAnchor.constraint(equalToConstant: 400.0),
            customView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -100),
            customView.centerXAnchor.constraint(equalTo: centerXAnchor),
        ])
    }
    
}

Edit - another quick example, showing a "bottom view" in the cell that can be tapped. On tap, it will inform the controller via a closure, allowing the controller to do something - in your case, push to a new controller.

class ViewController: UIViewController {

    let collectionView:UICollectionView = {
        let fl = UICollectionViewFlowLayout()
        fl.scrollDirection = .horizontal
        fl.minimumLineSpacing = 0
        fl.minimumInteritemSpacing = 0
        let cv : UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: fl)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.showsHorizontalScrollIndicator = false
        cv.isPagingEnabled = true
        return cv
    }()
    
    let arrayData: [String] = [
        "First", "Second", "Third", "Fourth",
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupView()
        setDelegatesAndDatasource()
        registerCell()
    }
    
    // so we can set the cell size when the collection view
    //  has been laid-out
    var currentCVWidth: CGFloat = 0
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        if currentCVWidth != collectionView.frame.width {
            currentCVWidth = collectionView.frame.width
            if let fl = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
                fl.itemSize = collectionView.frame.size
            }
        }
    }
    
    func setupView() {
        view.addSubview(collectionView)
        
        NSLayoutConstraint.activate([
            
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            
        ])
    }
    
    func setDelegatesAndDatasource() {
        collectionView.delegate = self
        collectionView.dataSource = self
    }
    
    func registerCell() {
        collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return arrayData.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)as! CollectionViewCell
        
        // set the cell UI elements
        cell.customView.headingLabel.text = arrayData[indexPath.item]
        
        // set the closure so we know when the "bottom view" in a cell was tapped
        cell.gotTapClosure = { [weak self] aCell in
            guard let self = self,
                  let idx = self.collectionView.indexPath(for: aCell)
            else {
                return
            }
            // here is where we would push to another controller on the navigation stack
            //  for now, let's just confirm the cell index
            print("bottomView in cell at indexPath: \(idx) was tapped")
        }
        
        return cell
    }
}


class CollectionViewCell: UICollectionViewCell {

    // closure
    var gotTapClosure: ((UICollectionViewCell) -> ())?
    
    let customView = ScreenView()
    
    @objc func handleTap(_ t: UITapGestureRecognizer) {
        
        print("Got Tap!")
        
        // call the closure to inform the controller that
        //  the "bottomView" was tapped
        gotTapClosure?(self)
        
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        
        // create and add Tap Gesture Recognizer to customView.bottomView
        let t = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        customView.bottomView.addGestureRecognizer(t)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupView() {
        
        customView.translatesAutoresizingMaskIntoConstraints = false
        
        // make sure we addSubview to contentView !!
        contentView.addSubview(customView)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            customView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            customView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            customView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            customView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])
    }
    
}

class ScreenView: UIView {
    
    let headingLabel: UILabel = {
        let v = UILabel()
        v.text = "Some Text"
        v.font = .systemFont(ofSize: 30, weight: .black)
        v.textColor = .black
        // so we can see the label frame
        v.backgroundColor = .yellow
        return v
    }()
    
    let bottomView: UIView = {
        let v = UIView()
        v.backgroundColor = .systemPurple
        v.layer.cornerRadius = 12
        return v
    }()
    
    let labelInView: UILabel = {
        let v = UILabel()
        v.text = "Get Started"
        v.font = .systemFont(ofSize: 14, weight: .black)
        v.textColor = .white
        // so we can see the label frame
        v.backgroundColor = .blue
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupViews() {
        
        [headingLabel, bottomView, labelInView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        
        addSubview(headingLabel)
        addSubview(bottomView)
        bottomView.addSubview(labelInView)
        
        NSLayoutConstraint.activate([
            
            headingLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20),
            headingLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            
            bottomView.widthAnchor.constraint(equalToConstant: 345),
            bottomView.heightAnchor.constraint(equalToConstant: 52),
            bottomView.centerXAnchor.constraint(equalTo: centerXAnchor),
            
            labelInView.centerYAnchor.constraint(equalTo: bottomView.centerYAnchor),
            labelInView.centerXAnchor.constraint(equalTo: bottomView.centerXAnchor),
            
            bottomView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12.0),
        ])
        
        headingLabel.backgroundColor = .yellow
        labelInView.backgroundColor = .blue
        
        // so we can see this view
        self.backgroundColor = UIColor(white: 0.90, alpha: 1.0)
        
    }
    
}

Looks like this when running:

enter image description here

When you scroll to the next cell, the Title label will be updated.

When you tap on the purple "bottomView" - but not the rest of the cell's UI elements - it will call the closure.

Upvotes: 1

Related Questions