Open4

UIButton.Configurationを使うときはaddSubviewの順番に気をつける

だじだじ
  • UIButtonで作ったボタンにバッジをつけるため実装していた時に、UIButton.Configurationを使った時に変な挙動を発見したのでメモ
  • バッジの実装はUIButtonの上にバッジに見立てたUIViewをのせるような実装
  • 以下のコードは意図した通りに動作したもの
override func viewDidLoad() {
    super.viewDidLoad()
    // ボタンを作成
    let button = UIButton(configuration: .plain())
    button.configuration?.image = .init(systemName: "house.fill")
    button.translatesAutoresizingMaskIntoConstraints = false
    // viewに追加
    view.addSubview(button)
    NSLayoutConstraint.activate([
        button.widthAnchor.constraint(equalToConstant: 32),
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
    // ボタンの上に赤丸を設置
    let badgeView = UIView()
    badgeView.backgroundColor = .red
    badgeView.layer.cornerRadius = 4
    badgeView.translatesAutoresizingMaskIntoConstraints = false
    button.addSubview(badgeView)
    NSLayoutConstraint.activate([
        badgeView.topAnchor.constraint(equalTo: button.topAnchor, constant: 4),
        badgeView.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: -4),
        badgeView.widthAnchor.constraint(equalToConstant: 8),
        badgeView.heightAnchor.constraint(equalToConstant: 8)
    ])
}

だじだじ

おかしな挙動

  • UIViewControllerのviewへaddSubviewするよりも前に、buttonへbadgeViewをaddSubviewしてみる
  • するとバッジのレイヤが画像よりも後ろになってしまう
override func viewDidLoad() {
    super.viewDidLoad()
    // ボタン作成
    let button = UIButton(configuration: .plain())
    button.configuration?.image = .init(systemName: "house.fill")
    button.translatesAutoresizingMaskIntoConstraints = false
    // ボタンの上に赤丸を設置
    let badgeView = UIView()
    badgeView.backgroundColor = .red
    badgeView.layer.cornerRadius = 4
    badgeView.translatesAutoresizingMaskIntoConstraints = false
    button.addSubview(badgeView)
    NSLayoutConstraint.activate([
        badgeView.topAnchor.constraint(equalTo: button.topAnchor, constant: 4),
        badgeView.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: -4),
        badgeView.widthAnchor.constraint(equalToConstant: 8),
        badgeView.heightAnchor.constraint(equalToConstant: 8)
    ])
    // viewに追加
    view.addSubview(button)
    NSLayoutConstraint.activate([
        button.widthAnchor.constraint(equalToConstant: 32),
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
}

だじだじ
  • ヒエラルキーでも画像よりも後ろへ配置されてしまっていることがわかる

だじだじ
  • configurationを使わずにsetImage(_:for:)を使った場合は問題ない
  • setImageでもconfigurationを設定した場合は発生するので注意
override func viewDidLoad() {
    super.viewDidLoad()
    // ボタンを設置
    let button = UIButton()
    button.setImage(.init(systemName: "house.fill"), for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    // ボタンの上に赤丸を設置
    let badgeView = UIView()
    badgeView.backgroundColor = .red
    badgeView.layer.cornerRadius = 4
    badgeView.translatesAutoresizingMaskIntoConstraints = false
    button.addSubview(badgeView)
    NSLayoutConstraint.activate([
        badgeView.topAnchor.constraint(equalTo: button.topAnchor, constant: 4),
        badgeView.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: -4),
        badgeView.widthAnchor.constraint(equalToConstant: 8),
        badgeView.heightAnchor.constraint(equalToConstant: 8)
    ])
    // viewに追加
    view.addSubview(button)
    NSLayoutConstraint.activate([
        button.widthAnchor.constraint(equalToConstant: 32),
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
}