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

iOS 26で UINavigationBar に置いたカスタムボタンの赤いバッジが、白い円として描画された。 UIBarButtonItem の hidesSharedBackground を true にすると、意図した赤色で描画された。

# UIBarButtonItem(customView:) に置いたバッジが白い円になった

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

BadgeButton は UIButton のサブクラスで、バッジ用の UILabel を addSubview している。

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

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

# 背景色やレイヤーの変更では直らなかった

次の変更は、どれも単独では解決しなかった。

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

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

# カスタムビュー全体が共有Liquid Glass背景の合成対象になっていた

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

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

# hidesSharedBackground を true にして共有背景から外す

UIBarButtonItem の hidesSharedBackground を true に設定する。

private let badgeButton = BadgeButton()

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

これにより、カスタムビュー全体がナビゲーションバーの共有Liquid Glass背景から除外される。 その結果、UILabel.backgroundColor = .systemRed が意図した赤色で描画される。

# ベルアイコンだけに .glass() を設定して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 にする

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

navigationItem.leftBarButtonItems = [
    backButtonItem,
    badgeButtonItem
]

badgeButtonItem は UIBarButtonItem.Badge を使用していない。 UIBarButtonItem は、BadgeButton をナビゲーションバーへ配置するためだけに使っている。

# 標準の UIBarButtonItem.Badge を使わなかった理由

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

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

Xcode 26/iOS 26対応の実例として、次の記事も参考になる。