Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: UIStackView of UIControls with selector method that doesn't fire

Introduction

I'm creating an app which uses a custom view in which I have a UIStackView to sort out 5 UIControls. When a user taps one of the UIControls an underscore line gets animated, sliding under the tapped UIControl.

However, for some reason the method/selector for these UIControls no longer gets called. I believe this has to do with that I updated my Mac to the macOS (and Xcode) update released this week (wk.44). (updated from swift 4.2 to swift 4.2.1). Before the updated this animation and selector worked perfectly. But I'm not sure. And I'm now completely stuck on what I'm doing wrong.

Context

  • I created a playground and scaled down everything as much as I could and the issue persists.

  • I have tried to define the UIStackView in the global scope of my SetupView class but it doesn't change anything. So I believe it is not an issue of the stackView or its subviews being deallocated?

  • Below I've provided my UIControl subclass and my SetupView (UIView subclass) that I use. I've created a playground so you may copy paste in Xcode playground to test if you want.

Question

  • Why doesn't the method goalViewControlTapped(_ sender: SetupViewControl) get called?

Code

import UIKit
import PlaygroundSupport

class SetupViewControl: UIControl {

    let titleLabel : UILabel = {
        let lbl = UILabel()
        lbl.font = UIFont(name: "Futura", size: 14)
        lbl.textColor = .white
        lbl.backgroundColor = .clear
        lbl.textAlignment = .center
        lbl.translatesAutoresizingMaskIntoConstraints = false
        return lbl
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupLabel()
        layer.cornerRadius = 5
    }

    fileprivate func setupLabel() {
        addSubview(titleLabel)
        titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5).isActive = true
        titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
        titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

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

    override var isHighlighted: Bool {
        didSet {
            UIView.animate(withDuration: 0.12) {
                self.backgroundColor = self.isHighlighted ? UIColor.lightGray : UIColor.clear
            }
        }
    }
}

class SetupView: UIView {

    let dataModel : [String] = ["2 weeks", "1 month", "2 months", "6 months", "1 year"]
    var selectionLineCenterX : NSLayoutConstraint!

    let selectionLine = UIView()
    let labelZero = SetupViewControl()
    let labelOne = SetupViewControl()
    let labelTwo = SetupViewControl()
    let labelThree = SetupViewControl()
    let labelFour = SetupViewControl()
    let labelFive = SetupViewControl()

    lazy var controlArray = [self.labelZero, self.labelOne, self.labelTwo, self.labelThree, self.labelFour, self.labelFive]

    init(frame: CGRect, color: UIColor) {
        super.init(frame: frame)
        self.backgroundColor = color
        setupView()
    }

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

    fileprivate func setupView() {
        layer.cornerRadius = 0
        layer.borderColor = UIColor.black.cgColor
        layer.borderWidth = 1
        setupLabelText()
        setupControlsInStackView()
    }

    fileprivate func setupLabelText() {
        for num in 0...(dataModel.count - 1) {
            controlArray[num].titleLabel.text = dataModel[num]
        }
    }

    // let stackView = UIStackView(frame: .zero) I have tried to declare the stackView here but it doesn't fix my issue.
    func setupControlsInStackView() {
        var stackViewArray = [SetupViewControl]()
        for num in 0...(dataModel.count - 1) {
            controlArray[num].isUserInteractionEnabled = true
            controlArray[num].addTarget(self, action: #selector(goalViewControlTapped(_:)), for: .touchUpInside)
            stackViewArray.append(controlArray[num])
        }
        let stackView = UIStackView(arrangedSubviews: stackViewArray)
        stackView.alignment = .fill
        stackView.distribution = .fillEqually
        stackView.axis = .horizontal
        stackView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(stackView)
        stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8).isActive = true
        stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8).isActive = true
        stackView.topAnchor.constraint(equalTo: topAnchor, constant: 15).isActive = true

        addSubview(selectionLine)
        selectionLine.backgroundColor = .white
        selectionLine.translatesAutoresizingMaskIntoConstraints = false
        selectionLine.heightAnchor.constraint(equalToConstant: 1).isActive = true
        selectionLine.topAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
        selectionLine.widthAnchor.constraint(equalToConstant: 50).isActive = true
        selectionLineCenterX = selectionLine.centerXAnchor.constraint(equalTo: leadingAnchor, constant: -100)
        selectionLineCenterX.isActive = true
    }

    @objc fileprivate func goalViewControlTapped(_ sender: SetupViewControl) {
        print("This is not getting printed!!!")
        selectionLineCenterX.isActive = false
        selectionLineCenterX = selectionLine.centerXAnchor.constraint(equalTo: sender.centerXAnchor)
        selectionLineCenterX.isActive = true
        UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: .curveEaseIn, animations: {
            self.layoutIfNeeded()
        }, completion: nil)
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        let testView = SetupView(frame: .zero, color: UIColor.blue)
        view.addSubview(testView)
        testView.translatesAutoresizingMaskIntoConstraints = false
        testView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        testView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        testView.heightAnchor.constraint(equalToConstant: 100).isActive = true
        testView.widthAnchor.constraint(equalToConstant: 365).isActive = true
    }
}
// For live view in playground
let vc = ViewController()
vc.preferredContentSize = CGSize(width: 375, height: 812)
PlaygroundPage.current.liveView = vc

Thanks for reading my question.

like image 970
theoadahl Avatar asked Nov 02 '18 22:11

theoadahl


1 Answers

Does your UIStackView show as having an ambiguous layout when you open the view debugger? If so, that may be causing the internal views to not receive the touch events.

You can provide UIStackView with either:

x and y constraints only

or

x, y, width and height.

In the above case the height constraint is missing.

like image 62
nkh Avatar answered Nov 19 '22 11:11

nkh