Voltron
Voltron

Reputation: 441

SwiftUI List only using first item of given array

I have a simple view below to display all of the contacts for the user:

struct AllContactsView: View {
    static let withState: some View = AllContactsView().environmentObject(AllContactsProvider())
    @EnvironmentObject var contactsProvider: AllContactsProvider
    let title: UserListType = .selectInvitees

    var body: some View {
        List(self.contactsProvider.invitees) { invite in
            self.row(for: invite)
        }
        .navigationBarTitle(Text(self.title.rawValue))
        .onAppear(perform: self.contactsProvider.fetch)
    }

    func row(for invite: Invitee) -> some View {
        // everything below is only printed out once!
        print(self.contactsProvider.invitees.prettyPrinted) // shows correct array of contacts
        print(type(of: self.contactsProvider.invitees)) // this is indeed an array
        print(invite) // prints out the first item in the array (which is expected on first pass)
        return UserRow(invitee: invite)
    }
}

I am manipulating the array of CNContacts I get like this to an array of Invitees, which is what I am attempting to display in my list:

self?.invitees = contacts.asSimpleContacts.map({ $0.asUser.asInvitee })

Using the supporting functions and extensions below:

// Contact Value simplified so we can pass it around as a value type.
public struct SimpleContact: Hashable, Codable {
    let firstName: String
    let lastName: String
    let emails: [String]
    let phoneNumbers: [PhoneNumber]

    var fullName: String { "\(self.firstName) \(self.lastName)" }

    var asUser: User {
        User(
            id: Constants.unsavedID,
            username: self.fullName,
            picURL: "al",
            email: self.emails.first ?? "",
            phone: self.phoneNumbers.first ?? "",
            created: Date().timeIntervalSince1970
        )
    }
}


extension CNContact {
    /// Returns the `SimpleContact` representation of `self`
    var asSimpleContact: SimpleContact {
        SimpleContact(
            firstName: self.givenName,
            lastName: self.familyName,
            emails: self.emailAddresses.map({ String($0.value) }),
            phoneNumbers: self.phoneNumbers.map({ Authentication.sanitize(phoneNo: $0.value.stringValue) })
        )
    }
}

extension Array where Element == CNContact {
    /// Returns the `SimpleContact` mapping of `self`
    var asSimpleContacts: [SimpleContact] { self.map({ $0.asSimpleContact }) }
}

public struct User: Hashable, Codable, Identifiable {
    public let id: String
    let username: String
    let picURL: String
    let email: String
    let phone: String
    let created: Double

    var asInvitee: Invitee { Invitee(user: self, isGoing: false) }
}

The contacts are populated into self.contactsProvider.invitees as expected following self.contactsProvider.fetch(). However, SwiftUI is displaying self.contactsProvider.invitees.count instances of self.contactsProvider.invitees.first, rather than each contact. I have compared my approach below to other examples online and can't seem to find where I went wrong. I have determined that the issue lies somewhere with the contacts manipulation - when I supply a mocked array of invitees, everything works as expected, despite things compiling and running as expected without the mocks, and printing and debugging not revealing anything.

Any help would be appreciated.

Upvotes: 4

Views: 2300

Answers (2)

Nick
Nick

Reputation: 2369

I just ran into this issue, to answer a bit more clearly:

Each row in a SwiftUI list should be generated with a unique ID.

If you are using the List() function to create the view, make sure you are specifying an id

struct MenuView: View {
var body: some View {
    VStack {
        Section(header: MenuSectionHeader()) {
            //item.sku is the unique identifier for the row
            List(Menu.allItems, id:\.sku) { item in 
                MenuItemRow(item)
            }
        }
    }
}

Upvotes: 4

Voltron
Voltron

Reputation: 441

For anyone who runs across something similar, the problem was that the ID I was instantiating the objects with was not unique. Curious that this would be the expected behavior if that error is made, but thats what it was.

Upvotes: 2

Related Questions