【问题标题】:SwiftUI: Filtered list indices are not corresponding to new listSwiftUI:过滤的列表索引与新列表不对应
【发布时间】: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")           

【问题讨论】:

标签: swift swiftui


【解决方案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 与国家/地区数组的索引不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
  • 1970-01-01
  • 2012-08-04
  • 2013-09-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多