# 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
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 は文字の描画領域に適用されるため、バッジ背景として使用すると赤い矩形になる。円形バッジには適さない。
# 原因
UIBarButtonItem(customView:) に渡したカスタムビュー全体が、iOS 26 のナビゲーションバーによる共有 Liquid Glass 背景の合成対象になっていた。
この状態では、カスタムビュー内のバッジ背景色も共有背景の影響を受ける。そのため、.systemRed を指定していても白い円として表示された。
# 解決方法
UIBarButtonItem の hidesSharedBackground を true に設定する。
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
]
badgeButtonItem は UIBarButtonItem.Badge を使用していない。UIBarButtonItem は BadgeButton をナビゲーションバーへ配置するためだけに使用している。
# 補足
iOS 26 には UIBarButtonItem.Badge も用意されている。標準のバッジ表示で要件を満たせる場合は、UIKit に描画を任せる方法も選択肢になる。
一方、バッジを含むボタン自体を UIButton のカスタムクラスとして実装する要件がある場合は、UIBarButtonItem(customView:) と hidesSharedBackground = true を組み合わせる。
食べログアプリのXcode 26/iOS 26対応 〜実例集〜 https://tech-blog.tabelog.com/entry/advent-calendar-20251204