Anonymous
Как создать кнопку панели инструментов жидкого стекла, которая адаптируется к фоновому содержанию
Сообщение
Anonymous » 11 сен 2025, 21:56
У меня есть следующий код, пытающийся создать панель инструментов с двумя строками, как я видел из ios safari 26, но проблема в том, что структура инструментов на самом деле не адаптируется к цвету фона, она остается темной, когда на темном фоне. Я также пытался использовать .buttonstyle (.glass) , который также создает капсульный пузырь вокруг каждой кнопки. Будьте адаптивны к фоновому содержанию Scrollview.
Код: Выделить всё
// MARK: - Toolbar Button, but not adaptive to background content color
struct ToolbarButton: View {
var icon: String
var label: String
var isSelected: Bool
var action: () -> Void
var body: some View {
Image(systemName: icon)
.font(.system(size: 18, weight: .semibold))
.symbolVariant(isSelected ? .fill : .none)
.frame(maxWidth: .infinity)
.padding(.vertical, 6)
.contentShape(Rectangle())
.onTapGesture {
action()
}
}
}
< /code>
import SwiftUI
@main
struct GlassToolbarDemoApp: App {
var body: some Scene {
WindowGroup {
DemoView()
}
}
}
struct DemoView: View {
@State private var selectedTab: Tab = .home
@State private var query: String = ""
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
ZStack {
SampleBackgroundScroll()
.ignoresSafeArea() // let content run under the glass for a better effect
}
// Pin our two-row glass toolbar to the TOP. Change to .bottom to make it a bottom toolbar.
.safeAreaInset(edge: .bottom) {
GlassBar {
// ACCESSORY ROW (above the buttons)
HStack(spacing: 10) {
// Search field
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
TextField("Search…", text: $query)
.textInputAutocapitalization(.never)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(.thinMaterial)
)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 2)
} mainRow: {
// MAIN TOOLBAR BUTTONS ROW
if hSizeClass == .compact {
HStack(spacing: 0) {
ToolbarButton(icon: "house.fill", label: "Home", isSelected: selectedTab == .home) { selectedTab = .home }
ToolbarButton(icon: "text.magnifyingglass", label: "Discover", isSelected: selectedTab == .discover) { selectedTab = .discover }
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: "person.fill", label: "Me", isSelected: selectedTab == .me) { selectedTab = .me }
}
} else {
// On iPad / regular width, show accessory and buttons in ONE row (no height waste)
HStack(spacing: 16) {
ToolbarButton(icon: "house.fill", label: "Home", isSelected: selectedTab == .home) { selectedTab = .home }
ToolbarButton(icon: "text.magnifyingglass", label: "Discover", isSelected: selectedTab == .discover) { selectedTab = .discover }
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: "person.fill", label: "Me", isSelected: selectedTab == .me) { selectedTab = .me }
Spacer(minLength: 16)
HStack(spacing: 10) {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
TextField("Search…", text: $query)
.textInputAutocapitalization(.never)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(.thinMaterial)
)
}
.frame(maxWidth: 420)
}
}
}
}
}
}
// MARK: - Glass Bar (two-row toolbar)
struct GlassBar: View {
var accessoryRow: Accessory
var mainRow: Main
init(@ViewBuilder accessory: () -> Accessory, @ViewBuilder mainRow: () -> Main) {
self.accessoryRow = accessory()
self.mainRow = mainRow()
}
var body: some View {
VStack(spacing: 8) {
accessoryRow
Divider().opacity(0.25)
mainRow
}
.padding(.horizontal, 14)
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
.background(GlassBackground(cornerRadius: 26))
.padding(.horizontal)
.padding(.top, 6)
.shadow(color: .black.opacity(0.12), radius: 20, y: 10)
}
}
// MARK: - Glass Background helper
struct GlassBackground: View {
var cornerRadius: CGFloat = 10
var body: some View {
Group {
if #available(iOS 26.0, *) {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.fill(.clear)
.glassEffect(.clear)
} else {
// Fallback for older OSes: use material to approximate the effect.
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.fill(.ultraThinMaterial)
.overlay(
// subtle highlight to simulate glass rim
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.strokeBorder(.white.opacity(0.08), lineWidth: 1)
)
}
}
}
}
// MARK: - Toolbar Button
struct ToolbarButton: View {
var icon: String
var label: String
var isSelected: Bool
var action: () -> Void
var body: some View {
Image(systemName: icon)
.font(.system(size: 18, weight: .semibold))
.symbolVariant(isSelected ? .fill : .none)
.frame(maxWidth: .infinity)
.padding(.vertical, 6)
.contentShape(Rectangle())
.onTapGesture {
action()
}
}
}
// MARK: - Demo background content to show light/dark behind glass
struct SampleBackgroundScroll: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 0) {
demoBlock(.light, title: "Sunny Paper")
demoBlock(.dark, title: "Night Slate")
demoBlock(.colorful, title: "Aurora Cyan")
demoBlock(.light, title: "Porcelain")
demoBlock(.dark, title: "Graphite")
demoBlock(.colorfulAlt, title: "Sunset Blend")
demoBlock(.light, title: "Foggy White")
demoBlock(.dark, title: "Charcoal")
}
}
}
@ViewBuilder
func demoBlock(_ style: BlockStyle, title: String) -> some View {
ZStack {
switch style {
case .light:
LinearGradient(colors: [Color.white, Color(white: 0.93)], startPoint: .topLeading, endPoint: .bottomTrailing)
case .dark:
LinearGradient(colors: [Color.black, Color(white: 0.15)], startPoint: .top, endPoint: .bottom)
case .colorful:
LinearGradient(colors: [Color.cyan.opacity(0.7), Color.blue.opacity(0.4)], startPoint: .topLeading, endPoint: .bottomTrailing)
case .colorfulAlt:
LinearGradient(colors: [Color.purple, Color.orange], startPoint: .topLeading, endPoint: .bottomTrailing)
}
VStack(spacing: 12) {
Text(title)
.font(.system(size: 28, weight: .bold))
.foregroundStyle(style == .dark ? .white : .primary)
Text("Scroll to see how the glass adapts to different backgrounds.")
.font(.system(size: 15))
.foregroundStyle(style == .dark ? .white.opacity(0.85) : .secondary)
}
.padding(.top, 120)
}
.frame(height: 360)
}
enum BlockStyle { case light, dark, colorful, colorfulAlt }
}
// MARK: - Tabs
enum Tab { case home, discover, add, saved, me }
#Preview {
DemoView()
}
Подробнее здесь:
https://stackoverflow.com/questions/797 ... nd-content
1757616985
Anonymous
У меня есть следующий код, пытающийся создать панель инструментов с двумя строками, как я видел из ios safari 26, но проблема в том, что структура инструментов на самом деле не адаптируется к цвету фона, она остается темной, когда на темном фоне. Я также пытался использовать .buttonstyle (.glass) , который также создает капсульный пузырь вокруг каждой кнопки. Будьте адаптивны к фоновому содержанию Scrollview.[code]// MARK: - Toolbar Button, but not adaptive to background content color struct ToolbarButton: View { var icon: String var label: String var isSelected: Bool var action: () -> Void var body: some View { Image(systemName: icon) .font(.system(size: 18, weight: .semibold)) .symbolVariant(isSelected ? .fill : .none) .frame(maxWidth: .infinity) .padding(.vertical, 6) .contentShape(Rectangle()) .onTapGesture { action() } } } < /code> import SwiftUI @main struct GlassToolbarDemoApp: App { var body: some Scene { WindowGroup { DemoView() } } } struct DemoView: View { @State private var selectedTab: Tab = .home @State private var query: String = "" @Environment(\.horizontalSizeClass) private var hSizeClass var body: some View { ZStack { SampleBackgroundScroll() .ignoresSafeArea() // let content run under the glass for a better effect } // Pin our two-row glass toolbar to the TOP. Change to .bottom to make it a bottom toolbar. .safeAreaInset(edge: .bottom) { GlassBar { // ACCESSORY ROW (above the buttons) HStack(spacing: 10) { // Search field HStack(spacing: 8) { Image(systemName: "magnifyingglass") TextField("Search…", text: $query) .textInputAutocapitalization(.never) } .padding(.horizontal, 12) .padding(.vertical, 10) .background( RoundedRectangle(cornerRadius: 14, style: .continuous) .fill(.thinMaterial) ) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 2) } mainRow: { // MAIN TOOLBAR BUTTONS ROW if hSizeClass == .compact { HStack(spacing: 0) { ToolbarButton(icon: "house.fill", label: "Home", isSelected: selectedTab == .home) { selectedTab = .home } ToolbarButton(icon: "text.magnifyingglass", label: "Discover", isSelected: selectedTab == .discover) { selectedTab = .discover } 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: "person.fill", label: "Me", isSelected: selectedTab == .me) { selectedTab = .me } } } else { // On iPad / regular width, show accessory and buttons in ONE row (no height waste) HStack(spacing: 16) { ToolbarButton(icon: "house.fill", label: "Home", isSelected: selectedTab == .home) { selectedTab = .home } ToolbarButton(icon: "text.magnifyingglass", label: "Discover", isSelected: selectedTab == .discover) { selectedTab = .discover } 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: "person.fill", label: "Me", isSelected: selectedTab == .me) { selectedTab = .me } Spacer(minLength: 16) HStack(spacing: 10) { HStack(spacing: 8) { Image(systemName: "magnifyingglass") TextField("Search…", text: $query) .textInputAutocapitalization(.never) } .padding(.horizontal, 12) .padding(.vertical, 10) .background( RoundedRectangle(cornerRadius: 14, style: .continuous) .fill(.thinMaterial) ) } .frame(maxWidth: 420) } } } } } } // MARK: - Glass Bar (two-row toolbar) struct GlassBar: View { var accessoryRow: Accessory var mainRow: Main init(@ViewBuilder accessory: () -> Accessory, @ViewBuilder mainRow: () -> Main) { self.accessoryRow = accessory() self.mainRow = mainRow() } var body: some View { VStack(spacing: 8) { accessoryRow Divider().opacity(0.25) mainRow } .padding(.horizontal, 14) .padding(.vertical, 10) .frame(maxWidth: .infinity) .background(GlassBackground(cornerRadius: 26)) .padding(.horizontal) .padding(.top, 6) .shadow(color: .black.opacity(0.12), radius: 20, y: 10) } } // MARK: - Glass Background helper struct GlassBackground: View { var cornerRadius: CGFloat = 10 var body: some View { Group { if #available(iOS 26.0, *) { RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) .fill(.clear) .glassEffect(.clear) } else { // Fallback for older OSes: use material to approximate the effect. RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) .fill(.ultraThinMaterial) .overlay( // subtle highlight to simulate glass rim RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) .strokeBorder(.white.opacity(0.08), lineWidth: 1) ) } } } } // MARK: - Toolbar Button struct ToolbarButton: View { var icon: String var label: String var isSelected: Bool var action: () -> Void var body: some View { Image(systemName: icon) .font(.system(size: 18, weight: .semibold)) .symbolVariant(isSelected ? .fill : .none) .frame(maxWidth: .infinity) .padding(.vertical, 6) .contentShape(Rectangle()) .onTapGesture { action() } } } // MARK: - Demo background content to show light/dark behind glass struct SampleBackgroundScroll: View { var body: some View { ScrollView { LazyVStack(spacing: 0) { demoBlock(.light, title: "Sunny Paper") demoBlock(.dark, title: "Night Slate") demoBlock(.colorful, title: "Aurora Cyan") demoBlock(.light, title: "Porcelain") demoBlock(.dark, title: "Graphite") demoBlock(.colorfulAlt, title: "Sunset Blend") demoBlock(.light, title: "Foggy White") demoBlock(.dark, title: "Charcoal") } } } @ViewBuilder func demoBlock(_ style: BlockStyle, title: String) -> some View { ZStack { switch style { case .light: LinearGradient(colors: [Color.white, Color(white: 0.93)], startPoint: .topLeading, endPoint: .bottomTrailing) case .dark: LinearGradient(colors: [Color.black, Color(white: 0.15)], startPoint: .top, endPoint: .bottom) case .colorful: LinearGradient(colors: [Color.cyan.opacity(0.7), Color.blue.opacity(0.4)], startPoint: .topLeading, endPoint: .bottomTrailing) case .colorfulAlt: LinearGradient(colors: [Color.purple, Color.orange], startPoint: .topLeading, endPoint: .bottomTrailing) } VStack(spacing: 12) { Text(title) .font(.system(size: 28, weight: .bold)) .foregroundStyle(style == .dark ? .white : .primary) Text("Scroll to see how the glass adapts to different backgrounds.") .font(.system(size: 15)) .foregroundStyle(style == .dark ? .white.opacity(0.85) : .secondary) } .padding(.top, 120) } .frame(height: 360) } enum BlockStyle { case light, dark, colorful, colorfulAlt } } // MARK: - Tabs enum Tab { case home, discover, add, saved, me } #Preview { DemoView() } [/code] Подробнее здесь: [url]https://stackoverflow.com/questions/79762292/how-to-create-a-liquid-glass-toolbar-button-that-adapt-to-background-content[/url]