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
相关产品推荐
相关产品推荐

