WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: How to sort/filter out my API call to these menu settings in a list view?

Forums > SwiftUI

Hi,

This is my Menu i'd like to add to my list:

  Menu{
                       Button("Date Added", action: sortByDate)
                       Button("Creator", action: sortByCreator)
                       Button("Difficulty", action: sortByDifficulty)

            } label: {
                Label("Sort", systemImage: "line.3.horizontal.decrease.circle.fill")
            }

but the problem I am having is that my struct is like this:

struct RecipeList: View {
    let menu: RecipeSelection
    /// takes the passeed in value from the recipe selction on the home page and passes it to the list page to fetch the correct repecies.
  @StateObject var api = RecipeAPI()

    var body: some View {

        List {
            //HeaderImage
            Image("HeaderImages/\(menu.name)")
                .resizable()
                .frame(height: 250)
                .aspectRatio(contentMode: .fill)
                .listRowBackground(Color.black)
                .listRowInsets(EdgeInsets(.zero))
                .padding(.bottom, 10)

            //View Discription and Title Header
            Text(menu.name)
                .font(.largeTitle)
                .fontWeight(.bold)
                .listRowSeparator(.hidden)
                .listRowBackground(Color.black)

            Text("Explore \(menu.name) recipes from creators you love.")
                .listRowBackground(Color.black)

            Menu{
                       Button("Date Added", action: sortByDate)
                       Button("Creator", action: sortByCreator)
                       Button("Difficulty", action: sortByDifficulty)

            } label: {
                Label("Sort", systemImage: "line.3.horizontal.decrease.circle.fill")
            }

            //List of Recipes
            ForEach(api.recipes) { recipe in

                NavigationLink(destination: RecipesLanding(recipe: recipe)){
                    HStack{
                        AsyncImage(url: URL(string: "\(recipe.imageURL)")) { image in
                            image
                                .resizable()
                                .cornerRadius(8)
                                .frame(width: 130, height: 81)
                                .clipped()
                                .aspectRatio(contentMode: .fit)
                        } placeholder: {
                            Rectangle()
                                .fill(Color.gray)
                                .frame(width: 130, height: 81)
                                .cornerRadius(8)
                        }

                        VStack(alignment: .leading) {
                            Text(recipe.name)
                                .font(.headline)
                            Text(recipe.creator)
                        }
                    }
                }

            }
            .listRowBackground(Color.black)
            .padding(5)

        }
        .frame( maxWidth: .infinity)
        .edgesIgnoringSafeArea(.all)
        .listStyle(GroupedListStyle())

        .task {
            await api.fetchRecipes(for: menu)

        }
        .listRowBackground(Color.black)

    }

    func sortByDate() {}
    func sortByCreator() {}
    func sortByDifficulty() {}

}

How do I sort by Creator of my API call which is a string? Or By Date which is a Date type but It is formatted as you can see in the API below. and the diffilcuty?


@MainActor
class BreakfastAPI: ObservableObject {
    @Published var recipes: [Recipe] = []

        func fetchRecipes() async {

                let formatter = DateFormatter()
                formatter.dateFormat = "dd/MM/yyyy"

                do  {
                    let breakfastAPIURL = URL(string: URLData.breakfast.rawValue)!
                    async let items = try await URLSession.shared.decode([Recipe].self, from: breakfastAPIURL, dateDecodingStrategy: .formatted(formatter))
                    recipes = try await items
                    recipes = recipes.filter { $0.dateAdded <= Date.now }
                } catch {
                    print("Failed to fetch data!", error)
                }
            }

}

Best, Imran

   

Add this to BreakfastAPI

func sortByCreator() {
  recipes.sort { $0.creator < $1.creator }
}

You can work out the other ones

   

Hi,

So I tried that but the problem I am now having is that the function cannot be a found in scope.

function added to Recipe list view to bring in the function from the RecipeAPI:

func sortedByCreator() {
        sortByCreator()
    }

Menu:

   Menu{
                       Button("Date Added", action: sortByDate)
                       Button("Creator", action: sortedByCreator)
                       Button("Difficulty", action: sortByDifficulty)

            } label: {
                Label("Sort", systemImage: "line.3.horizontal.decrease.circle.fill")
            }

Thanks again...

   

Hacking with Swift is sponsored by Emerge

SPONSORED Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Reliably measure app size, speed up your app's startup time with Emerge's Launch Booster, and much more. Emerge is actively used by many of the top mobile development teams in the world.

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

You do not need the method inside the RecipeList just do this

Button("Creator", action: api.sortedByCreator)

   

Hi,

So I tried that and this is the error I got:

Converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor'

on all the buttons.

Best, Imran

   

Change

@StateObject var api = RecipeAPI()

to

@StateObject var api: RecipeAPI

init() {
    self._api = StateObject(wrappedValue: RecipeAPI())
}

   

It then throws this error:

Return from initializer without initializing all stored properties

   

let menu: RecipeSelection
@StateObject var api: RecipeAPI

init(menu: RecipeSelection) {
    self._api = StateObject(wrappedValue: RecipeAPI())
    self.menu = menu
}

   

Hey,

This error presists on all of them:

Converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor'

   

Hi Imran,

In your code that you have given us you have

@MainActor
class BreakfastAPI: ObservableObject { // <- Is this RecipeAPI?
// rest of code

and

struct RecipeList: View {
  let menu: RecipeSelection

  @StateObject var api = RecipeAPI() // <- does not match the BreakfastAPI

So you need to gives us that. Where about is the error message? Does the RecipeAPI have @MainActor?

   

Hi,

So It's for my recipesAPI It has the @MainActor tag and I have done the above init to the view.

This is the error I am getting:

Converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor'

on all the buttons on the Menu we created.

   

Sorry I am very confused about how the RecipeAPI and BreakfastAPI are connected! The methods(func) for your Button should be in the same class as @Published var recipes: [Recipe] = [] as they change it directly.

Try removing the @MainActor and see if you get an error about change the UI not on main thread, if not then you do not need it.

   

Hacking with Swift is sponsored by Emerge

SPONSORED Why are Swift reference types bad for app startup time, and what’s the performance cost of protocol conformances? That’s just a couple of the topics you can learn about on the Emerge blog — written by the app performance experts behind Emerge’s advanced app optimization and monitoring tools, based on their experience of working at companies like Apple, Airbnb, Snap, and Spotify.

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.