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:过滤的列表索引与新列表不对应的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI CoreData 过滤列表删除意外失败

SwiftUI 过滤列表

自学IOS开发第3天·基础SwiftUI之动态滑动列表(上)

自学IOS开发第3天·基础SwiftUI之动态滑动列表(上)

自学IOS开发第3天·基础SwiftUI之动态滑动列表(上)

无法更改 iOS 15 SwiftUI 列表部分标题填充