Search code examples
iosswiftuiscrollviewswift5uicontrol

Swift: UIScrollView & UIControl - addTarget | addGestureRecognizer - Not working as expected


I am working with UIScrollView and UIStackView to scroll horizontally and select a UIControl element.

I have addTarget set on a UIControl element as follows inside a UIStackView:
i.e HighlightColorView is of type UIControl

lazy var colorsStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.isUserInteractionEnabled = true
        stackView.alignment = .center
        stackView.spacing = 12
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.isUserInteractionEnabled = true
        for color in colors {
            let colorView = HighlightColorView()
            colorView.backgroundColor = color
            colorView.addTarget(self, action: #selector(self.handleColorTap(_:)), for: .touchUpInside)
            stackView.addArrangedSubview(colorView)
        }
        return stackView
    }()

@objc func handleColorTap(_ sender: UIView) {
        highlightSelectionDelegate.didTapColor(colorView: sender, mainView: self)
        let viewBackgroundColor = sender.backgroundColor ?? .black
        print("🎯", viewBackgroundColor)
    }

The issue is that when you tap on a colorView which is of type UIControl the tap doesn't work as expected. There seems to be a freeze and it only works when the view is dismissed.
Basically you tap on a color and nothing happens then when you dismiss the view then the tap registers and starts working. It seems to be an issue with the scrollView but I am not sure.

Implementation of the scrollView:

lazy var scrollView: UIScrollView = {
        let scView = UIScrollView()
        scView.contentInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
        scView.showsHorizontalScrollIndicator = false
        scView.translatesAutoresizingMaskIntoConstraints = false
        scView.isUserInteractionEnabled = true
        scView.delaysContentTouches = false
        return scView
    }()

The use case it to be able to scroll on colors and tap them to highlight a text.


Solution

  • One thing I noticed is that you may be referring to UITapGestureRecognizer. if you are using UIGestureRecognizer by itself then it won't work.

         let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleColorTap))
         view.addGestureRecognizer(tapRecognizer)
    

    To answer the question you asked

    A UIGestureRecognizer is to be used with a single view. answer by @TomSwift.
    I think this applies in your case where you are using multiple views. set addTarget or addGestureRecognizer to only one view.

    If you are using multiple view such as in your case where you have a UIControl inside a UIStackView and the UIStackView inside a UIScrollView you need to make sure that addTarget or addGestureRecognizer is only set to one of the views only.

    Since you want the UIControl to respond to a tap you need to set addTarget only on this view and not on the UIScrollView and/or UIStackView .

    for color in colors {
        let colorView = HighlightColorView()
        colorView.backgroundColor = color
        colorView.addTarget(self, action: #selector(handleColorTap(_:)), for: .touchUpInside)
        colorsStackView.addArrangedSubview(colorView)
      }
    

    Note: You do not have to put the code inside a viewDidLoad function, It is fine where it is. Just remove additional addTarget or addGestureRecognizer from your views.