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

SOLVED: Querying with SwiftData

Forums > Swift

Assuming I have a to many relatioship Parent -> Children, is there a way to make a query for Parent but sort the array based on Children property? or how could I achieve that. No much tutorials around yet.

3      

Thanks @magna, I have read that but I can't figure out how to achive the following:

in the main view I use @Query to fetch an array of parents with a predicate (there will be only one parent with this predicate).

Then pass this parent to another view by a NavigationLink.

From here when using ForEach($parent.children) there is not problem, but I would need that the array of children is sorted.

If I try to sort it the bindable would no longer work. I have tried different ways but it seems I can't find the right one.

How can I get Children sorted when fetching from Parent or what workaround

3      

@Obelix thanks for pointing that out, I had found it anyway. This is the correct link: https://www.hackingwithswift.com/quick-start/swiftdata/

3      

Markdown Link Syntax


Try this technique:

Click the link icon while composing your message. It looks like three chain links.

Enter a clever text description between the [] brackets. Enter the web address between the parentheses. ()

Example:

See -> [Clever Description](https: //hackingwithswift.com/quick-start/swiftdata/ )

In markdown, this resolves to:
See -> HWS SwiftData Book

This gives your link a cleaner look in forum responses.

Keep Coding!

3      

Thanks @Obelix I'll use the Markdown link syntax next time... my bad.

Regarding my problem. Let's assume I have an array of family with properties name and origin

[Bennett, Fletcher, Dawson, Spencer]

and each family has an array of four members called

[Susan, Adam, Henry, Laura]

[Eric, John, Jane, Monika]

[Anthony, Lucia, Anna, Maria]

[Karl, Alfred, Zara, Fiona]

Based on the following Fetch:

@Query(filter: #Predicate<Family> {family in
        family.name == "New Zealand", sort: \Family.name) var families : Family

        var body: some View {
        NavigationStack {
            List {
                 ForEach(family) {family  in
                    NavigationLink {
                        EditFamily(family: family)
                    } label: {
                        Text("Default Allocation")
                    }
                }
                }

in the EditFamily view the outcome woul be:

Family Name: Bennett

Member1 name: Laura

Member2 name: Henry

Member3 name: Susan

Member4 name: Adam

But I want them ordered:

Member1 name: Adam

Member2 name: Henry

Member3 name: Laura

Member4 name: Susan

I can't understand how to Query from Family the relationship People sorted by name.

P.S.: I am aware I could use computed property with get and set to return the array sorted like with Core Data, just curious to see if that can be done

3      

I am fighting similar challenges when working with parent and children relationships and @Queries.

I believe there is currently no way to order the result by children properties when querying the parents.

However, what you could try is, that you are doing a second query filtering for all children where children parent is a specific parent and ordering the result as you prefer.

The computed property should also work but I believe this gets difficult when querying a lot of objects regarding performance.

3      

@rockeby would like some order to his family tree:

I can't understand how to Query from Family the relationship People sorted by name.

I used some code from another example program I was working. Perhaps this example can help you understand a one-to-many relationship and how you can select objects from SwiftData, and sort them based on parameters from two different SwiftData models.

It's a long example. But I've annotated with lot o'comments.

Lumos!

Think of Hogwarts. Each House in Hogwarts has a number of student wizards. Each Wizard is in one house, but each house may have several Wizards.

House and Wizard Models

import SwiftData

@Model
class House {
    @Attribute(.unique) var name: String
    // If you delete a House, then DELETE all wizards in the house too.
    @Relationship(deleteRule: .cascade, inverse: \Wizard.house)
    var wizards: [Wizard] // each house has a number of Wizard students

    init(named: String) {  // basic initializer
        self.name = named
        self.wizards = []  // none at first
    }
}

@Model
class Wizard {
    var name:   String
    var house:  House? // Maybe a wizard doesn't have a house?
    init(named: String, ofHouse house: House?) {
        self.name  = named
        self.house = house
    }
}

House View

The answer to your question (above) is in the @Query macro below. This was some playing-around code, so I used emojis as variable names.

import SwiftData
import SwiftUI

// MARK: - Computed Variables
struct HouseView: View {
    @Environment(\.modelContext) var 🏰  // context = Hogwarts!

    // Get All Wizards in Hogwarts (the context)
    // Sort the results, first by house name, THEN by Wizard name.
    @Query( sort: [
        SortDescriptor(\Wizard.house?.name),
        SortDescriptor(\Wizard.name)
    ] )
    var πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½: [Wizard]  //  Accio!  (summon objects spell!)

    // All houses in the context.🏚️
    // Weird syntax, I know. First, get all the houses linked to all Wizards, excluding nils.
    // Next compact them into a Set, which removes duplicates.
    // THEN, turn the set back into an array, so it's easy to manipulate.
    var 🏚️🏚️🏚️: Array<House> { Array(Set(πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½.compactMap{ $0.house })) }

    var body: some View {
        NavigationStack {
            VStack {
                HeaderView(πŸ§™πŸ½count: πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½.count, 🏚️count: 🏚️🏚️🏚️.count)
                List {
                   ForEach (πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½) { πŸ‘» in
                        WizardView(πŸ§™πŸ½: πŸ‘»)
                    }.onDelete(perform: obscuroπŸ§™πŸ½) // swipe to delete
                }
                Text("Notice the order!").font(.largeTitle)
                    .navigationTitle("🏰 Rosters").navigationBarTitleDisplayMode(.inline)
                HStack {
                    Button { enrollWizards()    } label: { Text("Enroll") }
                    Button { homenumRevelioπŸ§™πŸ½() } label: { Text("New Wizard")} // Spell: Homenum Revelio
                }
            }.buttonStyle(.borderedProminent)
            Spacer()
        }
    }
}

Helper Methods

This is an extension to the view code. It contains a few helper methods.

// MARK: - View Methods
extension HouseView {
    // Spell reveals the presence of another person
    private func homenumRevelioπŸ§™πŸ½() {
        guard let 𝓧          = "ABCDEFGHJKLMNOP".randomElement() else { return }
        guard let random🏚️   = 🏚️🏚️🏚️.randomElement() else { return }
        guard let randomName = ["Rockby", "Luna", "Harry", "Obelix"].randomElement() else { return }
        // Reveal the presence!
        _ = Wizard(named: "\(String(describing: 𝓧))-\(randomName)", ofHouse: random🏚️)
    }

    // Hide or remove an object
    private func obscuroπŸ§™πŸ½(_ indexSet: IndexSet) {
        for index in indexSet {
            let πŸ§™πŸ½ = πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½πŸ§™πŸ½[index]
            // Magic! Pass the object to the model context and delete it.
            🏰.delete(πŸ§™πŸ½)
        }
    }

    // this populates the house and Wizard objects. 1 to many relationships
    private func enrollWizards() {
        // Enroll Gryffindor ============================
        let gryffindor = House(named: "Gryffindor")
        🏰.insert(gryffindor )
        let _ = Wizard(named: "Hermione", ofHouse: gryffindor)
        let _ = Wizard(named: "Ginny",    ofHouse: gryffindor)
        let _ = Wizard(named: "Ron",      ofHouse: gryffindor)
        let _ = Wizard(named: "Harry",    ofHouse: gryffindor)

        // Enroll Hufflepuff ============================
        let hufflepuff = House(named: "Hufflepuff")
        🏰.insert(hufflepuff)
        let _ = Wizard(named: "Newton", ofHouse: hufflepuff)
        let _ = Wizard(named: "Pamona", ofHouse: hufflepuff)
        let _ = Wizard(named: "Cedric", ofHouse: hufflepuff)

        // Enroll Ravenclaw ============================
        let ravenclaw = House(named: "Ravenclaw")
        🏰.insert(ravenclaw  )
        let _ = Wizard(named: "Rowena", ofHouse: ravenclaw)
        let _ = Wizard(named: "Padma",  ofHouse: ravenclaw)
        let _ = Wizard(named: "Luna",   ofHouse: ravenclaw)
        let _ = Wizard(named: "Obelix", ofHouse: nil)
        // autosave!  Just insert, SwiftData does the magic of saving.
    }
}

Subview Structs

These are subviews used in the HouseView struct. They're included as extensions. Why? Extensions help you organize your code into similar functional areas: Computed Vars, Structures, Methods. If I have them, I'll also toss enums into their own extension as well.

This level of organization isn't necessary for small structs. But @twoStraws wrote a nice short article on how this technique is useful.

See -> Organizing your Views

// MARK: - Structs for Subviews
extension HouseView {
    private struct WizardView: View {
        var πŸ§™πŸ½:    Wizard
        var πŸ§™πŸ½πŸšοΈ : String {
            πŸ§™πŸ½.house != nil ? πŸ§™πŸ½.house!.name.padding(toLength: 14, withPad: " ", startingAt: 0) : "unk         "
        }
        var body: some View {
            HStack(alignment: .bottom) {
                Text("πŸ§™πŸ½ "); Text(πŸ§™πŸ½πŸšοΈ); Text(πŸ§™πŸ½.name)
            }
        }
    }

    private struct HeaderView: View {
        let πŸ§™πŸ½count: Int
        let 🏚️count: Int
        var body: some View {
            HStack{  // inflect is very cool! Learn this technique.
                Spacer(); Text("^[\(πŸ§™πŸ½count) Wizard](inflect: true)") // Cool! Wizard / Wizards!
                Spacer(); Text("^[\(🏚️count) House](inflect: true)"); Spacer()
            }.font(.caption2)
        }
    }
}

Keep Coding!

3      

Thank you very much @Thensel and @Obelix.

@Obelix I think that will work with my idea. I will give it a go as soon as I can. Thanks again for taking your time to find a solution, cheers !

3      

@Obelix, question on your (incredible) example above. In your House and Wizard Models, you include a reference to House in Wizard, which @twostraws does not do in his SwiftData project (there, Destination references Sight and adds @Relationship, but Sight has no mention of Destination). Is there a reason you do this? Perhaps unrelated, but you also initialize wizard in house (and vice versa), which isn't something done in the sample project either.

A few of us are struggling to delete the 'relationship' items in the SwiftData project challenge...wondering if you're onto something here.

https://www.hackingwithswift.com/forums/books/hacking-with-swiftdata-delete-a-sight/24119

3      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.