0

I am trying to create a custom UIView/Scrollview named MyScrollView that contains a few labels (UILabel), and these labels receive tap gestures/events in order to respond to user's selections See the below screen shot for the custom UIView.

In order to make the tap event work on the UILabels, I make sure they all have userIteractionEnabled = true and I created a delegate as below:

protocol MyScrollViewDelegate {
    func labelClicked(recognizer: UITapGestureRecognizer)
}

The custom UIView is being used in ScrollViewController that I created, this ScrollViewController implements the delegate method as well:

import UIKit
import Neon
class ScrollViewController: UIViewController, MyScrollViewDelegate {

var curQuestion: IPQuestion?
var type: QuestionViewType?
var lastClickedLabelTag: Int = 0 //

init(type: QuestionViewType, question: IPQuestion) {
    super.init(nibName: nil, bundle: nil)
    self.curQuestion = question
    self.type = type
}

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

override func viewDidLoad() {
    super.viewDidLoad()
    self.automaticallyAdjustsScrollViewInsets = false
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

override func loadView() {
    view = MyScrollView(delegate: self, q: curQuestion!)
    view.userInteractionEnabled = true
}
}

// implementations for MyScrollViewDelegate
extension ScrollViewController {

func labelTitleArray() -> [String]? {
    print("labelTitleArray called in implemented delegate")

    return ["Comments", "Answers"]
}

func labelClicked(recognizer: UITapGestureRecognizer) {
    print("labelClicked called in implemented delegate")
    let controller = parentViewController as? ParentViewController
    controller?.labelClicked(recognizer)
    lastClickedLabelTag = recognizer.view!.tag
    }
}

// MARK: - handle parent's ViewController event
extension QuestionDetailViewController {
    func updateActiveLabelsColor(index: Int) {
        print("updating active labels color: \(index)")
        if let view = view as? MyScrollView {
            for label in (view.titleScroll.subviews[0].subviews as? [UILabel])! {
                if label.tag == index {
                    label.transform = CGAffineTransformMakeScale(1.1,1.1)
                    label.textColor = UIColor.purpleColor()
                }
                else {
                    label.transform = CGAffineTransformMakeScale(1,1)
                    label.textColor = UIColor.blackColor()
                }
            }
        }
    }
}

This above ScrollViewController is added, as a child view controller to the parent view controller, and positioned to the top part of the parent's view:

override func viewDidLoad() {
        super.viewDidLoad()
        self.automaticallyAdjustsScrollViewInsets = false
        self.view.backgroundColor = UIColor.whiteColor()
        addChildViewController(scrollViewController) // added as a child view controller here
        view.addSubview(scrollViewController.view) // here .view is MyScrollView
        scrollViewController.view.userInteractionEnabled = true
        scrollViewController.view.anchorToEdge(.Top, padding: 0, width: view.frame.size.width, height: 100)
    }

The app can load everything up in the view, but the tap gesture/events are not passed down to the labels in the custom MyScrollView. For this, I did some google search and have read Event Delivery: Responder Chain on Apple Developer website and did a hit test as well. The hitTest function below can be triggered in the MyScrollView:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        print("hit test started, point: \(point), event: \(event)")
        return self
    }

My observations with the hitTest is that the touchesBegan() and touchesEnded() methods are triggered in the view only when the hitTest function is there. Without hitTest, both functions do not get called with taps.

but no luck getting the UILabel to respond to Tap Gestures. So I am reaching out to experts on SO here. Thanks for helping!

TonyW
  • 18,375
  • 42
  • 110
  • 183
  • 1
    Within the relevant touch methods on the parent, try forwarding them to the next responder. For example, within `touchesBegan` within the parent, try calling `self.nextResponder.touchesEnded`. – Matthew Seaman May 03 '16 at 15:32
  • Have you set the property userInteractionEnabled = true for your labels? The default value is false for UILabel, so you should change it if you want to receive gestures. – Vladimir K May 03 '16 at 15:33
  • @VladimirK, yes, I did set the `userIteractionEnabled` to true on all the labels. Thanks – TonyW May 03 '16 at 15:35
  • The `self.nextResponder()?.touchesEnded()` seems to send the event up, not downward. The problem persists. Thanks – TonyW May 03 '16 at 15:42
  • Ok, have you implemented delegate method for gesture recognizer that you added to label: gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:? – Vladimir K May 03 '16 at 15:44
  • @VladimirK, yes, that has been implemented in the custom UIView, but does not work :( – TonyW May 03 '16 at 15:50

3 Answers3

4

I think I found out the reason why the UILabel did not respond to tapping after much struggle: the .addGestureRecognizer() method to the label was run in the init() method of my custom UIView component, which is wrong, because the view/label may not have been rendered yet. Instead, I moved that code to the lifecycle method layoutSubviews(), and everything started to work well:

var lastLabel: UILabel? = nil

        for i in 0..<scrollTitleArr.count {
            let label = UILabel()
            label.text = scrollTitleArr[i] ?? "nothing"
            print("label: \(label.text)")
            label.font = UIFont(name: "System", size: 15)
            label.textColor = (i == 0) ? MaterialColor.grey.lighten2 : MaterialColor.grey.darken2
            label.transform = (i == 0) ? CGAffineTransformMakeScale(1.1, 1.1) : CGAffineTransformMakeScale(0.9, 0.9)
            label.sizeToFit()
            label.tag = i // for tracking the label by tag number
            label.userInteractionEnabled = true
            label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.labelClicked(_:))))

            titleContainer.addSubview(label)

            if lastLabel == nil {
                label.anchorInCorner(.TopLeft, xPad: 0, yPad: 0, width: 85, height: 40)
      //         label.anchorToEdge(.Left, padding: 2, width: 85, height: 40)
            } else {
                label.align(.ToTheRightMatchingTop, relativeTo: lastLabel!, padding: labelHorizontalGap, width: 85, height: 40)
            }

            lastLabel = label

        }

In addition, I don't need to implement any of the UIGestureRecognizer delegate methods and I don't need to make the container view or the scroll view userInteractionEnabled. More importantly, when embedding the custom UIView to a superview, I configured its size and set clipsToBounds = true.

I guess I should have read more UIView documentation on the Apple Developer website. Hope this will help someone like me in the future! Thanks to all!

TonyW
  • 18,375
  • 42
  • 110
  • 183
0

You have to set the property userInteractionEnabled = YES.

FullMetalFist
  • 208
  • 1
  • 2
  • 14
vjking9
  • 39
  • 2
0

For some reason, my simulator was frozen or something when the tap gesture recognizer wasn't working. So, when I restarted the app, then it all worked again. I don't know if this applies here, but that was the fix for me.

Daniel Jones
  • 1,032
  • 10
  • 18