Iterate over a NSSet - SwiftUI 2.0
Asked Answered
J

2

7

I am starting to look at CoreData and have two entities. These form a many to many relationship between Player:

enter image description here

and Team:

enter image description here

I am trying to iterate through the players of a team using a ForEach inside a list but I can't seem to get it to work. The code I have been trying is below. Any other solutions I try I get errors such as 'Value of type NSObject has no member name'

struct PlayersView: View{
    var team: Team
    
    var body: some View{
        NavigationView{
            List{
                ForEach(Array(team.people! as Set), id: \.self){ player in
                    Text("\(player.name)" ) //Error on this line
                    
                }
            }
        }
    }
}

Any ideas? I think I need to change the player type back to the Player Object but I am not sure

Jointure answered 13/1, 2021 at 15:31 Comment(0)
P
14

Try the following (compiled with Xcode 12.1 / iOS 14.1)

ForEach(Array(team.people as? Set<Player> ?? []), id: \.self){ player in
    Text("\(player.name ?? "")" )
}

Caution: try to avoid force unwrapping for optionals, very often it will result in run-time crash.

Plafond answered 13/1, 2021 at 15:48 Comment(0)
I
8

While the solution that @Asperi suggested works, there is an issue. ForEach is deprived of reactive updates when displaying a collection of values backed by NSSet. If people change, ForEach will not be able to pick up that change and refresh itself right away.

This is because NSSet is a class and SwiftUI can only reactively update properties that are structures, as noted here.

I believe the better architecture would be to use fetch request to get all the players associated with a given team. That fetch request would have to be configured inside of view’s initializer method, with NSPredicate that specifies that we want players for a certain team. If you have a many-to-many relationship, use ANY operator in your NSPredicate.

struct PlayersView: View {
    let team: Team
    
    @FetchRequest private var people: FetchedResults<Player>
    
    init(team: Team) {
        self.team = team
        
        _people = FetchRequest(
            entity: Player.entity(),
            sortDescriptors: [NSSortDescriptor(keyPath: \Player.name, ascending: true)],
            predicate: NSPredicate(format: "team == %@", team)
        )
    }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(people) { player in
                    Text("\(player.name ?? "")")
                }
            }
        }
    }
}

Now we can plug the results of that fetch request right into ForEach and don’t have to do any type casting. Even better, SwiftUI can now automatically update contents of that ForEach based on changes that it notices.

Ioannina answered 20/1, 2022 at 5:22 Comment(1)
Why the hell is this not the accepted answer!!Tussore

© 2022 - 2024 — McMap. All rights reserved.