Reputation: 441
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
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
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