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

SwiftUI中AppStorage结合ForEach导致输入框失焦问题

问题解决:SwiftUI 编辑数组元素时输入框/颜色选择器丢失焦点

问题根源

你遇到的焦点丢失问题,核心原因有两个:

  1. ForEach 的标识不稳定:使用 id: \.self 时,Profile 是值类型,修改 title 或 color 会改变其哈希值,ForEach 会认为这是一个新元素,销毁旧视图并重建,导致输入框/选择器失去焦点。
  2. @AppStorage 直接存数组的隐性问题:默认 @AppStorage 不支持直接存储自定义 Codable 数组,你的写法实际依赖系统隐式转换,可能导致数据更新时的视图异常。

解决方案

1. 给 Profile 添加稳定的唯一标识符

为结构体添加不会随属性变化的 UUID,确保 ForEach 能稳定识别元素:

struct Profile: Codable, Hashable, Equatable {
    let id: UUID // 稳定唯一ID,属性修改不会改变它
    var title: String = ""
    var color: Color = .blue

    // 初始化自动生成ID,也支持传入自定义ID
    init(id: UUID = UUID(), title: String = "") {
        self.id = id
        self.title = title
    }
}

2. 扩展 Array 支持 @AppStorage 存储

让 @AppStorage 能正确序列化/反序列化 Codable 数组:

extension Array: RawRepresentable where Element: Codable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let decoded = try? JSONDecoder().decode([Element].self, from: data)
        else { return nil }
        self = decoded
    }

    public var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
              let encoded = String(data: data, encoding: .utf8)
        else { return "[]" }
        return encoded
    }
}

3. 优化视图中的绑定与遍历逻辑

使用索引遍历数组,直接绑定到具体位置的元素,避免因元素哈希变化导致的视图重建:

struct ContentView: View {
    @AppStorage("profilesString") var profiles: [Profile] = [Profile(title: "初始配置")]

    var body: some View {
        Grid(alignment: .center, verticalSpacing: 32) {
            // 通过索引遍历,确保视图标识稳定
            ForEach(profiles.indices, id: \.self) { index in
                GridRow {
                    TextField(
                        "Required",
                        text: Binding(
                            get: { profiles[index].title },
                            set: { newTitle in
                                guard !newTitle.isEmpty else { return }
                                profiles[index].title = newTitle
                            }
                        )
                    )
                    .textFieldStyle(.roundedBorder)

                    ColorPicker("", selection: Binding(
                        get: { profiles[index].color },
                        set: { newColor in
                            profiles[index].color = newColor
                        }
                    ))
                }
            }

            // 添加新Profile的按钮(可选)
            Button("添加配置") {
                profiles.append(Profile())
            }
        }
        .padding()
    }
}

效果说明

  • 稳定的 UUID 确保 ForEach 不会因为属性修改而重建视图,输入框和颜色选择器会持续保持焦点。
  • 基于索引的绑定直接操作数组元素,更新实时且无额外查找开销。
  • 自定义的 Array 扩展解决了 @AppStorage 存储数组的持久化问题,修改后的数据会正确保存。

内容的提问来源于stack exchange,提问作者Leonardo Migliorelli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:45:22