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.