You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在水平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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 00:05:55