1

Setup

I implemented an inputAcessoryView in a UIViewController via the following:

override var inputAccessoryView: UIView? {

    get {

        return bottomBarView
    }
}
override var canBecomeFirstResponder: Bool {

    return true
}

fileprivate lazy var bottomBarView: UIView = {

    let view = UIView()
    let separatorLineView = UIView()

    view.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height / 9)
    view.backgroundColor = .white

    /...

    separatorLineView.backgroundColor = UIColor(r: 220, g: 220, b: 220, a: 1)
    separatorLineView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(separatorLineView)
    separatorLineView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
    separatorLineView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    separatorLineView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
    separatorLineView.heightAnchor.constraint(equalToConstant: 1).isActive = true

    view.addSubview(self.photoButton)
    self.photoButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    self.photoButton.centerYAnchor.constraint(equalTo: separatorLineView.centerYAnchor, constant: -12).isActive = true
    self.photoButton.widthAnchor.constraint(equalToConstant: view.frame.width / 4.5).isActive = true
    self.photoButton.heightAnchor.constraint(equalToConstant: view.frame.width / 4.5).isActive = true

    return view
}()

photoButton implements an action on .touchUpInside.

The issue

The button is only clickable in the part included in inputAccessoryView's frame: if I click on it on the part that's outside, nothing happens.

Is there any quick way to make it work like so ?

I have tried moving the photoButton out of bottomBarView and adding it to the view instead however:

  • I need photoButton to be "anchored" to the inputAccessoryView, but it seems inputAccessoryView doesn't have anchor properties from within the view ? (I can only access their frame properties via bang unwrapping)
  • When I do so, photoButton is partly hidden behind inputAccessoryView and I can't bring it forward with view.bringSubview(ToFront:)

Thanks in advance.

Herakleis
  • 513
  • 5
  • 21

2 Answers2

1

The issue you have is that the inputAccessoryView (bottomBarView for you) has no height. All of the subviews you're adding are therefore outside of its bounds. If you were to set clipsToBounds = true, you would see that everything disappears.

The issue with this, as you're seeing, is that those subviews aren't getting touch events since they're outside the bounds of their superview. From Apple's docs:

If a touch location is outside of a view’s bounds, the hitTest:withEvent: method ignores that view and all of its subviews. As a result, when a view’s clipsToBounds property is NO, subviews outside of that view’s bounds are not returned even if they happen to contain the touch.

Understanding Event Handling, Responders, and the Responder Chain

The solution is to get the inputAccessoryView to be the correct height. self.view.frame.height / 9 is 0 in your example, since the frame has not been set by the time you're using it.

Instead, create a UIView subclass, with the following important parts:

class BottomBarView: UIView {

    init(frame: CGRect) {
        super.init(frame: frame)

        autoresizingMask = .flexibleHeight

        let someView = UIView()
        addSubview(someView)
        someView.translatesAutoresizingMaskIntoConstraints = false
        someView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        someView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        someView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
        someView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true

        // In this case, the top, bottom, and height constraints will 
        // determine the height of the inputAccessoryView
        someView.heightAnchor.constraint(equalToConstant: 100).isActive = true
    }

    override var intrinsicContentSize: CGSize {
        return CGSize(width: UIViewNoIntrinsicMetric, height: 0)
    }
}

Try adding a button. You'll see that now it gets a touch event, since it's within the bounds of it's superview now.

solidcell
  • 7,639
  • 4
  • 40
  • 59
0

I just had this problem myself in my own work over the past couple days.

You need to implement your own hitTest UIResponder method for your inputAcessoryView. Something that might look like this:

override func hitTest(_ point: CGPoint, 
        with event: UIEvent?) -> UIView? {

    if self.photoButton.frame.contains(point) {
        return self.photoButton
    }

    return super.hitTest(point, with: event)
}

More information can be seen in this related question.

Community
  • 1
  • 1
Michael Dautermann
  • 88,797
  • 17
  • 166
  • 215
  • 1
    Hey Michael, thanks for pointing to this answer (missed it somehow). Happy to report it worked, but had to implement this with `bottomBarView` being a subview of my controller's view and remove the `inputAccessoryView` as it is not a subview of the controller's view and therefore was still not taking into account taps in a button outside of its frame. Would gladly like to hear if you have any idea to make it work with an `inputAccessoryView` ? – Herakleis Mar 04 '17 at 14:57