【iOS11】UIToolbarの上に乗せたUIButtonやUITextViewのタップイベントが効かない

iOS11対応をしている時にハマったので記録として残しておく。

問題のコード

普通にtoobarの上にボタンを置いているだけ。
iOS10以下なら問題なくボタンのタップイベントを取得できるがiOS11だとボタンのタップが効かなくなる。

  UIToolbar *toolBar = [[UIToolbar alloc]init];
  [self.view addSubview:toolBar];

  UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
  [button setTitle:@"push" forState:UIControlStateNormal];
  [toolBar addSubview:button];

  toolBar.translatesAutoresizingMaskIntoConstraints = NO;
  [toolBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
  [toolBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
  [toolBar.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
  [toolBar.heightAnchor constraintEqualToConstant:50];

  button.translatesAutoresizingMaskIntoConstraints = NO;
  [button.topAnchor constraintEqualToAnchor:toolBar.topAnchor].active = YES;
  [button.leadingAnchor constraintEqualToAnchor:toolBar.leadingAnchor].active = YES;
  [button.trailingAnchor constraintEqualToAnchor:toolBar.trailingAnchor].active = YES;
  [button.bottomAnchor constraintEqualToAnchor:toolBar.bottomAnchor].active = YES;

改善

toobarにaddSubViewする前に toobarに対して layoutIfNeededを呼んでやればいいみたい。 サブクラスにしている場合でも、クラス内でtoolbarにaddSubViewする前にlayoutIfNeededを呼べば良い。

ちなみにiOS10以下でlayoutIfNeededを呼んでいても特に問題はなかったのでiOS11用にコードを分岐する必要はなさそう。
toolbarのviewの階層を調べてみたが特に違いはなかったので単純にバグのような気がする。

素直にViewとかで代用した方がいいかもしれない。blur効果が欲しいならUIVisualEffectViewとか。

  UIToolbar *toolBar = [[UIToolbar alloc]init];
  [self.view addSubview:toolBar];
  // 追記
  [toolBar layoutIfNeeded];

  UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
  [button setTitle:@"push" forState:UIControlStateNormal];
  [toolBar addSubview:button];

  toolBar.translatesAutoresizingMaskIntoConstraints = NO;
  [toolBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
  [toolBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
  [toolBar.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
  [toolBar.heightAnchor constraintEqualToConstant:50];

  button.translatesAutoresizingMaskIntoConstraints = NO;
  [button.topAnchor constraintEqualToAnchor:toolBar.topAnchor].active = YES;
  [button.leadingAnchor constraintEqualToAnchor:toolBar.leadingAnchor].active = YES;
  [button.trailingAnchor constraintEqualToAnchor:toolBar.trailingAnchor].active = YES;
  [button.bottomAnchor constraintEqualToAnchor:toolBar.bottomAnchor].active = YES;

一応Viewの階層をのせておく。

layoutIfNeededを呼んでいない時のToolbarのViewの階層

// iOS10.2
(
    "<UIButton: 0x7feac8d84c20; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x608000423020>>"
)

// iOS11.0.1
(
    "<UIButton: 0x7ffc36443830; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x60000043e740>>"
)

layoutIfNeededを呼んだ後のToolbarのViewの階層

なんか違ってて怖い。

// iOS10.2
(
    "<_UIBarBackground: 0x7f9ff6e736d0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x600000220740>>",
    "<UIButton: 0x7f9ff6e746a0; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x600000425ec0>>"
)

// iOS11.0.1
 (
    "<_UIBarBackground: 0x7f8cc9c98f20; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x600000629da0>>",
    "<_UIToolbarContentView: 0x7f8cc9c9a090; frame = (0 0; 0 0); layer = <CALayer: 0x60000062a600>>",
    "<UIButton: 0x7f8cc9c9ce10; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x60000062b380>>"
)