简体   繁体   中英

SwiftUI how to filter a dynamic array populate from a json file

I am a newbie to Swift and SwiftUI.

I have a RecipeLinkFetcher() which picks up a json file dynamically and populates an array of recipe objects. I am trying to filter the array of RecipeLink objects based on the category.

The RecipeLink is defined as

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"
    }
}

In my view I am using the below code to populate the variable fetcher which contains an array of RecipeLink

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

And I am trying to filter it to a new variable using the below, however this does not work

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

Here is my 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"
    }
}

This is my UIView with the line marked with ** where I am trying to filter the array/list

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)
                    }
                }
            }
        }
    }
}

Move your filtered array to an @Published on the ObseravbleObject so your view rebuilds whenever it changes. Make the category name a CurrentValueSubject on the observedObject and combine it with your network results to reactively derive the filtered array. ie when ever you change the .value of categoryName or get a new value for recipeLink, the filteredRecipeLinks will be automatically recalculated and since its a @Published property your view will automatically rebuild itself.

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)
    }
...
}

I would separate fetched model and presented filtered recipes, as in below approach (scratchy)

@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}
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM