John Doah
John Doah

Reputation: 1999

Getting the parent of parent view?

I use a CollectionView in my app, which of course has a CollectionViewCell, In the CollectionViewCell, I have a StackView, which has buttons in it. When tapping one of the buttons, I call a method which needs to get the cell that the button belongs to. I tried getting the Sender(the button) and then use Sender.superview, but it gives me the StackView, and Sender.superview.superview didn't work. How can I get the current cell that the button is in?

enter image description here

Marked in Red, is the StackView where the button is, and in Pink is the button I click.

Upvotes: 0

Views: 250

Answers (1)

Jay Lee
Jay Lee

Reputation: 1902

You need a more scalable approach. I would add a closure var action: (() -> Void)? to your custom CollectionViewCell, and in collectionView(_:cellForItemAt:), assign the action. To avoid memory retain cycle, don't forget to unowned self or weak self. In your CollectionViewCell, make an action function that would run action?().

So it would be like:

class CollectionViewCell: UICollectionViewCell {
  var action: (() -> Void)?

  @objc func buttonWasTouched(_ sender: UIButton) {
    action?()
  }

  ...
    button.addTarget(self, action: #selector(buttonWasTouched), for: .touchUpInside)
  ...
}

and in your collectionView(_:cellForItemAt:):

  cell.action = { [unowned self] in /* Do something with self and the cell. */ }

====

EDIT:

The following example demonstrates a minimal example of using the above approach:

class CollectionViewCell: UICollectionViewCell {
  var action: (() -> Void)?
  var votes = 0 {
    didSet { button.setTitle("\(votes)", for: .normal) }
  }

  private lazy var button: UIButton = {
    let button = UIButton()
    // Set translatesAutoresizingMaskIntoConstraints to programmatically setup layout
    button.translatesAutoresizingMaskIntoConstraints = false

    button.setTitle("\(votes)", for: .normal)
    button.setTitleColor(.black, for: .normal)

    button.addTarget(self, action: #selector(buttonWasTouched), for: .touchUpInside)

    return button
  }()

  @objc private func buttonWasTouched(_ sender: UIButton) {
    action?()
  }

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

    contentView.addSubview(button)
    // Programmatially setting layout constraints
    NSLayoutConstraint.activate([
      button.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
      button.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
      button.topAnchor.constraint(equalTo: contentView.topAnchor),
      button.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
    ])
  }

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


class CollectionViewController: UICollectionViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.backgroundColor = .white
    // Programatically register CollectionViewCell
    collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cell")
  }

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

  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
    cell.action = { cell.votes += 1 }
    return cell
  }
}

If you run the code, each cells will have a button, which increments its number when touched.

Upvotes: 2

Related Questions