I’ve the next code making an attempt to create a two rows toolbar as I’ve seen from iOS Safari 26, however the issue is the ToolbarButton struct would not actually adapt to the background coloration, it stayed darkish when on darkish background.
I attempted to make use of .glassEffect
nevertheless it creates a small bubble relying on the form of the image, which does not look good with different buttons on the toolbar. I additionally tried to make use of .buttonStyle(.glass)
which additionally creates a capsule bubble round every button as effectively.
I additionally tried to group all of the buttons with the glass container in a namespace, that is shut nevertheless it simply teams all my buttons, once I wished to share the only glass panel with the search area.
All I actually wished was only for the Picture foreground coloration to be adaptive to the background scrollview’s content material.
// MARK: - Toolbar Button, however not adaptive to background content material coloration
struct ToolbarButton: View {
var icon: String
var label: String
var isSelected: Bool
var motion: () -> Void
var physique: some View {
Picture(systemName: icon)
.font(.system(measurement: 18, weight: .semibold))
.symbolVariant(isSelected ? .fill : .none)
.body(maxWidth: .infinity)
.padding(.vertical, 6)
.contentShape(Rectangle())
.onTapGesture {
motion()
}
}
}
import SwiftUI
@essential
struct GlassToolbarDemoApp: App {
var physique: some Scene {
WindowGroup {
DemoView()
}
}
}
struct DemoView: View {
@State personal var selectedTab: Tab = .residence
@State personal var question: String = ""
@Atmosphere(.horizontalSizeClass) personal var hSizeClass
var physique: some View {
ZStack {
SampleBackgroundScroll()
.ignoresSafeArea() // let content material run beneath the glass for a greater impact
}
// Pin our two-row glass toolbar to the TOP. Change to .backside to make it a backside toolbar.
.safeAreaInset(edge: .backside) {
GlassBar {
// ACCESSORY ROW (above the buttons)
HStack(spacing: 10) {
// Search area
HStack(spacing: 8) {
Picture(systemName: "magnifyingglass")
TextField("Search…", textual content: $question)
.textInputAutocapitalization(.by no means)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 14, fashion: .steady)
.fill(.thinMaterial)
)
}
.body(maxWidth: .infinity, alignment: .main)
.padding(.horizontal, 2)
} mainRow: {
// MAIN TOOLBAR BUTTONS ROW
if hSizeClass == .compact {
HStack(spacing: 0) {
ToolbarButton(icon: "home.fill", label: "House", isSelected: selectedTab == .residence) { selectedTab = .residence }
ToolbarButton(icon: "textual content.magnifyingglass", label: "Uncover", isSelected: selectedTab == .uncover) { selectedTab = .uncover }
ToolbarButton(icon: "plus.circle.fill", label: "Add", isSelected: selectedTab == .add) { selectedTab = .add }
ToolbarButton(icon: "star.fill", label: "Saved", isSelected: selectedTab == .saved) { selectedTab = .saved }
ToolbarButton(icon: "particular person.fill", label: "Me", isSelected: selectedTab == .me) { selectedTab = .me }
}
} else {
// On iPad / common width, present accent and buttons in ONE row (no top waste)
HStack(spacing: 16) {
ToolbarButton(icon: "home.fill", label: "House", isSelected: selectedTab == .residence) { selectedTab = .residence }
ToolbarButton(icon: "textual content.magnifyingglass", label: "Uncover", isSelected: selectedTab == .uncover) { selectedTab = .uncover }
ToolbarButton(icon: "plus.circle.fill", label: "Add", isSelected: selectedTab == .add) { selectedTab = .add }
ToolbarButton(icon: "star.fill", label: "Saved", isSelected: selectedTab == .saved) { selectedTab = .saved }
ToolbarButton(icon: "particular person.fill", label: "Me", isSelected: selectedTab == .me) { selectedTab = .me }
Spacer(minLength: 16)
HStack(spacing: 10) {
HStack(spacing: 8) {
Picture(systemName: "magnifyingglass")
TextField("Search…", textual content: $question)
.textInputAutocapitalization(.by no means)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 14, fashion: .steady)
.fill(.thinMaterial)
)
}
.body(maxWidth: 420)
}
}
}
}
}
}
// MARK: - Glass Bar (two-row toolbar)
struct GlassBar<Accent: View, Foremost: View>: View {
var accessoryRow: Accent
var mainRow: Foremost
init(@ViewBuilder accent: () -> Accent, @ViewBuilder mainRow: () -> Foremost) {
self.accessoryRow = accent()
self.mainRow = mainRow()
}
var physique: some View {
VStack(spacing: 8) {
accessoryRow
Divider().opacity(0.25)
mainRow
}
.padding(.horizontal, 14)
.padding(.vertical, 10)
.body(maxWidth: .infinity)
.background(GlassBackground(cornerRadius: 26))
.padding(.horizontal)
.padding(.high, 6)
.shadow(coloration: .black.opacity(0.12), radius: 20, y: 10)
}
}
// MARK: - Glass Background helper
struct GlassBackground: View {
var cornerRadius: CGFloat = 10
var physique: some View {
Group {
if #accessible(iOS 26.0, *) {
RoundedRectangle(cornerRadius: cornerRadius, fashion: .steady)
.fill(.clear)
.glassEffect(.clear)
} else {
// Fallback for older OSes: use materials to approximate the impact.
RoundedRectangle(cornerRadius: cornerRadius, fashion: .steady)
.fill(.ultraThinMaterial)
.overlay(
// delicate spotlight to simulate glass rim
RoundedRectangle(cornerRadius: cornerRadius, fashion: .steady)
.strokeBorder(.white.opacity(0.08), lineWidth: 1)
)
}
}
}
}
// MARK: - Toolbar Button
struct ToolbarButton: View {
var icon: String
var label: String
var isSelected: Bool
var motion: () -> Void
var physique: some View {
Picture(systemName: icon)
.font(.system(measurement: 18, weight: .semibold))
.symbolVariant(isSelected ? .fill : .none)
.body(maxWidth: .infinity)
.padding(.vertical, 6)
.contentShape(Rectangle())
.onTapGesture {
motion()
}
}
}
// MARK: - Demo background content material to point out mild/darkish behind glass
struct SampleBackgroundScroll: View {
var physique: some View {
ScrollView {
LazyVStack(spacing: 0) {
demoBlock(.mild, title: "Sunny Paper")
demoBlock(.darkish, title: "Evening Slate")
demoBlock(.colourful, title: "Aurora Cyan")
demoBlock(.mild, title: "Porcelain")
demoBlock(.darkish, title: "Graphite")
demoBlock(.colorfulAlt, title: "Sundown Mix")
demoBlock(.mild, title: "Foggy White")
demoBlock(.darkish, title: "Charcoal")
}
}
}
@ViewBuilder
func demoBlock(_ fashion: BlockStyle, title: String) -> some View {
ZStack {
change fashion {
case .mild:
LinearGradient(colours: [Color.white, Color(white: 0.93)], startPoint: .topLeading, endPoint: .bottomTrailing)
case .darkish:
LinearGradient(colours: [Color.black, Color(white: 0.15)], startPoint: .high, endPoint: .backside)
case .colourful:
LinearGradient(colours: [Color.cyan.opacity(0.7), Color.blue.opacity(0.4)], startPoint: .topLeading, endPoint: .bottomTrailing)
case .colorfulAlt:
LinearGradient(colours: [Color.purple, Color.orange], startPoint: .topLeading, endPoint: .bottomTrailing)
}
VStack(spacing: 12) {
Textual content(title)
.font(.system(measurement: 28, weight: .daring))
.foregroundStyle(fashion == .darkish ? .white : .main)
Textual content("Scroll to see how the glass adapts to totally different backgrounds.")
.font(.system(measurement: 15))
.foregroundStyle(fashion == .darkish ? .white.opacity(0.85) : .secondary)
}
.padding(.high, 120)
}
.body(top: 360)
}
enum BlockStyle { case mild, darkish, colourful, colorfulAlt }
}
// MARK: - Tabs
enum Tab { case residence, uncover, add, saved, me }
#Preview {
DemoView()
}