UIButtons do not respond to touched on ViewController with UIScrollView

34 Views Asked by At

I am working on the ViewController with UIScrollView embedded in. I've got: view -> scrollView -> contentView (UIView) -> backgroundImageView -> SettingsItemsView (UIView) -> UIButtons. So after adding targets on UIButtons inside SettingsItemsView, none of them respond to touches. There is most likely a conflict because of UIScrollView, but I have not managed to resolve it. Thanks in advance! The code is attached

Controller

final class SettingsViewController: ViewController {
    
    var viewModel: SettingsViewModel!
    
    private let scrollView: UIScrollView = {
        let sv = UIScrollView()
        sv.alwaysBounceVertical = true
        sv.delaysContentTouches = false
        return sv
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        return view
    }()
    
    private let backgroundImageView: UIImageView = {
        let iv = UIImageView()
        iv.image = Asset.Images.settingsBackground.image
        iv.contentMode = .scaleToFill
        iv.isUserInteractionEnabled = true
        iv.clipsToBounds = true
        return iv
    }()
    
    private let settingsItemsView: SettingsItemsView = {
        let view = SettingsItemsView()
        return view
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let titleLabel = UILabel()
        titleLabel.text = "Settings"
        if let font = FontFamily.JosefinSans.semiBold.font(size: 24) {
            titleLabel.font = font
        }
        titleLabel.textColor = Asset.Colors.white.color
        navigationItem.titleView = titleLabel
    }
    
    override func arrangeSubviews() {
        super.arrangeSubviews()
        view.addSubview(backgroundImageView)
        backgroundImageView.addSubview(scrollView)
        scrollView.addSubview(contentView)
        contentView.addSubview(settingsItemsView)
    }
    
    override func setupViewConstraints() {
        super.setupViewConstraints()
        
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        contentView.translatesAutoresizingMaskIntoConstraints = false
        backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            
            backgroundImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            backgroundImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            backgroundImageView.topAnchor.constraint(equalTo: view.topAnchor),
            backgroundImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        settingsItemsView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            settingsItemsView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            settingsItemsView.widthAnchor.constraint(equalToConstant: 343),
            settingsItemsView.heightAnchor.constraint(equalToConstant: 350),
            settingsItemsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 40)
        ])
    }
}

SettingsView

final class SettingsItemsView: UIView {
    // Temporary image
    private var tempImage = UIImage(systemName: "apple.logo")

    private lazy var items: [SettingsItem] = [
        SettingsItem(option: SettingsOptions.privacy),
        SettingsItem(option: SettingsOptions.termsOfUse),
        SettingsItem(option: SettingsOptions.feedback),
        SettingsItem(option: SettingsOptions.share),
        SettingsItem(option: SettingsOptions.rate),
    ]

    private func configureOptions() -> [SettingsItemButton] {
        var options: [SettingsItemButton] = []

        for item in items {
            let button = SettingsItemButton(image: item.option.image!, name: item.option.name, option: item.option)
            button.clipsToBounds = true
            button.backgroundColor = .clear
            button.addTarget(self, action: #selector(testTapped(_:)), for: .touchUpInside)
            button.isUserInteractionEnabled = true
            options.append(button)
        }
        return options
    }

    @objc func testTapped(_ sender: SettingsItemButton) {
        print("Tapped: \(sender)")
    }

    private let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.distribution = .equalCentering
        stack.spacing = 0
        return stack
    }()

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

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

    private func setupView() {
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        setupStyle()
        addSubviews()
    }

    private func addSubviews() {
        configureOptions().forEach { stackView.addArrangedSubview($0) }
    }

    private func setupStyle() {
        backgroundColor = Asset.Colors.tabBarBackground.color
        layer.cornerRadius = 22
        clipsToBounds = true
    }
}

Button

final class SettingsItemButton: UIButton {
    
    var option: SettingsOptions!

    private let itemImage: UIImageView = {
        let btn = UIImageView()
        btn.tintColor = Asset.Colors.white.color
        btn.contentMode = .scaleAspectFill
        btn.backgroundColor = .blue
        return btn
    }()
    
    private let itemCheckMark: UIImageView = {
        let btn = UIImageView()
        btn.image = Asset.Images.arrowRight.image
        btn.tintColor = Asset.Colors.white.color
        btn.backgroundColor = .blue
        return btn
    }()
    
    private let itemName: UILabel = {
        let label = UILabel()
        if let customFont = FontFamily.Outfit.medium.font(size: 17) {
            label.font = customFont
        }
        label.textColor = Asset.Colors.white.color
        label.backgroundColor = .blue
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    convenience init(image: UIImage, name: String, option: SettingsOptions) {
        self.init(frame: .zero)
        configure(image: image, name: name, option: option)
    }
    
    func configure(image: UIImage, name: String, option: SettingsOptions) {
        self.option = option
        itemImage.image = image
        itemName.text = name
    }
    
    func setupConstraints() {
        itemImage.translatesAutoresizingMaskIntoConstraints = false
        itemName.translatesAutoresizingMaskIntoConstraints = false
        itemCheckMark.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            itemImage.centerYAnchor.constraint(equalTo: centerYAnchor),
            itemImage.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 24),
            itemImage.heightAnchor.constraint(equalToConstant: 24),
            itemImage.widthAnchor.constraint(equalToConstant: 24),
            
            itemName.centerYAnchor.constraint(equalTo: centerYAnchor),
            itemName.leadingAnchor.constraint(equalTo: itemImage.trailingAnchor, constant: 16),
            
            itemCheckMark.centerYAnchor.constraint(equalTo: centerYAnchor),
            itemCheckMark.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            itemCheckMark.heightAnchor.constraint(equalToConstant: 32),
            itemCheckMark.widthAnchor.constraint(equalToConstant: 24)
        ])
    }
    
    func setupSubviews() {
        addSubview(itemImage)
        addSubview(itemName)
        addSubview(itemCheckMark)
    }
}

I tried using UICollectionView instead of UIStackView. But no button on the cell, neither the cell itself responded to touches as well. I also tried settings isUserInteractionEnabled property on different layers to false, in order to prevent some conflicts.

1

There are 1 best solutions below

0
DonMag On

After digging through your code and making some edits to get it to run...

Your buttons do not respond to taps because the stack view holding them extends outside the bounds of its superview.

In your SettingsViewController, your contentView has no height. If you set .clipsToBounds = true:

private let contentView: UIView = {
    let view = UIView()
    // set clips to bounds true
    view.clipsToBounds = true
    return view
}()

and run the app, you will no longer even see the buttons.

If you add this line:

    NSLayoutConstraint.activate([
        settingsItemsView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
        settingsItemsView.widthAnchor.constraint(equalToConstant: 343),
        settingsItemsView.heightAnchor.constraint(equalToConstant: 350),
        settingsItemsView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 40),
        
        // add this line
        settingsItemsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20.0),
        
    ])

your contentView will now have a height ... and you'll see the buttons again ... and you can now tap them.