TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

**SPOILER** SwiftData By Example: "Building a Complete Project" (Challenges) **SPOILER**

Forums > SwiftUI

I'm a bit out of practice, but decided to come back here to learn how to use SwiftData. I completed the "Building a Complete Project" section of the new book, and made an attempt at the challenges at the end. I got it all to work as described in the challenges, but can't help feeling like the way that I did it is not the best way of solving the problem, so I just wanted to post here for advice on the final challenge.

Add a second picker menu to the toolbar in ContentView, allowing the user to toggle showing all destinations or only ones that are in the future.

So, I basically just added the extra menu in ContentView with 2 options ("Show All" & "Show Upcoming"), and bound it to a new variable

@State private var dateFilterIsOn = false

Then, I modified the initializer for DestinationListingView so that it can take the dateFilterIsOn as a parameter. But then I didn't know what else to do but run it through some nested if else logic.

init(dateFilterIsOn: Bool, sort: [SortDescriptor<Destination>], searchString: String) {

        //I wasn't able to use Date.now inside the #Predicate closure, so I created this constant.
        let currentDate = Date.now

        if dateFilterIsOn {
        //This closure is only entered when "Show Upcoming" has been selected
            _destinations = Query(filter: #Predicate {
                if searchString.isEmpty {
                    return $0.date > currentDate
                } else {
                    return $0.name.localizedStandardContains(searchString) && $0.date > currentDate
                }
            }, sort: sort)

        } else {
        //This closure was basically the full initializer before attempting the challenge
            _destinations = Query(filter: #Predicate {
                if searchString.isEmpty {
                    return true
                } else {
                    return $0.name.localizedStandardContains(searchString)
                }
            }, sort: sort)

        }
    }

Did I do this the right way more or less? Or is there a better way that my out of practice brain can't think of?

3      

Did I do this the right way more or less?

It works? Ok then. More or less, it's the right way!

However, I think @twoStraws was hinting you should try the techniques listed in this article.

See -> Dynamically Changing a Predicate

Instead of passing in a boolean that is evaluated in another struct, pass in a predicate representing the choice made in the other view. You're doing something similar with the sort descriptors in your code above. Can you do the same with the predicate?

Try to make a choice, and form the predicate in one view, then pass the predicate to the next view to execute.

3      

So, for the sort, there was an initializer for SortDescriptor that we were able to use to store a SortDescriptor (or an array of them) in a @State variable. For the Predicate, I don't really know how to use the initializer for it.

The way that I did it, I basically just used these two variables and these two menus...

@State private var sortOrder = [SortDescriptor(\Destination.name), SortDescriptor(\Destination.priority)]
@State private var dateFilterIsOn = false
Menu("Sort", systemImage: "arrow.up.arrow.down") {
    Picker("Sort", selection: $sortOrder) {
        Text("Name")
            .tag([SortDescriptor(\Destination.name), SortDescriptor(\Destination.date)])

        Text("Priority")
            .tag([SortDescriptor(\Destination.priority, order: .reverse), SortDescriptor(\Destination.date)])

        Text("Date")
            .tag([SortDescriptor(\Destination.date), SortDescriptor(\Destination.priority, order: .reverse)])
    }
    .pickerStyle(.inline)
}

Menu("Filter", systemImage: "calendar.badge.clock") {
    Picker("Filter", selection: $dateFilterIsOn) {
        Text("Show All")
            .tag(false)

        Text("Show Upcoming")
            .tag(true)
        }
        .pickerStyle(.inline)
}

It seems like what you are telling me is that I should be using Predicate instead of Bool in the second menu here. But what am I supposed to give as parameters for the Predicate initializer?

I've only seen #Predicate as a macro given as a parameter to Query(filter:, sort:) as shown in the initializer in my original post, but if we are supposed to create the Query in DestinationListingView and not in ContentView then I would have to store the predicate as a @State variable in ContentView before the Query is created, and I don't know how to do that.

3      

I know that the link provided in the response above shows an example of how to use a dynamically changing sort, but can anyone provide an example of how to do the same with the predicate? That part is not clear to me.

3      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

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.