SwiftUI, как отфильтровать динамический массив, заполненный из файла JSON

Я новичок в Swift и SwiftUI.

У меня есть RecipeLinkFetcher (), который динамически подбирает файл json и заполняет массив объектов рецепта. Я пытаюсь отфильтровать массив объектов RecipeLink на основе категории.

RecipeLink определяется как

struct RecipeLink: Codable, Identifiable 
{
    public var id: Int
    public var category: String
    public var title: String
    public var permalink: String
    public var excerpt: String
    public var image: String

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case category = "category"
        case title = "title"
        case permalink = "permalink"
        case excerpt = "excerpt"
        case image = "featured_image"
    }
}

На мой взгляд, я использую приведенный ниже код для заполнения переменной fetcher, который содержит массив RecipeLink

@ObservedObject var fetcher = RecipeLinkFetcher()
var categoryName: String = "Featured"

И я пытаюсь отфильтровать его к новой переменной, используя ниже, однако это не работает

var recipes = fetcher.filter($0.category == categoryName)

Вот мой RecipeLinkFetcher

import Foundation

public class RecipeLinkFetcher: ObservableObject {

    @Published var recipeLink:[RecipeLink] = [RecipeLink]()

    init(){
        load()
    }

    func load() {
     let url = URL(string: "http://localhost/wordpress2/wp-content/uploads/json-export.json")!

        URLSession.shared.dataTask(with: url) {(data,response,error) in
            do {
                if let d = data {
                    let decodedLists = try JSONDecoder().decode([RecipeLink].self, from: d)
                    DispatchQueue.main.async {
                        self.recipeLink = decodedLists
                    }
                }else {
                    print("No Data")
                }
            } catch {
                print ("Error")
            }

        }.resume()

    }
}


struct RecipeLink: Codable, Identifiable {
    public var id: Int
    public var category: String
    public var title: String
    public var permalink: String
    public var excerpt: String
    public var image: String

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case category = "category"
        case title = "title"
        case permalink = "permalink"
        case excerpt = "excerpt"
        case image = "featured_image"
    }
}

Это мой UIView со строкой, отмеченной **, где я пытаюсь отфильтровать массив / список

struct CategoryRow: View {

    @ObservedObject var fetcher = RecipeLinkFetcher()
    var categoryName: String

    **var filteredList = $fetcher.filter({$0.category==categoryName})**

    var body: some View {
        VStack() {
            Text(self.categoryName.htmlUnescape())
                .font(.headline)
                .padding(.leading, 15)
                .padding(.top, 5)
           // ScrollView(.horizontal, showsIndicators: false) {
                List(fetcher.recipeLink){ recipe in
                    if(recipe.category.htmlUnescape() == self.categoryName){
                        HStack(alignment: .top, spacing: 0 ){
                            CategoryItem(imageURL: recipe.image, recipeTitle: recipe.title)
                    }
                }
            }
        }
    }
}

Всего 2 ответа


Я бы отделил извлеченную модель и представил отфильтрованные рецепты, как в подходе ниже (неуклюжий)

@State private var filteredList = [RecipeLink]() // to be presented

var body: some View {
    VStack() {
        Text(self.categoryName.htmlUnescape())
            .font(.headline)
            .padding(.leading, 15)
            .padding(.top, 5)

        List(filteredList) { recipe in // list only filtered
            HStack(alignment: .top, spacing: 0 ){
                CategoryItem(imageURL: recipe.image, recipeTitle: recipe.title)
        }
        .onReceive(fetcher.$recipeLink) { _ in // once model updated refilter
            self.filteredList = self.fetcher.recipeLink.filter 
                         {$0.category == self.categoryName}
        }
    }
}

Переместите отфильтрованный массив в @Published в ObseravbleObject, чтобы ваше представление перестраивалось при каждом изменении. Сделайте имя категории CurrentValueSubject для наблюдаемого объекта и объедините его с результатами вашей сети, чтобы реактивно получить отфильтрованный массив. т. е. когда вы изменяете .value categoryName или получаете новое значение для recipeLink, FilterRecipeLinks будет автоматически пересчитываться, и, поскольку его свойство @Published ваше представление будет автоматически перестраиваться.

public class RecipeLinkFetcher: ObservableObject {

    @Published var recipeLink:[RecipeLink] = [RecipeLink]()
    @Published var filteredRecipeLinks:[RecipeLink] = []
    var categoryName = CurrentValueSubject<String, Never>("")
    private var subscriptions = Set<AnyCancellable>()
    init(){
        load()
        Publishers
          .CombineLatest($recipeLink, categoryName)
          .map { combined -> [RecipeLink] in
            let (links, name) = combined
            guard !name.isEmpty else { return links }  // Show all when search term is empty
            return links.filter { $0.category == categoryName }
          }
          .assign(to: .recipeLink, on: self)
          .store(in: &subscriptions)
    }
...
}

Есть идеи?

10000