# iOS 26 Liquid Glass 環境で UINavigationBar のカスタムバッジが白く描画される事象

# 概要

iOS 26 の Liquid Glass 環境で、UINavigationBar の左側に UIBarButtonItem(customView:) を使ってカスタムボタンを配置した。

カスタムボタンは UIButton のサブクラスであり、ベルアイコンとバッジ表示用の UILabel を持つ。バッジには以下のように赤い背景色を指定した。

badgeLabel.backgroundColor = .systemRed
badgeLabel.textColor = .white
badgeLabel.layer.cornerRadius = badgeLabel.bounds.height / 2
badgeLabel.layer.masksToBounds = true

しかし、実機またはシミュレータ上では、赤いバッジが白い円として描画された。

# 発生条件

以下の構成で発生した。

let badgeButton = BadgeButton()
let item = UIBarButtonItem(customView: badgeButton)
navigationItem.leftBarButtonItem = item

BadgeButtonUIButton のサブクラスで、バッジ用の UILabeladdSubview している。

final class BadgeButton: UIButton {
    private let badgeLabel = UILabel()

    private func configure() {
        badgeLabel.backgroundColor = .systemRed
        addSubview(badgeLabel)
    }
}

# 確認した回避策

以下の変更だけでは解決しなかった。

  • UILabel.backgroundColor.systemRed に設定する
  • UILabel.layer.backgroundColorUIColor.systemRed.cgColor を設定する
  • UILabel.layer.cornerRadiusmasksToBounds を設定する
  • UILabel.attributedText.backgroundColor を設定する
  • UILabel.drawText(in:) で赤い円を直接描画する
  • バッジを Liquid Glass 用 UIButton の兄弟ビューとして配置する

NSAttributedString.Key.backgroundColor は文字の描画領域に適用されるため、バッジ背景として使用すると赤い矩形になる。円形バッジには適さない。

# 原因

UIBarButtonItem(customView:) に渡したカスタムビュー全体が、iOS 26 のナビゲーションバーによる共有 Liquid Glass 背景の合成対象になっていた。

この状態では、カスタムビュー内のバッジ背景色も共有背景の影響を受ける。そのため、.systemRed を指定していても白い円として表示された。

# 解決方法

UIBarButtonItemhidesSharedBackgroundtrue に設定する。

private let badgeButton = BadgeButton()

private lazy var badgeButtonItem: UIBarButtonItem = {
    let item = UIBarButtonItem(customView: badgeButton)
    item.hidesSharedBackground = true
    return item
}()

これにより、UIBarButtonItem(customView:) に渡したカスタムビュー全体が、ナビゲーションバーの共有 Liquid Glass 背景から除外される。

その結果、UILabel.backgroundColor = .systemRed が意図した赤色で描画される。

# ベルアイコンの Liquid Glass 表示を維持する方法

カスタムビュー全体の共有背景を無効化すると、必要な Liquid Glass 表現も失われる。ベルアイコン用の UIButton にのみ .glass() を設定する。

final class BadgeButton: UIButton {
    private let glassButton = UIButton()
    private let badgeLabel = UILabel()

    private func configure() {
        glassButton.configuration = .glass()
        glassButton.configuration?.image = UIImage(systemName: "bell.fill")
        glassButton.isUserInteractionEnabled = false

        badgeLabel.backgroundColor = .systemRed
        badgeLabel.textColor = .white
        badgeLabel.textAlignment = .center
        badgeLabel.layer.masksToBounds = true

        addSubview(glassButton)
        addSubview(badgeLabel)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        glassButton.frame = bounds

        let badgeWidth = max(18, badgeLabel.intrinsicContentSize.width + 8)
        badgeLabel.frame = CGRect(
            x: bounds.width - badgeWidth,
            y: 0,
            width: badgeWidth,
            height: 18
        )
        badgeLabel.layer.cornerRadius = badgeLabel.bounds.height / 2
        bringSubviewToFront(badgeLabel)
    }
}

この構成では、ベルアイコン部分だけが Liquid Glass 表示になり、バッジ用 UILabel は通常の赤い円として描画される。

# 複数ボタンを左側に配置する例

戻るボタンとバッジボタンを別々の UIBarButtonItem として配置できる。

navigationItem.leftBarButtonItems = [
    backButtonItem,
    badgeButtonItem
]

badgeButtonItemUIBarButtonItem.Badge を使用していない。UIBarButtonItemBadgeButton をナビゲーションバーへ配置するためだけに使用している。

# 補足

iOS 26 には UIBarButtonItem.Badge も用意されている。標準のバッジ表示で要件を満たせる場合は、UIKit に描画を任せる方法も選択肢になる。

一方、バッジを含むボタン自体を UIButton のカスタムクラスとして実装する要件がある場合は、UIBarButtonItem(customView:)hidesSharedBackground = true を組み合わせる。

食べログアプリのXcode 26/iOS 26対応 〜実例集〜 https://tech-blog.tabelog.com/entry/advent-calendar-20251204