0

I've got a UIButton and a couple of TextEdit's in a UIView. UIView itself is in a UIScrollView. Somehow UIButton can not be pressed in the structure I mentioned. Look at the following code:

    private lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.showsVerticalScrollIndicator = true
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.backgroundColor = .systemBrown
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()

    private lazy var contentView: UIView = {
        let contentView = UIView()
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentView.backgroundColor = .yellow
        contentView.isUserInteractionEnabled = true
        return contentView
    }()

    private lazy var logInButton: UIButton = {
        let button = UIButton(type: .system)        
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(loggedIn), for: .touchUpInside)
        return button
    }()

Now the following code show's how I added UIScrollView to my ViewController, then UIView as a subview to UIScrollView and then UIButton as a subview to UIView.

        view.addSubview(scrollView)
        scrollView.addSubview(contentView)

        let safeAreaGuide = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: safeAreaGuide.leadingAnchor),
            scrollView.widthAnchor.constraint(equalTo: safeAreaGuide.widthAnchor),
            scrollView.trailingAnchor.constraint(equalTo: safeAreaGuide.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: safeAreaGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: safeAreaGuide.bottomAnchor)
        ])
        
        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
        ])

        contentView.addSubview(logInButton)

        NSLayoutConstraint.activate([
            logInButton.topAnchor.constraint(equalTo: logInInputContainer.bottomAnchor, constant: 16),
            logInButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            logInButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            logInButton.heightAnchor.constraint(equalToConstant: 50)
        ])

Somehow UIButton is placed correctly on the screen yet can not be pressed. As a workaround I've tried adding UIButton directly to UIScrollView and it actually fixes the problem. It made me think that adding contentView.isUserInteractionEnabled = true would be a solution for the original task. It didn't help though.

What's the root of the issue and how to make objects placed in a UIView which is placed in UIScrollView interactable?

Philipp
  • 183
  • 1
  • 9
  • 1
    What is `logInInputContainer`? – Sweeper Jun 30 '23 at 01:50
  • While not part of the issue, remove the redundant `widthAnchor` constraints. if you set both the leading and trailing anchors, there's no reason to set the width. And what's the point of using a scrollview if you set the content to the same size as the scrollview? – HangarRash Jun 30 '23 at 05:24
  • @Sweeper `logInInputContainer` is a UIView containing two TextEdits: one for user login input and another for user password input. Button is aligned lower than this UIView. – Philipp Jul 01 '23 at 11:57

2 Answers2

2

when I run your code you are taking the wrong constraints, I just edit your constraints you can replace your code with the following and test if it works for you, in this piece of code, I just add the background colour to the button just to check the button's appearance.

private lazy var logInButton: UIButton = {
       let button = UIButton(type: .system)
       button.translatesAutoresizingMaskIntoConstraints = false
       button.backgroundColor = .systemRed
       button.addTarget(self, action: #selector(loggedIn), for: 
       .touchUpInside)
       return button
   }()

and the other piece of code that I change is

    view.addSubview(scrollView)
    scrollView.addSubview(contentView)
    contentView.addSubview(logInButton)
    
    let safeAreaGuide = view.safeAreaLayoutGuide
    NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: 
            safeAreaGuide.leadingAnchor),
            scrollView.widthAnchor.constraint(equalTo: 
            safeAreaGuide.widthAnchor),
            scrollView.trailingAnchor.constraint(equalTo: 
            safeAreaGuide.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: 
            safeAreaGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: 
            safeAreaGuide.bottomAnchor)
    ])
    NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: 
             scrollView.leadingAnchor),
            contentView.widthAnchor.constraint(equalTo: 
            scrollView.widthAnchor),
            contentView.trailingAnchor.constraint(equalTo: 
             scrollView.trailingAnchor),
            contentView.topAnchor.constraint(equalTo: 
             scrollView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: 
            scrollView.bottomAnchor),
    ])
    NSLayoutConstraint.activate([
            logInButton.topAnchor.constraint(equalTo: 
             contentView.topAnchor, constant: 16),
            logInButton.leadingAnchor.constraint(equalTo: 
             contentView.leadingAnchor, constant: 16),
            logInButton.trailingAnchor.constraint(equalTo: 
             contentView.trailingAnchor, constant: -16),
            logInButton.bottomAnchor.constraint(equalTo: 
             contentView.bottomAnchor, constant: -16),
            logInButton.heightAnchor.constraint(equalToConstant: 50),
    ])

and the reason you were unable to tap on the button is you are taking the wrong constraints and the button is going outside the content view so you are unable to tap. now the final output will look like:

  [ Please open the below link and check the ScreenShot of Simulator ][1]

  [1]: https://i.stack.imgur.com/9otvC.png
Rajni Bajaj
  • 174
  • 9
  • Why are you setting the button’s height and top/bottom? Why set the contentView’s leading/trailing and width? It’s redundant. – HangarRash Jun 30 '23 at 20:48
  • Content view doesn't have any height /width it will make its height and width only by calculating the dimensions of button inside it. and when they are in scroll view we have to set the constraints like scroll view can calculate the height and width of the full view. for that purpose we have to give all the contraints of button attaching with view and height to calculate the length of scroll view. – Rajni Bajaj Jul 01 '23 at 04:48
  • `contentView` does have height and width by virtue of assigning its leading/trailing and top/bottom constraints to be the same as the scroll view. What the OP is doing makes no sense because of this. Why bother having a scroll view if the only view you add to it is the same size as the scroll view. – HangarRash Jul 01 '23 at 05:22
  • because the above question has a requirement that he wants scroll view, content view and UIButton on their UI so I just correct the given Constraints, but yes without content view output can also be achieved by directly attaching the UIButton Constraints to scroll view. – Rajni Bajaj Jul 01 '23 at 05:52
  • @RajniBajaj I used your code and added `scrollView.isUserInteractionEnabled = true` and `contentView.isUserInteractionEnabled = true`. And it did help! Thank you so much for your solution! Would you also clarify why `scrollView.isUserInteractionEnabled = true` is necessary and `contentView.isUserInteractionEnabled = true` is not (I've tried deleting them in turns)? – Philipp Jul 01 '23 at 12:17
  • Maybe you are adding some more items in your content view and they can hide your button so in that case you need to make the interaction enable, I can clearify you after looking at your hierarchy if you can send me the whole UI. even my code was working without enabling interaction. – Rajni Bajaj Jul 01 '23 at 16:29
  • @RajniBajaj sure thing! I've got my project's latest version in the following [pull request](https://github.com/lord-anonymoose/social-media-app/pull/8) – Philipp Jul 02 '23 at 08:11
  • The default **isUserInteractionEnabled** property of each type of view is true, so if you do not mention this property it will be accepted as true, but if you mention it as **isUserInteractionEnabled = false** then you will not be able to interact with any element enclosed under that view. also if you are using this property and making the interaction of View false somewhere, in that case, you have to mention this property with true value. – Rajni Bajaj Jul 03 '23 at 05:17
  • in your case if you are making the `scrollView.isUserInteractionEnabled = true` which means you are able to interact with all the elements enclosed under this scrollView only if you have not intentionally disabled the interaction of any element under Scrollview. – Rajni Bajaj Jul 03 '23 at 05:17
-1

Basically the reason for the button to not be interactive is because the containerview it's being added is not completely setup, currently your container view width and height are zero.

If you simply set a height constraint for the containerview, the auto layout will do the work, if not, the autolayout doesn't know exactly the size of the containerview just by setting up its constraints according to the scrollview.

Try this:

NSLayoutConstraint.activate([
    contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
    contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
    contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
    contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
    contentView.heightAnchor.constraint(equalToConstant: 900), // add this for example
    contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])
paulRick
  • 546
  • 3
  • 9
  • There's no need to set the height. The contentView is already anchored at the top and bottom. That defines the height. In fact, the OP has no reason to set the width since both leading and trailing are already set. – HangarRash Jun 30 '23 at 05:22
  • @HangarRash all views inside a scrollView need to have an intrinsic content size, for example an imageView with it's image, or a collection with it's cells. In the OP case, a view doesn't have it, and the auto layout doesn't recognize it just by setting it's edges. – paulRick Jun 30 '23 at 19:36
  • No the views do not need an intrinsic content size. And it has nothing to do with a scroll view. When using constraints a view must ultimately have a position and a size. The size can come from either an intrinsic content size, the size can come from setting the width/height, or the size can come from setting the leading/trailing or top/bottom. It’s redundant to set both leading/trailing and width. – HangarRash Jun 30 '23 at 20:39
  • @HangarRash https://stackoverflow.com/a/39516433/6480152 https://developer.apple.com/library/archive/technotes/tn2154/_index.html#//apple_ref/doc/uid/DTS40013309-CH1-DontLinkElementID_2 – paulRick Jun 30 '23 at 22:09