34

In iOS 11 buttons and text field are unresponsive being subviews of UIToolBar. Comparing view hierarchy to iOS 10 we see there is a _UIToolBarContentView over all subview of UIToolBar.

For instance, this new layout of the UIToolBar breaks slacktextviewcontroller https://github.com/slackhq/SlackTextViewController/issues/604

Need a solution working in iOS 10/11.

malex
  • 9,374
  • 3
  • 53
  • 71
Mustard
  • 367
  • 3
  • 5
  • Did you solve this issue? – Tometoyou Sep 12 '17 at 22:06
  • Its weird but I experienced same issue but some case its not visible – REALFREE Sep 15 '17 at 06:15
  • 3
    I tried editing the question for you to take it out of closed status. The mods rejected my edits because I included an answer in the edits. They said I should just answer the question. I CAN'T answer the question if it is closed. Anyway, you should override the `layoutSubviews` method of your `UIToolBar` and either bring your unresponsive views to the front or send the last view to the back inside of that override. – Jon Vogel Sep 21 '17 at 20:48
  • 4
    This is actually very good question. No reason to be closed. It's takes time and research to find out the difference between iOS 10/11. Thanks for that – Roi Mulia Sep 22 '17 at 18:29
  • 3
    I run into the same issue, too.I think the question is clear enough to state the issue.We should reopen this question – Randall Wang Sep 26 '17 at 02:07

6 Answers6

35

To solve the problem for iOS11 (compatible with lower versions) you only need to make layoutSubview right after UIToolBar was added as a subview to UI hierarchy.

In this case _UIToolbarContentView lowers to the first subview of UIToolBar, and you can add all your subviews higher as before.

For example in ObjC,

    UIToolbar *toolbar = [UIToolbar new];
    [self addSubview: toolbar];
    [toolbar layoutIfNeeded];

    <here one can add all subviews needed>

The same problem happens with slacktextviewcontroller

malex
  • 9,374
  • 3
  • 53
  • 71
  • 4
    What if we added the views in a interface builder ? any other solution other than send them to front ? – Michael Pirotte Oct 02 '17 at 09:07
  • Thank you. Was facing the same problem. – user1007522 Oct 03 '17 at 13:37
  • 4
    I have added UIToolBar using storyboard, how do I solve this issue. – anamika41 Oct 23 '17 at 11:06
  • @anamika41 When I try to add a textField or button into toolbar via xib, the element is automatically wrapped in BarButtonItem. So I have touch access to these element, also one can check that toolbarContentView is below these elements at work. Also if I add a button on viewDidLoad into toolbar from xib it also works fine on touch. – malex Oct 26 '17 at 09:28
  • My UIToolbar is part of a storyboard. The UIToolbar and its children (buttons) are referenced in a view controller. I'm unable to access them because at runtime they are obscured by `_UIToolbarContentView` I'm not sure how I should handle it. I'm not using a xib nor creating it in code. – Kirill Kulakov Dec 04 '17 at 15:42
  • I have a view controller that uses a toolbar as its view. The solution in this answer worked for me up to iOS 12 and stopped working with iOS 13. My new solution is to override `viewDidLayoutSubviews` and invoke `bringSubviewToFront:()` on the toolbar. – herzbube Jan 27 '21 at 22:52
3

I have solved this problem in my case. I rewrite the layoutSubviews method in subclass of UIToobar and change the userInteractionEnable of _UIToolbarContentView into NO.

- (void)layoutSubviews {
    [super layoutSubviews];


    NSArray *subViewArray = [self subviews];

    for (id view in subViewArray) {
        if ([view isKindOfClass:(NSClassFromString(@"_UIToolbarContentView"))]) {
            UIView *testView = view;
            testView.userInteractionEnabled = NO;
         }
     }

}
1

You can just use the hitTest(_:with:) method.

  1. First, create a property contentView in UIToolbar:

    open private(set) var contentView: UIView = UIView()
    
  2. Then, make the contentView's frame the same as the UIToolbar's. For example:

    contentView.frame = bounds
    contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    addSubview(contentView)
    
  3. Finally, override the hitTest(_:with:) method:

    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if self.point(inside: point, with: event) {
            if let hitTestView = contentView.hitTest(point, with: event) {
                return hitTestView
            } else {
                return self
            }
        } else {
            return nil
        }
    }
    

In this situation, if you want to customize a toolbar by simply adding additional views, you should add them to the contentView so they will be positioned appropriately.

hengyu
  • 66
  • 4
0

The new UIToolbar object actively uses layout based on constraints, so it is better to override - (void)updateConstraints method. To present custom views over UIToolbar object it is better to subclass it and add custom container view:

- (UIView *)containerView
{
    if (_containerView) {
        return _containerView;
    }
    _containerView = [[UIView alloc] initWithFrame:self.bounds];
    _containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    return _containerView;
}

Now you can safely add your custom views to the container view. To make the custom views responsive we need change the order of toolbar subviews after the constraints update:

- (void)updateConstraints
{
    [super updateConstraints];

    [self bringSubviewToFront:self.containerView];
}

Note, that if you are using UINavigationController with custom toolbar, you should force it to update its layout before adding your custom subviews.

voromax
  • 3,281
  • 2
  • 27
  • 53
0

In Swift with autolayout and code only, what worked for me was to do layout as malex mentions just before adding items, but after setting constraints.

  • Instantiate your toolbar
  • Add it to your view
  • Add constraints

    toolbar.layoutIfNeeded()

    toolbar.setItems([... (your items)], animated: true)

vauxhall
  • 1,761
  • 16
  • 14
-2

There is an odd way to do it.

[self.textInputbar sendSubviewToBack:[self.textInputbar.subviews lastObject]];
Tamás Sengel
  • 47,657
  • 24
  • 144
  • 178