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

iOS 26+中Toolbar Menu图标切换symbolVariant从.none到.fill时间歇性短暂消失问题求助

iOS 26+中Toolbar Menu图标切换symbolVariant从.none到.fill时间歇性短暂消失问题求助

大家好,最近做SwiftUI项目时碰到了一个头疼的UI问题,想请教下各位大佬:

我在工具栏里实现了一个Menu,它的图标会根据内嵌的Picker选择结果,动态切换.symbolVariant和.foregroundStyle。但发现当从默认状态(无变体、主色调)切换到激活状态(.fill、蓝色)时,图标有时候会突然消失大概一秒钟,之后才会以正确的样式重新显示出来。

问题的几个特点:

  • 只在默认→激活这个切换方向出现,切回默认状态时完全不会有闪烁问题
  • 属于间歇性问题,但多切换几次Picker选项就能轻松复现

我已经尝试了几种常见的解决方案,但都没能解决这个问题:

  • 给图标添加.contentTransition(.identity)
  • 使用.symbolEffectsRemoved()移除系统默认效果
  • 通过Task延迟状态更新,避免同步刷新可能的冲突
  • 重构了整个Toolbar的结构,调整布局逻辑

我的项目最低兼容版本是iOS 18,而且这个问题只在iOS 26+系统上出现。

希望有遇到过类似问题或者了解SwiftUI底层渲染逻辑的朋友能给我一些思路,非常感谢!

问题演示:

图标切换时的闪烁消失现象

最小复现代码:

import SwiftUI

// MARK: - Filter Enum
enum ItemFilter: String, CaseIterable, Identifiable {
    case all
    case onlySystem
    case onlyCustom

    var id: Self { self }

    var title: String {
        switch self {
        case .all: "All"
        case .onlySystem: "Only system"
        case .onlyCustom: "Only custom"
        }
    }

    var icon: String {
        switch self {
        case .all: "square.grid.2x2.fill"
        case .onlySystem: "gearshape.fill"
        case .onlyCustom: "person.fill"
        }
    }
}

// MARK: - Sample Data
struct SampleItem: Identifiable, Equatable {
    let id: String
    let name: String
    let isSystem: Bool
}

let systemItems: [SampleItem] = [
    SampleItem(id: "s1", name: "Food & Groceries", isSystem: true),
    SampleItem(id: "s2", name: "Transportation", isSystem: true),
    SampleItem(id: "s3", name: "Entertainment", isSystem: true),
    SampleItem(id: "s4", name: "Healthcare", isSystem: true),
    SampleItem(id: "s5", name: "Utilities", isSystem: true),
    SampleItem(id: "s6", name: "Education", isSystem: true),
]

let customItems: [SampleItem] = [
    SampleItem(id: "c1", name: "Coffee", isSystem: false),
    SampleItem(id: "c2", name: "Gym Membership", isSystem: false),
    SampleItem(id: "c3", name: "Streaming Services", isSystem: false),
    SampleItem(id: "c4", name: "Pet Supplies", isSystem: false),
]

// MARK: - ContentView
struct ContentView: View {
    @State private var showSheet = false

    var body: some View {
        VStack(spacing: 20) {
            Text("Toolbar Flicker Test")
                .font(.title2.bold())

            Button("Open Selection Sheet") {
                showSheet = true
            }
            .buttonStyle(.borderedProminent)
        }
        .sheet(isPresented: $showSheet) {
            SelectionSheet()
        }
    }
}

// MARK: - SelectionSheet
struct SelectionSheet: View {
    @Environment(\.dismiss) private var dismiss

    @State private var query = ""
    @State private var selectedFilter: ItemFilter = .all
    @State private var selection: SampleItem?

    private let menuIcon = "line.3.horizontal.decrease.circle"

    private var filteredSystemItems: [SampleItem] {
        let base: [SampleItem]
        switch selectedFilter {
        case .all, .onlySystem: base = systemItems
        case .onlyCustom: base = []
        }
        if query.isEmpty { return base }
        return base.filter { $0.name.localizedStandardContains(query) }
    }

    private var filteredCustomItems: [SampleItem] {
        let base: [SampleItem]
        switch selectedFilter {
        case .all, .onlyCustom: base = customItems
        case .onlySystem: base = []
        }
        if query.isEmpty { return base }
        return base.filter { $0.name.localizedStandardContains(query) }
    }

    var body: some View {
        NavigationStack {
            List {
                if !filteredSystemItems.isEmpty {
                    Section("System") {
                        ForEach(filteredSystemItems) { item in
                            itemRow(item)
                        }
                    }
                }

                if !filteredCustomItems.isEmpty {
                    Section("Custom") {
                        ForEach(filteredCustomItems) { item in
                            itemRow(item)
                        }
                    }
                }

                if filteredSystemItems.isEmpty && filteredCustomItems.isEmpty {
                    ContentUnavailableView.search(text: query)
                }
            }
            .navigationTitle("Categories")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    let filterBinding = Binding(
                        get: { selectedFilter },
                        set: { updatedFilter in
                            selectedFilter = updatedFilter
                        }
                    )

                    Menu {
                        Section {
                            Picker("Filter Items", selection: filterBinding) {
                                ForEach(ItemFilter.allCases) { filter in
                                    Label(filter.title, systemImage: filter.icon)
                                        .tag(filter)
                                }
                            }
                            .pickerStyle(.inline)
                        }
                    } label: {
                        Image(systemName: menuIcon)
                            .symbolVariant(selectedFilter == .all ? .none : .fill)
                            .foregroundStyle(selectedFilter == .all ? .primary : Color.blue)
                    }
                }

                ToolbarItem(placement: .topBarLeading) {
                    Button("Done") { dismiss() }
                }
            }
            .searchable(
                text: $query,
                placement: .navigationBarDrawer(displayMode: .always),
                prompt: "Search categories"
            )
        }
    }

    private func itemRow(_ item: SampleItem) -> some View {
        Button {
            selection = item
            dismiss()
        } label: {
            HStack {
                Circle()
                    .fill(item.isSystem ? Color.orange.opacity(0.15) : Color.blue.opacity(0.15))
                    .frame(width: 36, height: 36)
                    .overlay {
                        Image(systemName: item.isSystem ? "gearshape.fill" : "person.fill")
                            .font(.system(size: 14, weight: .semibold))
                            .foregroundStyle(item.isSystem ? .orange : .blue)
                    }

                Text(item.name)
                    .foregroundStyle(.primary)

                Spacer()

                if selection?.id == item.id {
                    Image(systemName: "checkmark")
                        .foregroundStyle(.tint)
                }
            }
        }
    }
}

#Preview {
    ContentView()
}

备注:内容来源于stack exchange,提问作者Vitor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 17:59:32