0

I have a class that implements the Equatable protocol, and it uses a UUID field for comparison:

class MemberViewModel {
    private static var entities: [MemberViewModel] = []
    private var entity: Member

    let id = UUID()

    init(_ entity: Member) {
        self.entity = entity
    }

    static func members() -> [MemberViewModel] {
        entities.removeAll()

        try? fetch().forEach { member in
            entities.append(MemberViewModel(member))
        }

        return entities
    }
}

extension MemberViewModel: Equatable {
    static func == (lhs: MemberViewModel, rhs: MemberViewModel) -> Bool {
        return lhs.id == rhs.id
    }
}

I then have a view that creates icons that when tapped should display a stroke to denote it was "selected":

struct MyView: View {
    @State var selectedMember: MemberViewModel? = nil

    var body: some View {
        let members = MemberViewModel.members()

        ScrollView(.horizontal, showsIndicators: true) {
            HStack(alignment: .top, spacing: 4) {
                ForEach (members, id: \.id) { member in
                    var isSelected: Bool = selectedMember == member

                    Circle()
                        .fill(Color(.red))
                        .frame(width: 48, height: 48)
                        .overlay() {
                            if isSelected {
                                Circle()
                                    .stroke(Color(.black), lineWidth: 2)
                            }
                        }
                        .onTapGesture { selectedMember = member }
                }
            }
        }
    }
}

I have tried setting isSelected multiple ways, including the following from another SO question:

let isSelected = Binding<Bool>(get: { self.selectedMember == member }, set: { _ in })

When debugging using breakpoints, the value of isSelected is always false.

I'm using XCode Version 14.0.1 (14A400), and Swift 5.7.

5
  • Take a look at how Picker implemented https://developer.apple.com/documentation/swiftui/picker you will get much more ideas Commented Oct 28, 2022 at 2:47
  • I suspect you have a typo in MemberViewModel, you probably wanted static var entities: [Member] = [] and similarly in static func members() -> [Member]. Currently you have a MemberViewModel that contains an array of MemberViewModel that contains an array of MemberViewModel, that contains.....etc..,etc... Make the extension Member: Equatable {...} Commented Oct 28, 2022 at 2:56
  • @workingdogsupportUkraine I'm not sure what that has to do with comparing the selectedMember to member, given that the Equatable is using the UUID that is defined in MemberViewModel, but it is by design that I am abstracting away access to the Member object. Commented Oct 28, 2022 at 3:13
  • My point was, re-structure your code. But suit yourself, use the strange one you have. Commented Oct 28, 2022 at 3:26
  • What is selectedMember at the point where you do the comparison? I wonder if the fact that it is a property wrapped variable is screwing it up. Commented Oct 28, 2022 at 8:17

2 Answers 2

1

Put let members = MemberViewModel.members() just before var body: some View {...}, not inside it, like this:

let members = MemberViewModel.members()  // <-- here 

var body: some View {
  ...
} 

Due to your weird code structure, let members = MemberViewModel.members() gets re-done evey time the body is refreshed. And since the UUID is re-generated .... you can guess that the ids now are all over the place and not equal to the selectedMember.

In other words, the object comparison is working, but you are not comparing what you think.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. The body updating and regenerating the UUIDs never occurred to me, but moving the call outside of the body did the trick. Slava Ukraini!
0

Use the sample below hope you get the idea.

Model

struct Member {
    
    let id = UUID()
    
}

extension Member: Equatable {
    static func == (lhs: Member, rhs: Member) -> Bool {
        return lhs.id == rhs.id
    }
}

class MemberViewModel: ObservableObject {
    // each time you update the entities, view vill be updated
    @Published var entities: [Member] = []
    
    func fetchMembers() {
        entities.removeAll()

        // your code to fetch members
        try? fetch().forEach { member in
           entities.append(member)
        }
        
    }
}

MyView

struct MyView: View {
    
    @StateObject private var modelData = MemberViewModel()
    @State private var selectedMember: Member? = nil

    var body: some View {
        
        ScrollView(.horizontal, showsIndicators: true) {
            HStack(alignment: .top, spacing: 4) {
                ForEach (modelData.entities, id: \.id) { member in

                    Circle()
                        .fill(Color(.red))
                        .frame(width: 48, height: 48)
                        .overlay() {
                            if selectedMember == member {
                                Circle()
                                    .stroke(Color(.black), lineWidth: 2)
                            }
                        }
                        .onTapGesture { selectedMember = member }
                }
            }
        }.onAppear {
            modelData.fetchMembers()
        }
    }
}

1 Comment

This answer needs some text to explain what is going on.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.