# 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対応の実例として、次の記事も参考になる。