简体   繁体   中英

Subviews of custom cell class not recognizing tap gesture

So I have a custom cell class sitting in my table view with stack views that contain UIImageView s. I want to add tap gesture recognition to the symbols, however the gesture isn't recognized for ONLY subviews of the custom cell (it works fine for the view itself). My cell code looks like:

class MonthlyViewCell: UITableViewCell {

  private let someSymbol: UIImageView = { 
    guard let image = UIImage(systemName: "j.circle.fill") else {return nil}
    
    let config = UIImage.SymbolConfiguration(pointSize: 27)
    let newImage = image.applyingSymbolConfiguration(config)
    
    let view = UIImageView(image: newImage)
    
    view.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
    view.contentMode = .scaleAspectFit
    view.tag = 1
    view.isUserInteractionEnabled = true
    
    return view
  }()
  .
  .
  // other symbols

  private var delegate: MonthlyViewCellDelegate?

  private var stackViewOne = UIStackView()
  private var stackViewTwo = UIStackView()
  private var stackViewThree = UIStackView()

  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
      super.init(style: style, reuseIdentifier: reuseIdentifier)
    
      commonInit()
  }

  required init?(coder: NSCoder) {
      super.init(coder: coder)
  }

  private func commonInit() {
      bounds = CGRect(x: 0, y: 0, width: 320, height: 120)
      frame = bounds
      print(frame)
    
      configureSubViews()
    
      addSubview(stackViewOne)
      addSubview(stackViewTwo)
      addSubview(stackViewThree)
    
      //addTargetToSymbols()
      activateConstraints()
    
      stackViewOne.frame = CGRect(x: 0, y: 0, width: frame.width-1, height: 15)
      stackViewTwo.frame = CGRect(x: 0, y: 0, width: frame.width-1, height: 15)
      stackViewThree.frame = CGRect(x: 0, y: 0, width: frame.width-1, height: 15)
    
      let gestureRecognizer = UITapGestureRecognizer()
      gestureRecognizer.addTarget(self, action: #selector(testTapped(sender:)))
    
      stackViewTwo.addGestureRecognizer(gestureRecognizer)
  }

  private func configureSubViews() {
      stackViewOne.translatesAutoresizingMaskIntoConstraints = false
      stackViewTwo.translatesAutoresizingMaskIntoConstraints = false
      stackViewThree.translatesAutoresizingMaskIntoConstraints = false
    
      stackViewOne.axis = .horizontal
      stackViewTwo.axis = .horizontal
      stackViewThree.axis = .horizontal
    
      stackViewOne.spacing = 60
      stackViewTwo.spacing = 60
      stackViewThree.spacing = 60
    
      let viewsOne = [januarySymbol, februarySymbol, marchSymbol, aprilSymbol]
      let viewsTwo = [maySymbol, juneSymbol, julySymbol, augustSymbol]
      let viewsThree = [septemberSymbol, octoberSymbol, novemberSymbol, decemberSymbol]
    
      let stackViews = [stackViewOne,stackViewTwo,stackViewThree]
    
      for one in viewsOne {
          stackViewOne.addArrangedSubview(one!)
      }
    
      for two in viewsTwo {
          stackViewTwo.addArrangedSubview(two!)
      }
    
      for three in viewsThree {
          stackViewThree.addArrangedSubview(three!)
      }
    
      /*for view in stackViews {
          bringSubviewToFront(view)
      }*/
  }

  private func activateConstraints() {
      let array: [NSLayoutConstraint] = [
        stackViewOne.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
        stackViewTwo.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
        stackViewThree.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
        
          stackViewOne.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 25),
          stackViewTwo.topAnchor.constraint(equalTo: stackViewOne.bottomAnchor, constant: 25),
          stackViewThree.topAnchor.constraint(equalTo: stackViewTwo.bottomAnchor, constant: 25)
    ]
    
      NSLayoutConstraint.activate(array)
  }

  private func addTargetToSymbols() {
      let gestureRecognizer = UITapGestureRecognizer()
    
      let symbols: [UIImageView?] = [januarySymbol, februarySymbol, marchSymbol, aprilSymbol, maySymbol, juneSymbol, julySymbol, augustSymbol, septemberSymbol, novemberSymbol, decemberSymbol]
    
      for symbol in symbols {
          guard let symbol = symbol else {return}
        
          gestureRecognizer.addTarget(self, action: #selector(monthSymbolTapped(sender:)))
          symbol.addGestureRecognizer(gestureRecognizer)
        
          bringSubviewToFront(symbol)
      }
  }

  @objc func testTapped(sender: UITapGestureRecognizer) {
      print("tapped")
  }

  func setDelegate(delegate: MonthlyViewCellDelegate) {
      self.delegate = delegate
  }

}

And the delegate methods are in the vc. I have tried setting the frames of each subview to be contained within the frame of their superviews. I should also mention that I set a breakpoint at the target methods to be called and none of them are triggered, so it seems that the methods aren't being called.

Also isUserInteractionEnabled is set to true for the view and its subviews. translatesAutoresizingMaskIntoConstraints is set to false for all subviews but NOT for the view itself, as this conflicts with the views auto constraints with the tableview it is contained in.

One possibility is it may have something to do with the constraints I set causing the stackviews respective frames to exceed the frame of its superview, however I set the stackviews's frames AFTER the constraints are activated, so this seems unlikely as well.

Finally I should mention that that the UIViewController has its view assigned to a .UITableView property which contains the cell which is initialized in cellForRowAt .

EDIT: As mentioned in the comments it is possible that using only one UITapGestureRecognizer instance in addTargetsToSymbols() for multiple subviews was the issue, so I made the following adjustment;

private func addTargetToSymbols() {
      let symbols: [UIImageView?] = [januarySymbol, februarySymbol, marchSymbol, aprilSymbol, maySymbol, juneSymbol, julySymbol, augustSymbol, septemberSymbol, novemberSymbol, decemberSymbol]

      for symbol in symbols {
          guard let symbol = symbol else {return}
          let gestureRecognizer = UITapGestureRecognizer()

          gestureRecognizer.addTarget(self, action: #selector(monthSymbolTapped(sender:)))
          symbol.addGestureRecognizer(gestureRecognizer)

          bringSubviewToFront(symbol)
      }
  }

moving the instance into the for-in loop, so that a new unique instance is used for each loop. This did not work. Additionally I imagine my attempt to test this on stackViewTwo individually would've worked if it was true.

As requested in the comments; here is the code for monthSymbolTapped()

@objc func monthSymbolTapped(sender: UIGestureRecognizer) {
        print("tst")
        guard let tag = sender.view?.tag else {return}
        
        switch tag {
        case 1:
            delegate?.pushToYearlyVisits(month: tag)

        // other cases

        default:
            return
        }
    }

NOTE: addTargetsToSymbols() was commented out while trying to see if simply adding gesture recognition to one of the stack views would yield a different result. Otherwise addTargetsToSymbols() has not been commented out on my local machine when troubleshooting this problem.

Here is a version of your custom cell class that works just fine with each image view having its own tap gesture.

I cleaned up the code a lot. I created a main stack view that contains the 3 stack views you had with each of those containing 4 of the image views.

I eliminated most of the attempts to set frames since constraints are being used.

I think your main issue was having incorrect constraints for the stack views which resulted in most of the image views being outside of the frame's cell which prevented the gestures from working. You should also be adding subviews to the cell's contentView , not the cell itself.

I also changed how the image views are created so I could get a fully working demo since your code was not complete. Obviously you can use your own code for all of the symbols if you prefer.

With the updated code below, tapping on any of the 12 images views results in the "tapped" message being printed.

You should also set your table view's rowHeight property to UITableView.automaticDimension to ensure the correct row height.

class MonthlyViewCell: UITableViewCell {
    private static func monthImage(_ letter: String) -> UIImageView {
        let image = UIImage(systemName: "\(letter).circle.fill")!

        let config = UIImage.SymbolConfiguration(pointSize: 27)
        let newImage = image.applyingSymbolConfiguration(config)

        let view = UIImageView(image: image)

        view.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
        view.contentMode = .scaleAspectFit
        view.isUserInteractionEnabled = true

        return view
    }

    private let months: [UIImageView] = ["j", "f", "m", "a", "m", "j", "j", "a", "s", "o", "n", "d"].map { MonthlyViewCell.monthImage($0) }

    private var mainStack = UIStackView()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    private func commonInit() {
        configureSubViews()

        contentView.addSubview(mainStack)

        addTargetToSymbols()
        activateConstraints()
    }

    private func configureSubViews() {
        mainStack.axis = .vertical
        mainStack.distribution = .equalCentering
        mainStack.alignment = .fill
        mainStack.spacing = 40
        mainStack.translatesAutoresizingMaskIntoConstraints = false

        for i in stride(from: 0, to: 12, by: 4) {
            let stack = UIStackView(arrangedSubviews: Array(months[i..<i+4]))
            stack.axis = .horizontal
            stack.distribution = .equalSpacing
            stack.alignment = .top
            stack.spacing = 60

            mainStack.addArrangedSubview(stack)
        }
    }

    private func activateConstraints() {
        NSLayoutConstraint.activate([
            mainStack.topAnchor.constraint(equalTo: contentView.topAnchor),
            mainStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            mainStack.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 25),
            mainStack.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -25),
        ])
    }

    private func addTargetToSymbols() {
        for symbol in months {
            let gestureRecognizer = UITapGestureRecognizer()
            gestureRecognizer.addTarget(self, action: #selector(monthSymbolTapped(sender:)))
            symbol.addGestureRecognizer(gestureRecognizer)
        }
    }

    @objc func monthSymbolTapped(sender: UITapGestureRecognizer) {
        if sender.state == .ended {
            print("tapped")
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM