在水平ScrollView中使用.glassEffect时出现背景闪烁问题
解决iOS 16+底部SafeAreaBar水平滚动视图中GlassEffect闪烁问题
问题描述
在底部.safeAreaBar的水平滚动视图内为控件添加.glassEffect效果后,左右滑动切换控件组时会出现顽固的背景闪烁。移除玻璃效果后闪烁消失,但需要保留该效果以保持界面风格统一。
已尝试的无效方案
- 将控件或栈包裹在
GlassEffectContainer中 - 移除玻璃效果的
.interactive()修饰符 - 使用
.drawingGroup()(与玻璃效果不兼容) - 使用
.compositingGroup()和.geometryGroup()无变化 - 调整间距/边距(仅部分缓解)
可复现代码
import SwiftUI struct GlassEffectScrollTest: View { // Mock state @State private var selectedOption1: MockOption = .option1 @State private var selectedOption2: MockOption = .optionA @State private var value1: Double = 12 @State private var value2: Double = 20 @State private var showHighlight: Bool = false var body: some View { NavigationStack { if #available(iOS 16.0, *) { ScrollView { VStack(spacing: 0) { ForEach(0..<50, id: \.self) { index in VStack(alignment: .leading, spacing: 8) { Text("Item \(index + 1)") .font(.headline) Text("This is some sample content that will scroll beneath the toolbar. The toolbar should maintain a soft edge blur effect as this content scrolls underneath it.") .font(.subheadline) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(Color(.systemBackground), in: .rect(cornerRadius: 12)) .padding(.horizontal) .padding(.vertical, 6) } } .padding(.top) } .background(Color(.systemGroupedBackground)) .safeAreaBar(edge: .bottom) { mockToolbar() } .navigationTitle("Glass Effect Test") } else { // Fallback on earlier versions } } } @ViewBuilder private func mockToolbar() -> some View { if #available(iOS 16.0, *) { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 24) { // Section 1: Font controls mockup HStack(spacing: 16) { Picker("Option 1", selection: $selectedOption1) { ForEach(MockOption.allCases) { option in Text(option.rawValue).tag(option) } } .pickerStyle(.menu) .labelsHidden() .fixedSize() .controlSize(.small) .glassEffect(.clear, in: .capsule) Toggle(isOn: $showHighlight) { Image(systemName: "highlighter") .contentTransition(.symbolEffect(.replace)) .font(.subheadline) .foregroundStyle(.tint) } .toggleStyle(.button) .tint(.yellow) .glassEffect(.clear, in: .capsule) Spacer() Text("\(Int(value1))pt") .foregroundStyle(.secondary) .font(.subheadline) .frame(minWidth: 35, alignment: .trailing) Stepper("", value: $value1, in: 8...32, step: 1) .labelsHidden() .fixedSize() .controlSize(.small) .glassEffect(.clear, in: .capsule) } .containerRelativeFrame(.horizontal) // Section 2: Spacing controls mockup HStack(spacing: 16) { Picker("Option 2", selection: $selectedOption2) { ForEach(MockOption.allCases) { option in Text(option.rawValue).tag(option) } } .pickerStyle(.menu) .fixedSize() .labelsHidden() .glassEffect(.clear, in: .capsule) Spacer() Text("\(Int(value2))pt") .foregroundStyle(.secondary) .font(.subheadline) .frame(minWidth: 35, alignment: .trailing) Stepper("", value: $value2, in: 0...50, step: 2) .labelsHidden() .glassEffect(.clear, in: .capsule) } .containerRelativeFrame(.horizontal) } .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned) .scrollContentBackground(.hidden) .padding(.vertical, 20) .contentMargins(.horizontal, 24, for: .scrollContent) .containerRelativeFrame(.horizontal) .overlay(alignment: .bottom) { Text("Swipe horizontally for more tools \(Image(systemName: "hand.draw.fill"))") .font(.caption2) .foregroundStyle(.tertiary) .padding(.top, 10) } } } } // Mock data enum MockOption: String, CaseIterable, Identifiable { case option1 = "Option 1" case option2 = "Option 2" case option3 = "Option 3" case optionA = "Option A" case optionB = "Option B" case optionC = "Option C" var id: String { rawValue } } #Preview { GlassEffectScrollTest() }
问题演示

解决方案
方案1:为滚动视图添加统一玻璃效果背景(推荐)
移除单个控件的.glassEffect,转而给整个水平滚动视图的背景容器添加统一玻璃效果,减少多图层渲染冲突:
修改mockToolbar函数:
@ViewBuilder private func mockToolbar() -> some View { if #available(iOS 16.0, *) { ZStack { // 统一玻璃效果背景 Color.clear .glassEffect(.clear, in: .rect(cornerRadius: 16)) .containerRelativeFrame(.horizontal) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 24) { // Section 1: Font controls mockup HStack(spacing: 16) { Picker("Option 1", selection: $selectedOption1) { ForEach(MockOption.allCases) { option in Text(option.rawValue).tag(option) } } .pickerStyle(.menu) .labelsHidden() .fixedSize() .controlSize(.small) Toggle(isOn: $showHighlight) { Image(systemName: "highlighter") .contentTransition(.symbolEffect(.replace)) .font(.subheadline) .foregroundStyle(.tint) } .toggleStyle(.button) .tint(.yellow) Spacer() Text("\(Int(value1))pt") .foregroundStyle(.secondary) .font(.subheadline) .frame(minWidth: 35, alignment: .trailing) Stepper("", value: $value1, in: 8...32, step: 1) .labelsHidden() .fixedSize() .controlSize(.small) } .containerRelativeFrame(.horizontal) // Section 2: Spacing controls mockup HStack(spacing: 16) { Picker("Option 2", selection: $selectedOption2) { ForEach(MockOption.allCases) { option in Text(option.rawValue).tag(option) } } .pickerStyle(.menu) .fixedSize() .labelsHidden() Spacer() Text("\(Int(value2))pt") .foregroundStyle(.secondary) .font(.subheadline) .frame(minWidth: 35, alignment: .trailing) Stepper("", value: $value2, in: 0...50, step: 2) .labelsHidden() } .containerRelativeFrame(.horizontal) } .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned) .scrollContentBackground(.hidden) .padding(.vertical, 20) .contentMargins(.horizontal, 24, for: .scrollContent) .containerRelativeFrame(.horizontal) } .overlay(alignment: .bottom) { Text("左右滑动查看更多工具 \(Image(systemName: "hand.draw.fill"))") .font(.caption2) .foregroundStyle(.tertiary) .padding(.top, 10) .padding(.bottom, 4) } } }
方案2:调整玻璃效果透明度并组合渲染层
保留控件独立玻璃效果,通过降低透明度减少渲染冲突:
// 给每个控件的glassEffect添加透明度 .glassEffect(.clear, in: .capsule, opacity: 0.95) // 滚动视图外层添加组合渲染层 ScrollView(...) { ... } .compositingGroup()
方案3:禁用滚动过渡动画
移除滚动目标切换的默认动画,避免动画触发的渲染异常:
.scrollTargetBehavior(.viewAligned) .scrollTransition(.animated(nil))
原理说明
闪烁源于多个独立玻璃效果视图滚动时,各自的模糊渲染层发生重排冲突。统一背景玻璃效果可减少渲染图层数量;调整透明度或禁用过渡动画能降低系统渲染计算压力,从而消除闪烁。
内容的提问来源于stack exchange,提问作者Andrei G.
相关产品推荐
相关产品推荐

