21

In the detailViewController of a UISplitView I have subviews added to a UIStackView inside a UIScrollView.

Just using system buttons without subviews or images, results in responsive buttons, but subviews seem to interfere.

Enabling touch is specifically coded. I have attempted to keep each view inside the containing view so there will be no overlap to invalidate receiving touch events, but not sure if this is done properly. Each subview contains a label and a custom button with an image. The subview is then added to the stackview, and the stackview to the scrollview.

Thanks for any help.

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(scrollView)

    // Constrain the scroll view within the detailView
    view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: .AlignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
    view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: .AlignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))


    stackView = UIStackView()

    stackView.frame = CGRectMake(0,0,view.frame.width, 1000)
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.axis = .Vertical
    scrollView.contentSize = CGSizeMake(400, 1000)
    scrollView.addSubview(stackView)

    // Constrain the stackView within the scrollView
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[stackView]|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["stackView": stackView]))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[stackView]", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["stackView": stackView]))


    let selectedGroup: Group = GroupArray[5]

    let descriptorsArray = selectedGroup.descriptorsArray

    for descriptor in descriptorsArray {
        // Create a subview for each descriptor

        let subView = UIView()
        subView.frame = CGRectMake(0 , 0, self.stackView.frame.width-10, 54)
        subView.backgroundColor = UIColor.yellowColor()

        subView.heightAnchor.constraintEqualToConstant(54.0).active = true
        // Create a label for Descriptor subview

        let label = UILabel(frame: CGRectMake(20, 0, 200, 50))
        label.text = descriptor.name
        label.font = UIFont.boldSystemFontOfSize(22.0)
        label.textAlignment = .Left
        label.textColor = UIColor.brownColor()
        label.backgroundColor = UIColor.greenColor()
        label.heightAnchor.constraintEqualToConstant(50.0).active = true
        subView.addSubview(label)

        // Create a button for Checkbox
        let btn = UIButton()
        btn.frame = CGRectMake(220, 0, 50, 50)
        btn.backgroundColor = UIColor.blueColor()
        btn.setImage(UIImage(named:"checked.png"), forState: UIControlState.Normal)

        btn.heightAnchor.constraintLessThanOrEqualToConstant(50.0)
        btn.widthAnchor.constraintLessThanOrEqualToConstant(50.0)

        btn.addTarget(self, action: "btnPressed:", forControlEvents: UIControlEvents.TouchUpInside)

        subView.addSubview(btn)
        btn.userInteractionEnabled = true
        subView.userInteractionEnabled = true
        stackView.userInteractionEnabled = true
        scrollView.userInteractionEnabled = true
        stackView.addArrangedSubview(subView)


    }
}

func btnPressed(sender: UIButton!) {

    print("btn Pressed")

}
Prags
  • 2,457
  • 2
  • 21
  • 38
Jolly
  • 263
  • 1
  • 2
  • 7

13 Answers13

44

I have had the exact same issue: a StackView which contains views. Each view contains a button and a label. The views are only containers for layout reasons.

In my case: I did not give the view a height constraint and it seems that it had a zero height (after changing the background color for debugging I did not see the view), but the button and label was visible (but unresponsive).

After I have added a height constraint to the layout view the (debug) background color was visible and the buttons responded as expected.

theguy
  • 1,232
  • 1
  • 9
  • 21
  • 1
    This saved me, thanks. I added a missing ``label.bottomAnchor.constraint(...)`` and everything started magically working again. – elprl Jul 27 '18 at 19:39
  • I had the similar issue. I didn't set width constraint to the wrapper view so it has zero width in runtime. – Krešimir Prcela Aug 10 '19 at 06:22
  • 1
    same issue with me, I was missing height constraint. Thanks – Arafin Russell Mar 08 '21 at 06:58
  • I had to manually add a height constraint to my 3 view in a vertical `UIStackView` in a `UIScrollView`, which worked, but this is not good because the contents of my custom `UIView`s might change. I am surprised the UI displayed but the `UITextfield`s and `UIButton`s weren't interact-able. – CyberMew Feb 16 '22 at 06:41
26

In my case, it was the aligment property of the StackView that was causing the problem.

When I set that property to Fill instead of Center the problem dissappeared.

jmartinalonso
  • 2,324
  • 1
  • 19
  • 22
12

I just ran into this issue myself. In my case, I had a UIStackView as a subview of my UIButton. I needed to set isUserInteractionEnabled = false on the stack view in order to get my button to work. This is probably a wise thing to set on any UIButton subview.

Kyle Cronin
  • 77,653
  • 43
  • 148
  • 164
  • 3
    I had the exact opposite problem, I had to set `isUserInteractionEnabled = true` on my UIStackView in order to get the button to register taps. This is a UIStackView I created programmatically. – sean_j_roberts Jan 20 '21 at 16:30
  • Oh wow.. This should be higher. This was cousing my problem also and was fixed with `= false` – MVZ Apr 22 '21 at 20:37
11

It seems to be that something is lying over your button and catching all the touch events.

1. Never turn on userInterectionEnabled if you don't need it

There is no reason why a normal view like subView should have set userInteractionEnabled set to true so put it to false.

2. Find the bug:

To find out witch view catches the event start your app on a device and navigate to the stackView. Now in Xcode, press the button "Debug View Hierarchy" (placed right over the console output)

enter image description here

After that you will see every view currently displayed on your device. What you do now is finding out wich views are above your button, and then in code turn their userInteractionEnabled value to false.

M. Kremer
  • 679
  • 6
  • 24
  • M Kremer thank you so much for this debugging suggestion. It is the correct textbook and functional approach so I marked it as correct, but I failed to find the overlap using this approach. Since I knew stacked buttons worked while stacked subviews didn't (for me), my solution was to stack buttons instead of subviews with buttons in them. I changed the background of each button depending on state. Instead of a label, I set the title. No views to overlap as all should be contained in the button. – Jolly Aug 28 '16 at 14:55
  • 1
    The default value for all views is YES. – pronebird Mar 15 '18 at 17:28
  • the first advice does not make any sense, every time you put the view on the storyboard the user interaction is always set. – JBarros35 Jul 07 '20 at 14:45
  • 1
    your "debug view hierarchy" shows that everything is ok, "user interaction" is enabled for button and all its parent views. The button still doesn't work. Figured out that it is simply because it is inside stackview with alignment "leading". Changed alignment to "fill" and now everything works (even without of changes in hierarchy). – Vyachaslav Gerchicov Oct 07 '21 at 06:31
  • @VyachaslavGerchicov thanks for the note, actually what you said is the same problem that i have – Amr Angry Apr 06 '22 at 20:14
3

following theGuy's answer, I used the sherlock app and I immediately found the problem:

enter image description here

abbood
  • 23,101
  • 16
  • 132
  • 246
3

In my case, I had set a custom view subclass in loadView. That subclass had isUserInteractionEnabled = false which translated down to all the sub views including my stackView and all its views and buttons.

Justin Vallely
  • 5,932
  • 3
  • 30
  • 44
2

I noticed a bug (I guess) in XCode 12. When my IBOutlet func (on swift) is empty, and I put a breakpoint inside the body of the empty func, or on the func declaration itself, then touch the button - XCode doesn't stop on my breakpoint (tested on Simulator).

1

In my case, it was just Xcode's bug. I've just closed and reopened it. It just worked. My current Xcode version is 11.4.1.

miletliyusuf
  • 982
  • 1
  • 10
  • 12
1

For me the problem was that I was instantiating views for Stack View from storyboard, and my ViewController was not added to a views structure, but UIView did. This why button actions from UIViewController subclass didn't work. So the answer of Mostafa Aghajani from here https://stackoverflow.com/a/42179529/344386 helped me. If I do something like this:

    let testViewController = storyboard.instantiateViewController(withIdentifier: "TestViewController") as! TestViewController
    
    view.addSubview(testViewController.view) 
    // OR FOR STACK VIEW
    stackView.addArrangedSubview(testViewController.view)

Normally views structure when I check "XCode / Debug / View debugging / Capture view hierarchy" looks like UIViewController / UIView. But not in this case. The key was to do like this:

    let testViewController = storyboard.instantiateViewController(withIdentifier: "TestViewController") as! TestViewController
    self.addChild(testViewController) // this is the key

    view.addSubview(testViewController.view) 
    // OR FOR STACK VIEW
    stackView.addArrangedSubview(testViewController.view)

Then UIViewController was added in a views hierarchy and my UIButton inside of testViewController started to work. That's all is a kind of shamanism, may be I'm just doing something wrong.

nickeyzzz
  • 355
  • 4
  • 13
1

Make sure you add your view with addArrangedSubview.

I had this issue because I added the button to the UIStackView with addSubview.

Oliver Pearmain
  • 19,885
  • 13
  • 86
  • 90
0

I have had come cross the same issue. The reason is that i set button.alpha = 0, while setting the button.layer.opacity = 1.0. In this case, the button will not response to actions although it's visible. So setting button.alpha = 1.0 is the answer. May that help!

responser
  • 432
  • 4
  • 7
0

I had this happen in my stackview because the button went out of bounds of the stackview - but this was not obvious in the storyboard view. To confirm this as the issue, select the stackview and turn on "clips to bounds" in the attribute inspector to see if your button is going out of the stackview. You may have to check each stackview in the parent hierarchy all the way to the topmost stackview. Check this on all the phone sizes too - what might work on the iPhone 11 may clip on the iPhone4s.

0

Well, it is so obvious that you might not feel checking it necessary. I've spent maybe an hour trying all the other answers, and I want to save your time.

Check the code of your custom cell class, especially awakeFromNib method, to see if userInteractionEnabled of the cell is set to false somewhere. Set it to true. Voila! :]

Luke
  • 965
  • 8
  • 21