Alex Dong
Alex Dong

Reputation: 985

How to reuse the same SwiftUI ForEach structure for NSOrderedSet and FetchedResults

I have a SwiftUI view where the bulk of the logic displays a list of recipes with a LazyVGrid. The code looks something like this:

struct RecipeGrid: View {
    let recipes: [Recipe]
    var body: some View {
        return LazyVGrid {
            ForEach(countries) { country in 
                Text(recipe.title)
            }
        }
     }
}

This is the hello world of SwiftUI. Straightforward stuff. Two real-world requirements make things a bit more interesting/challenging.

First, we have over 20,000 recipes. So we don't want to pass everything in as an array, as suggested by 1 and 2.

Second, there are two different ways we get the recipes.

The first is to show the most popular recipes using a FetchRequest. It looks something like this:

@FetchRequest(sortDescriptors: [SortDescriptor(\.likes, order: .reverse)])
private var recipes: FetchedResults<Recipe>

The second is to get all recipes using the same ingredient. There is a ManyToMany relationship between Ingredient and Recipe. The declaration of Ingredients looks like this:

class Ingredient: Entity {
    @NSManaged var recipes: NSOrderedSet
}

I am wondering what would be the recommended way to declare the recipes inside RecipeGrid so that it will support both the use cases above.

I have tried the following

struct RecipeGrid: View {
    let recipes: RandomAccessCollection
    ...
}

The above doesn't work. Swift gives the following error message: Protocol 'RandomAccessCollection' can only be used as a generic constraint because it has Self or associated type requirements.

What is the best practice way to approach this? I guess another way to ask my question is:

How do I declare the associated type for RandomAccessCollection?

Upvotes: 0

Views: 231

Answers (1)

Alex Dong
Alex Dong

Reputation: 985

Thanks to @Asperi's help. I figured out a way to achieve what I had in mind.

There are two parts to the answer.

The first is to rewrite RecipeGrid's signature to bind to the RandomAccessCollection protocol, Based on passing FetchedResult to Preview. This opens up the possibility to test the view using an array and FetchedResult.

struct RecipeGrid<Recipes: RandomAccessCollection>: View where RandomAccessCollection.Element == Recipe {
  let recipes: Recipes
  ...
}

The second part is to give up using the CoreData relationship Ingredient.recipes. The recipes is of type NSOrderedSet, which does not conform to the RandomAccessCollection protocol.

The swift-collections' OrderedSet does support RandomAccessCollection but unfortunately it doesn't work with CoreData.

I rewrote the code to have a second FetchedResult to replace the ingredient.recipes call.

Upvotes: 2

Related Questions