切换中的图片与应有的图像不一致

移动开发 2026-07-11

下面的示例代码展示了我的问题:

import SwiftUI

struct TestView: View
{
    enum Option: Int, CaseIterable, Hashable, Identifiable {
        var id: Self { self }

        case one = 1
        case two = 2
        case three = 3

        var title: String {
            switch self {
            case .one: return "One"
            case .two: return "Two"
            case .three: return "Three"
            }
        }

        var image: some View {
            Image("TestImage")
                .frame(width: 24.0, height: 24.0)
                .clipShape(.circle)
                .overlay {
                    Circle()
                        .strokeBorder(.black)
                }
        }
    }

    @State var selectedOptions: Set<Option> = []

    var body: some View {
        VStack {
            VStack {
                ForEach(Option.allCases) { option in
                    Label {
                        Text(option.title)
                    } icon: {
                        option.image
                    }
                }
            }

        }
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                Menu {
                    ForEach(Option.allCases) { option in
                        let binding = Binding<Bool> {
                            selectedOptions.contains(option)
                        } set: { value in
                            if value {
                                selectedOptions.insert(option)
                            } else {
                                selectedOptions.remove(option)
                            }
                        }
                        Toggle(isOn: binding) {
                            Label {
                                Text(option.title)
                            } icon: {
                                option.image
                            }
                        }
                    }
                } label: {
                    Image(systemName: "checklist")
                }
                .menuOrder(.fixed)
            }
        }
    }
}

#Preview {
    TestView()
}

菜单中显示的图像与我预期的以及在VStack中显示的不同。

有人知道为什么会这样,以及我该如何让菜单中的图标像在VStack中那样显示?

示例图片

解决方案

当把图像用作菜单项时,剪裁形状(圆形)以及你应用到图像上的黑色边框都会被忽略。所以你的源图像很可能只是一个红色正方形。

一个简单的变通办法是使用一张已经看起来符合你期望的图像,换句话说,具有圆形并带黑色边框的图像。

否则,如果你想对现有图像应用样式,可以考虑从样式化的图像创建一个新图像。一个 ImageRenderer 可以用于此。

下面演示如何以这种方式更新示例。主要变化如下:

  • 通用函数 content2Image 使用一个 ImageRenderer 将任意内容转换为图像。这基于我另一条回答中同名的函数,但在这里为iOS(而非macOS)进行了改造。
  • 使用此函数将样式化的枚举图像转换为新图像。
  • 将新图像用作菜单项的图标。
struct TestView: View {

    enum Option: Int, CaseIterable, Hashable, Identifiable {
        // ...
    }

    @Environment(\.displayScale) private var displayScale // 👈 added
    @State var selectedOptions: Set<Option> = []

    private func content2Image<C: View>(content: () -> C) -> Image { // 👈 added
        let result: Image
        let renderer = ImageRenderer(content: content())
        renderer.scale = displayScale
        if let uiImage = renderer.uiImage {
            result = Image(uiImage: uiImage)
        } else {
            result = Image(systemName: "exclamationmark.triangle")
        }
        return result
    }

    var body: some View {
        VStack {
            // ...
        }
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                Menu {
                    ForEach(Option.allCases) { option in
                        let binding = Binding<Bool> {
                            // ...
                        }
                        Toggle(isOn: binding) {
                            Label {
                                Text(option.title)
                            } icon: {
                                content2Image { option.image } // 👈 changed
                            }
                        }
                    }
                } label: {
                    Image(systemName: "checklist")
                }
                .menuOrder(.fixed)
            }
        }
    }
}

截图

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章