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.
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, yourcontentViewhas no height. If you set.clipsToBounds = true:and run the app, you will no longer even see the buttons.
If you add this line:
your
contentViewwill now have a height ... and you'll see the buttons again ... and you can now tap them.