SwiftUI:过滤的列表索引与新列表不对应
Posted
技术标签:
【中文标题】SwiftUI:过滤的列表索引与新列表不对应【英文标题】:SwiftUI: Filtered list indices are not corresponding to new list 【发布时间】:2021-12-27 17:59:03 【问题描述】:我有一个Country
模型,然后是一个国家/地区的 JSON 列表。我使用ForEach
创建了列表。如果搜索框为空,则filteredCountries
的值只是整个列表,否则列表仅包含搜索框中包含whats 的国家/地区。
问题是当列表被过滤并且我点击favorite
一个项目时,错误的国家被收藏了。索引与过滤后的项目不匹配,它们仍然按顺序排列:0,1,2,3
等。
国家
import Foundation
struct Country: Codable, Identifiable
var id: String image
let display_name: String
let searchable_names: [String]
let image: String
var favorite: Bool
var extended: Bool
内容视图
var filteredCountries : [Country]
if (searchText.isEmpty)
return countries;
else
return countries.filter $0.display_name.contains(searchText)
NavigationView
ScrollView
ForEach(Array(filteredCountries.enumerated()), id: \.1.id) (index,country) in
LazyVStack
ZStack(alignment: .bottom)
Image("\(country.image)-bg")
.resizable()
.scaledToFit()
.edgesIgnoringSafeArea(.all)
.overlay(Rectangle().opacity(0.2))
.mask(
LinearGradient(gradient: Gradient(colors: [Color.black, Color.black.opacity(0)]), startPoint: .center, endPoint: .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 country.favorite
Image(systemName: "heart.fill").foregroundColor(.red).onTapGesture
print(country)
countries[index].favorite.toggle()
.padding(.top, 12)
else
Image(systemName: "heart").foregroundColor(.red).onTapGesture
print(country)
countries[index].favorite.toggle()
.padding(.top, 12)
.padding(.horizontal, 16.0)
.padding(.bottom, country.extended ? 220 : 0)
.onTapGesture
withAnimation(.easeInOut(duration: 0.4))
self.countries[index].extended.toggle();
.frame(maxWidth: bounds.size.width)
.navigationTitle("Countries")
.font(Font.custom("Avenir-Book", size: 28))
.navigationViewStyle(StackNavigationViewStyle())
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search Country")
【问题讨论】:
请附上minimal reproducible example 【参考方案1】:我做了一个简单的演示,你可以试试。最值得注意的是filteredCountry
是[Binding<Country>]
类型——这允许我们改变元素,因为它是Binding
。在List
/ForEach
循环中,使用$country
,因为我们传入的是Binding
。
代码:
struct ContentView: View
@State private var countries: [Country] = ["Canada", "England", "France", "Germany", "US"]
@State private var search = ""
private var filteredCounties: [Binding<Country>]
if search.isEmpty
return $countries.map $0
else
return $countries.filter $country in
country.name.lowercased().contains(search.lowercased())
var body: some View
NavigationView
List(filteredCounties) $country in
HStack
Text(country.name)
Spacer()
Image(systemName: country.isFavorite ? "heart.fill" : "heart")
.foregroundColor(.red)
.onTapGesture
country.isFavorite.toggle()
.navigationViewStyle(.stack)
.searchable(text: $search, prompt: "Canada")
Country
下面的结构不太相关,但为了完整起见:
struct Country: Identifiable, ExpressibleByStringLiteral
let id = UUID()
let name: String
var isFavorite = false
init(stringLiteral value: StringLiteralType)
name = value
结果:
【讨论】:
@Fogmeister 这不是我的问题...我只是为提出问题的 other 人提供了一个演示,如何做到这一点(在@ 987654322@方式)。这是工作代码。 啊,对不起,我的错? @Fogmeister Nw,大概是第一句话的措辞哈哈 非常感谢,这提供了非常丰富的信息,我很感谢您费心制作这样的演示!【参考方案2】:在
ForEach(Array(filteredCountries.enumerated()), id: \.1.id) (index,country) in ....
index
指的是“新”数组的索引,该数组仅包含过滤后的国家/地区,
不是所有国家的原始数组。要访问原始数组项,
您可以使用国家/地区 ID,例如:
if let ndx = countries.firstIndex(where: $0.id == country.id)
countries[ndx].favorite.toggle()
代替:
countries[index].favorite.toggle()
【讨论】:
一个非常现实的解决方案,但值得警告的是,对于一个大型数组(它可能用于列出所有国家/地区),由于搜索,这可能会变得相对较慢。仍然是一个很好的解决方案 实际上,这行得通吗?看起来你正在变异cntry
,它不能被变异,因为它是一个struct
,但用let
声明。 var
也无法解决问题,因为它仍然是本地副本。
更新了我的答案。请注意,所有国家/地区大约有 250 个,无论如何都不是一个大数组。
只使用countries[country.id].favorite.toggle()
会起作用吗?因为country.id
总是指正确的国家,而不是列表索引
不,那行不通。 country.id
与国家/地区数组的索引不同。以上是关于SwiftUI:过滤的列表索引与新列表不对应的主要内容,如果未能解决你的问题,请参考以下文章
自学IOS开发第3天·基础SwiftUI之动态滑动列表(上)
自学IOS开发第3天·基础SwiftUI之动态滑动列表(上)