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

Swift/SwiftUI:使用AppStorage存储ID列表功能异常求助

问题:AppStorage存储收藏列表功能异常

我尝试用AppStorage存储Country结构体的ID列表作为用户收藏,点击心形图标切换收藏状态,但功能没按预期工作,代码如下:

struct Number: Identifiable, Codable {
    var id: Int
}


struct ContentView: View {
    @Binding var countries: [Country]
    @Namespace var namespace;
    @AppStorage("favLists") var favLists: [Number] = [];
    
    var body: some View {
        GeometryReader { bounds in
            NavigationView {
                ScrollView {
                    ForEach(Array(filteredCountries.enumerated()), id: \.1.id) { (index,country) in
                        LazyVStack {
                            ZStack(alignment: .bottom) {
                                HStack {
                                    NavigationLink(
                                        destination: CountryView(country: country),
                                        label: {
                                            HStack {
                                                Image(country.image)
                                                    .resizable()
                                                    .frame(width: 50, height: 50)
                                                Text(country.display_name)
                                                    .foregroundColor(Color.black)
                                                    .padding(.leading)
                                                Spacer()
                                            }
                                            .padding(.top, 12.0)
                                            
                                        }
                                    ).buttonStyle(FlatLinkStyle())
                                    if (favLists.filter{$0.id == country.id}.count > 0) {
                                        Image(systemName: "heart.fill").foregroundColor(.red).onTapGesture {
                                            let index = favLists.firstIndex{ $0.id == country.id}
                                            if let index = index {
                                                favLists.remove(at: index)
                                            }
                                        }
                                        .padding(.top, 12)
                                    } else {
                                        Image(systemName: "heart").foregroundColor(.red).onTapGesture {
                                            favLists.append(Number(id: country.id))
                                        }
                                        .padding(.top, 12)
                                    }
                                    
                                }
                                .padding(.horizontal, 16.0)
                            }
                        }
                    }
                }
                .frame(maxWidth: bounds.size.width)
                .navigationTitle("Countries")
                .font(Font.custom("Avenir-Book", size: 28))
            }
            .navigationViewStyle(StackNavigationViewStyle())                
        }
    }
}

问题原因及修复方案

1. AppStorage不直接支持自定义Codable数组存储

AppStorage默认仅支持符合RawRepresentable协议的类型,自定义Codable数组无法直接存储。最简单的优化是直接存储[Int]类型的ID列表,省去自定义结构体的麻烦:

// 替换原@AppStorage定义
@AppStorage("favLists") var favLists: [Int] = []

如果一定要保留Number结构体,需给它和对应的数组实现RawRepresentable协议,完成JSON与字符串/数据的转换:

struct Number: Identifiable, Codable, RawRepresentable {
    var id: Int
    
    typealias RawValue = Data
    
    init?(rawValue: RawValue) {
        guard let decoded = try? JSONDecoder().decode(Number.self, from: rawValue) else {
            return nil
        }
        self = decoded
    }
    
    var rawValue: RawValue {
        guard let encoded = try? JSONEncoder().encode(self) else {
            return Data()
        }
        return encoded
    }
}

extension Array: RawRepresentable where Element == Number {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let result = try? JSONDecoder().decode([Number].self, from: data) else {
            return nil
        }
        self = result
    }
    
    public var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
              let result = String(data: data, encoding: .utf8) else {
            return ""
        }
        return result
    }
}

2. NavigationLink拦截了心形图标的点击事件

心形图标放在NavigationLink所在的HStack内,点击时会优先触发页面跳转,而非收藏逻辑。解决方法是将心形图标移到NavigationLink外部,或通过overlay覆盖在链接上方:

// 修改HStack及后续布局代码
HStack {
    NavigationLink(
        destination: CountryView(country: country),
        label: {
            HStack {
                Image(country.image)
                    .resizable()
                    .frame(width: 50, height: 50)
                Text(country.display_name)
                    .foregroundColor(.black)
                    .padding(.leading)
                Spacer()
            }
            .padding(.top, 12)
        }
    )
    .buttonStyle(FlatLinkStyle())
}
.padding(.horizontal, 16)
.overlay(alignment: .trailing) {
    Button(action: {
        if favLists.contains(country.id) {
            favLists.removeAll { $0 == country.id }
        } else {
            favLists.append(country.id)
        }
    }) {
        Image(systemName: favLists.contains(country.id) ? "heart.fill" : "heart")
            .foregroundColor(.red)
            .padding(.top, 12)
    }
}

3. 优化收藏判断逻辑

原代码用filter{$0.id == country.id}.count > 0判断收藏状态,效率较低,直接用contains更高效:

// 替换原判断逻辑
favLists.contains(country.id)

修改后的完整代码示例

struct ContentView: View {
    @Binding var countries: [Country]
    @Namespace var namespace
    // 直接存储Int类型的ID列表
    @AppStorage("favLists") var favLists: [Int] = []
    
    var body: some View {
        GeometryReader { bounds in
            NavigationView {
                ScrollView {
                    ForEach(Array(filteredCountries.enumerated()), id: \.1.id) { (index, country) in
                        LazyVStack {
                            HStack {
                                NavigationLink(
                                    destination: CountryView(country: country),
                                    label: {
                                        HStack {
                                            Image(country.image)
                                                .resizable()
                                                .frame(width: 50, height: 50)
                                            Text(country.display_name)
                                                .foregroundColor(.black)
                                                .padding(.leading)
                                            Spacer()
                                        }
                                        .padding(.top, 12)
                                    }
                                )
                                .buttonStyle(FlatLinkStyle())
                            }
                            .padding(.horizontal, 16)
                            .overlay(alignment: .trailing) {
                                Button(action: {
                                    if favLists.contains(country.id) {
                                        favLists.removeAll { $0 == country.id }
                                    } else {
                                        favLists.append(country.id)
                                    }
                                }) {
                                    Image(systemName: favLists.contains(country.id) ? "heart.fill" : "heart")
                                        .foregroundColor(.red)
                                        .padding(.top, 12)
                                }
                            }
                        }
                    }
                }
                .frame(maxWidth: bounds.size.width)
                .navigationTitle("Countries")
                .font(Font.custom("Avenir-Book", size: 28))
            }
            .navigationViewStyle(StackNavigationViewStyle())
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 23:45:43