Yoorque
Yoorque

Reputation: 26

SwiftData Query

Been learning SwiftData and ran into a hiccup. Let's say I have to data types User and Job

@Model
class User: Identifiable {
    var id = UUID().uuidString
    var name: String
    var jobs = [Job]()
    
    init(name: String) {
        self.name = name
        self.jobs = jobs
    }
}
@Model
class Job: Identifiable {
    var id = UUID().uuidString
    var name: String
    var user: User?
    
    init(name: String, user: User? = nil) {
        self.name = name
        self.user = user
    }
}

Now, I have a TabView with 2 tabs. One is PeopleView with a list of people and the other is the JobsView with a list of jobs. My idea is to add Users and then assign them a Job, from a list. Jobs would also be added

List of Users List of Jobs

Tapping on a name in a list of people, I get this menu, which lists all available jobs.

Jobs menu

When I choose one, it gets assigned to that person, which is expected. Now, since @Model are a class, selecting the same job for a different person, reassigns that job to the second person and it gets removed from the first person jobs array. This is due to @Model being a class and that is fine, but...

On the other hand, if I add a new instance of a job to a person, so they could all have a same job (task), the array of jobs in the second tab also gets an additional job with the same name. Seems like adding a job to a person.jobs array, also adds it to database in the array of Jobs.

Duplicated jobs

Does Querying jobs aggregates all possible jobs from all people in the database? How can I have only those jobs I added in the Jobs tab? @Query var jobs: [Job]

person.jobs.append(job)
try? ctx.save()
let copy = Job(name: job.name)
person.jobs.append(copy)
try? ctx.save()

Upvotes: 0

Views: 148

Answers (2)

Yoorque
Yoorque

Reputation: 26

The solution was to make sure there is a many-to-many relationship between the two.

@Model
class Job: Identifiable {
  var id = UUID().uuidString
  var name: String
  var users: [User] = [] <——- change

  init(name: String) {
    self.name = name
}

}

Upvotes: 0

malhal
malhal

Reputation: 30617

First you need a relationship, e.g.

@Model
final class AnimalCategory {
    @Attribute(.unique) var name: String
    // `.cascade` tells SwiftData to delete all animals contained in the 
    // category when deleting it.
    @Relationship(deleteRule: .cascade, inverse: \Animal.category)
    var animals = [Animal]()
    
    init(name: String) {
        self.name = name
    }
}

https://developer.apple.com/documentation/swiftdata/defining-data-relationships-with-enumerations-and-model-classes

Then you need another query, e.g.

struct ArticleView: View {
    
    @Environment(\.modelContext) private var modelContext    
    @Query private var articleStates: [ArticleState]


    let article: Article
    
    init(article: Article) {
        self.article = article
        _articleStates = Query(filter: #Predicate { $0.articleID == article.id } )
    }
    
    var body: some View {
        ... // SwiftUI view that depends on query data.
    }
}

https://developer.apple.com/tutorials/app-dev-training/swiftdata-sorting-and-filtering

Upvotes: -2

Related Questions