I am trying to make CNMutableContact "Codable". I have already built the encode function (see below), but I am getting some issues to decode array such as postalAddresses, emailAddresses, etc.
Here is my encode function:
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.contact.contactType.rawValue, forKey: .contactType)
try container.encode(self.contact.namePrefix, forKey: .namePrefix)
try container.encode(self.contact.givenName, forKey: .givenName)
try container.encode(self.contact.middleName, forKey: .middleName)
try container.encode(self.contact.familyName, forKey: .familyName)
try container.encode(self.contact.previousFamilyName, forKey: .previousFamilyName)
try container.encode(self.contact.nameSuffix, forKey: .nameSuffix)
try container.encode(self.contact.nickname, forKey: .nickname)
try container.encode(self.contact.jobTitle, forKey: .jobTitle)
try container.encode(self.contact.departmentName, forKey: .departmentName)
try container.encode(self.contact.organizationName, forKey: .organizationName)
var postalAddresses: [String:String] = [:]
self.contact.postalAddresses.forEach { postalAddress in
postalAddresses[postalAddress.label ?? "postal\(String(describing: index))"] = (CNPostalAddressFormatter.string(from: postalAddress.value, style: .mailingAddress))
}
try container.encode(postalAddresses, forKey: .postalAddresses)
var emailAddresses: [String:String] = [:]
self.contact.emailAddresses.forEach { emailAddress in
emailAddresses[emailAddress.label ?? "email\(String(describing: index))"] = (emailAddress.value as String)
}
try container.encode(emailAddresses, forKey: .emailAddresses)
var urlAddresses: [String:String] = [:]
self.contact.urlAddresses.forEach { urlAddress in
urlAddresses[urlAddress.label ?? "url\(String(describing: index))"] = (urlAddress.value as String)
}
try container.encode(urlAddresses, forKey: .urlAddresses)
var phoneNumbers: [String:String] = [:]
self.contact.phoneNumbers.forEach { phoneNumber in
phoneNumbers[phoneNumber.label ?? "phone\(String(describing: index))"] = phoneNumber.value.stringValue
}
try container.encode(phoneNumbers, forKey: .phoneNumbers)
var socialProfiles: [String:String] = [:]
self.contact.socialProfiles.forEach { socialProfile in
socialProfiles[socialProfile.label ?? "social\(String(describing: index))"] = socialProfile.value.urlString
}
try container.encode(socialProfiles, forKey: .socialProfiles)
try container.encode(self.contact.birthday, forKey: .birthday)
try container.encode(self.contact.note, forKey: .note)
}
As you can see, I encode the postalAddresses this way:
var postalAddresses: [String:String] = [:]
self.contact.postalAddresses.forEach { postalAddress in
postalAddresses[postalAddress.label ?? "postal\(String(describing: index))"] = (CNPostalAddressFormatter.string(from: postalAddress.value, style: .mailingAddress))
}
try container.encode(postalAddresses, forKey: .postalAddresses)
But I have some difficulties to understand exactly how to decode it. Here is my decode function (not complete):
init(from decoder: Decoder) throws {
let decodedContact = try decoder.container(keyedBy: CodingKeys.self)
id = try decodedContact.decode(UUID.self, forKey: .id)
contactIdentifier = try decodedContact.decode(String.self, forKey: .contactIdentifier)
contact = CNMutableContact()
var intContactType = try decodedContact.decode(Int.self, forKey: .contactType)
if intContactType == 0 {
contact.contactType = CNContactType.person
} else {
contact.contactType = CNContactType.organization
}
contact.namePrefix = try decodedContact.decode(String.self, forKey: .namePrefix)
contact.givenName = try decodedContact.decode(String.self, forKey: .givenName)
contact.middleName = try decodedContact.decode(String.self, forKey: .middleName)
contact.familyName = try decodedContact.decode(String.self, forKey: .familyName)
contact.previousFamilyName = try decodedContact.decode(String.self, forKey: .previousFamilyName)
contact.nameSuffix = try decodedContact.decode(String.self, forKey: .nameSuffix)
contact.nickname = try decodedContact.decode(String.self, forKey: .nickname)
contact.jobTitle = try decodedContact.decode(String.self, forKey: .jobTitle)
contact.departmentName = try decodedContact.decode(String.self, forKey: .departmentName)
contact.organizationName = try decodedContact.decode(String.self, forKey: .organizationName)
// MISSING ARRAYS
let postalAddresses = try decodedContact.decode([String:String], forKey: .postalAddresses)
contact.birthday = try decodedContact.decode(DateComponents.self, forKey: .birthday)
contact.note = try decodedContact.decode(String.self, forKey: .note)
}
Note: the decode function returns an error with the postalAdresses decoding line.
Can you help me understand if my approach is correct and how to decode arrays?
Thanks
I have tried different ways to decode postalAddresses, but always getting an error.
It's not necessary to reinvent the wheel. CN(Mutable)Contact
conforms to NSSecureCoding
, it can be serialized to Data
.
And in Swift there is the PropertyWrapper pattern which exposes the instance and can perform the en-/decoding stuff under the hood
@propertyWrapper
struct CodableContact {
var wrappedValue: CNMutableContact
}
extension CodableContact: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
guard let contact = try NSKeyedUnarchiver.unarchivedObject(ofClass: CNMutableContact.self, from: data) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Invalid contact"
)
}
wrappedValue = contact
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let data = try NSKeyedArchiver.archivedData(withRootObject: wrappedValue, requiringSecureCoding: true)
try container.encode(data)
}
}
In your struct declare
struct MyType: Codable {
@CodableContact var contact: CNMutableContact
}