10
9

More than 3 years have passed since last update.

UIButtonをaddSubViewする時はsuperviewを適切に選択すべし(戒め)

Last updated at Posted at 2019-12-08

ある日のこと……

画像の上にボタンを置いて、ボタンタップ時に処理を追加しようとした。

作りたいもの

完成図

早速作ってみる

コードを書いた

これで完了!……と思うじゃん?

ViewController.swift
import UIKit

final class ViewController: UIViewController {
    private lazy var imageView: UIImageView = {
        let imageView = UIImageView()
        // 本当は画像を置いていたのだが、サンプル用のコードなのでbackgroundColorで妥協
        imageView.backgroundColor = UIColor.systemPink.withAlphaComponent(0.5)
        imageView.layer.cornerRadius = 16
        imageView.clipsToBounds = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()

    private lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("Please tap!!", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 24)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        return button
    }()

    private func layoutImageView() {
        view.addSubview(imageView)
        NSLayoutConstraint.activate([
            imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            imageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
            imageView.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 9 / 16),
        ])
    }

    private func layoutButton() {
        imageView.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }

    @objc
    private func buttonTapped() {
        print("button tapped!!")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        layoutImageView()
        layoutButton()
    }
}

しかし…

⌘+Rを押してビルドされるのを待ち、いざ、ボタンをタップ!!
……あれ?コンソールに何も出てこないぞ??どうやらボタンがタップできていないらしい。
何が悪いんだ?

原因究明

UIViewには、isUserInteractionEnabledというプロパティがある

isUserInteractionEnabledはタップなどのイベントを有効にするかどうかのプロパティである。
で、このデフォルト値はtrueであったはず。なんたって、特にUIButtonはタップイベントを検知するためのUIパーツだし。

念のため、ドキュメントを確認しよう

isUserInteractionEnabled - UIView | Apple Developer Documentation

The default value of this property is true.

ですよねー。
……と思っていたら、下の項目にこんなことが。

Some UIKit subclasses override this property and return a different default value. See the documentation for that class to determine if it returns a different value.

なるほど。UIKitのサブクラスの中にはデフォルト値がtrueではないものがあると。

念のため、UIButtonのドキュメントも見てみましょう。
UIButton - UIKit | Apple Developer Documentation
当然ですが、isUserInteractionEnabledに関する項目はない。
うーん……🤔

DeveloperサイトをisUserInteractionEnabledでググってみた

スクリーンショット 2019-12-09 0.06.53.png

そういえば、UIButtonのsuperViewをUIImageViewにしたぞ……?
superViewをUIViewに変更して再度ビルドして試してみる。

-        imageView.addSubview(button)
+        view.addSubview(button)

今度はちゃんとタップが検出され、コンソールにbutton tapped!!と表示された!

何故ボタンのタップが検出されなかったか

理由が明記された文章は探した限りだと見つかりませんでした。
ただ、UIImageViewのsubViewだったUIButtonのタップが検出されなかったのに、UIViewのsubviewであったUIButtonのタップが検出された事実を考えれば、あるviewのisUserInteractionEnabledがfalseの場合、そのsubviewのisUserInteractionEnabledがtrueであってもタップは検出されなくなってしまうようです。

解決策

画像の上にボタンを置きたくなることはあると思います。その場合、簡単な対処法としては

  1. UIImageViewのisUserInteractionEnabledをtrueに変更する
  2. UIButtonのsuperViewをUIImageViewではないものにする

の2つが考えられます。どちらを選択するかは、Viewの構造によって適切に選択すると良いと思います。
上記の場合は、viewをsuperviewにする後者よりは、UIImageViewをsuperviewとする前者の方がよい気がします。
ちなみに今回私がハマったのは、UICollectionViewCell上に置いたUIImageViewとその上のUIButtonだったため、後者を選択しCellをsuperviewとしました。

Debug View Hierarchyだとどうしてもbuttonが最も手前に見えるので一瞬しっくりこないのですが、まあsubviewなのだからそういうものなのかもしれませんね🤔

10
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9