How to Get Vcf Data with Contact Images Using Cncontactvcardserialization Datawithcontacts: Method

How to get VCF data with contact images using CNContactVCardSerialization dataWithContacts: method?

As a workaround you can create PHOTO field inside of VCard.

NSError* error = nil;
NSData* vCardData = [CNContactVCardSerialization dataWithContacts:@[contact] error:&error];
NSString* vcString = [[NSString alloc] initWithData:vCardData encoding:NSUTF8StringEncoding];
NSString* base64Image = contact.imageData.base64Encoding;
NSString* vcardImageString = [[@"PHOTO;TYPE=JPEG;ENCODING=BASE64:" stringByAppendingString:base64Image] stringByAppendingString:@"\n"];
vcString = [vcString stringByReplacingOccurrencesOfString:@"END:VCARD" withString:[vcardImageString stringByAppendingString:@"END:VCARD"]];
vCardData = [vcString dataUsingEncoding:NSUTF8StringEncoding];

For some reasons CNContactVCardSerialization does not use any photo of contact. VCard after serialization is looks like:

BEGIN:VCARD
VERSION:3.0
PRODID:-//Apple Inc.//iPhone OS 9.3.2//EN
N:Contact;Test;;;
FN: Test Contact
END:VCARD

After insertion the PHOTO field inside VCard you will get

BEGIN:VCARD
VERSION:3.0
PRODID:-//Apple Inc.//iPhone OS 9.3.2//EN
N:Contact;Test;;;
FN: Test Contact
PHOTO;TYPE=JPEG;ENCODING=BASE64:<photo base64 string>
END:VCARD

After this insertion contact will looks fine in CNContactViewController

Contact image doesn't send when contact saved by CNContact

I figured out the problem. When I use CNContactVCardSerialization.dataWithContacts(), I get the image as NSData. If I convert it as UIImageJPEGRepresentation/UIImageJPEGRepresentation and then append the image with the vCard string as @"PHOTO;BASE64;ENCODING=b;TYPE=JPEG%@" and then append the image data, then picture is sent with vCard.

CNContactVCardSerialization.dataWithContacts giving exception

I found out my mistake. On the keys to fetch contact, I was missing CNContactVCardSerialization.descriptorForRequiredKeys(). After adding it, the code is working flawlessly.

How to create vCard/vcf file to use in share sheet?

The trick here is to save the contact to a VCard (.vcf) file using CNContactVCardSerialization.dataWithContacts, then pass the file URL to the UIActivityViewController. The activity view controller detects the VCard format from the file extension, and shows the apps where the format is supported (e.g. Messages, Mail, Notes, Airdrop, etc)

Example:

@IBAction func buttonTapped(button: UIButton) {

let contact = createContact()

do {
try shareContacts([contact])
}
catch {
// Handle error
}
}

func shareContacts(contacts: [CNContact]) throws {

guard let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first else {
return
}

var filename = NSUUID().UUIDString

// Create a human friendly file name if sharing a single contact.
if let contact = contacts.first where contacts.count == 1 {

if let fullname = CNContactFormatter().stringFromContact(contact) {
filename = fullname.componentsSeparatedByString(" ").joinWithSeparator("")
}
}

let fileURL = directoryURL
.URLByAppendingPathComponent(filename)
.URLByAppendingPathExtension("vcf")

let data = try CNContactVCardSerialization.dataWithContacts(contacts)

print("filename: \(filename)")
print("contact: \(String(data: data, encoding: NSUTF8StringEncoding))")

try data.writeToURL(fileURL, options: [.AtomicWrite])

let activityViewController = UIActivityViewController(
activityItems: [fileURL],
applicationActivities: nil
)

presentViewController(activityViewController, animated: true, completion: {})
}

func createContact() -> CNContact {

// Creating a mutable object to add to the contact
let contact = CNMutableContact()

contact.imageData = NSData() // The profile picture as a NSData object

contact.givenName = "John"
contact.familyName = "Appleseed"

let homeEmail = CNLabeledValue(label:CNLabelHome, value:"john@example.com")
let workEmail = CNLabeledValue(label:CNLabelWork, value:"j.appleseed@icloud.com")
contact.emailAddresses = [homeEmail, workEmail]

contact.phoneNumbers = [CNLabeledValue(
label:CNLabelPhoneNumberiPhone,
value:CNPhoneNumber(stringValue:"(408) 555-0126"))]

return contact
}

Activity view controller showing apps which support VCard format

VCard contact appearing in Messages app

ios: I want to create .vcf/vcard which contains all my contacts. How do I do this?

I found a way to do this finally.

func createContact() -> [CNContact] {

let contactStore = CNContactStore()
var contacts = [CNContact]()

let fetchRequest = CNContactFetchRequest(keysToFetch:[CNContactVCardSerialization.descriptorForRequiredKeys()])

do {
try contactStore.enumerateContactsWithFetchRequest(fetchRequest) {
(contact123, stop) in
// Array containing all unified contacts from everywhere
contacts.append(contact123)}
}
catch {
print("unable to fetch contacts")
}

return contacts

}

The above code will create a list of all your contacts which consists of all the details. You can also select only the keys you want to fetch from all the contacts(This may act as a filter for your search).

Then simply create a .vcard file using the CNContactVCardSerialization

func shareContacts(contacts: [CNContact]) throws {

    guard let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first else {
return
}

var filename = NSUUID().UUIDString

// Create a human friendly file name if sharing a single contact.
if let contact = contacts.first where contacts.count == 1 {

if let fullname = CNContactFormatter().stringFromContact(contact) {
filename = fullname.componentsSeparatedByString(" ").joinWithSeparator("")
}
}

let fileURL = directoryURL
.URLByAppendingPathComponent(filename)
.URLByAppendingPathExtension("vcf")

let data: NSData?
do {
data = try CNContactVCardSerialization.dataWithContacts(contacts)
print("filename: \(filename)")
print("contact: \(String(data: data!, encoding: NSUTF8StringEncoding))")

do {
try data!.writeToURL(fileURL, options: [.AtomicWrite])
}
catch {
print("eeror\(error)")
}
}
catch {
print("error\(error)")
}

let activityViewController = UIActivityViewController(
activityItems: [fileURL],
applicationActivities: nil
)

presentViewController(activityViewController, animated: true, completion: {})
}

The above code will give you an option to share it via Mail, whatsapp etc etc.

Hope this helps someone.

Export Picture in VCard Swift

From the response posted above (ABPersonCreatePeopleInSourceWithVCardRepresentation):

Address Book supports vCard version 3.0.

VCARD > PHOTO - An image or photograph of the individual associated with the vCard. It may point to an external URL or may be embedded in the vCard as a Base64 encoded block of text.

2.1: PHOTO;JPEG:http://example.com/photo.jpg
2.1: PHOTO;JPEG;ENCODING=BASE64:[base64-data]
3.0: PHOTO;TYPE=JPEG;VALUE=URI:http://example.com/photo.jpg
=> 3.0: PHOTO;TYPE=JPEG;ENCODING=b:[base64-data] // Works in iOS/macOS
4.0: PHOTO;MEDIATYPE=image/jpeg:http://example.com/photo.jpg
4.0: PHOTO:data:image/jpeg;base64,[base64-data]


How to create a VCF 3.0 card for iOS/macOS

Testing shows the 2nd way works: encode the JPEG photo using base64.

PHOTO;ENCODING=b;TYPE=JPEG:\(base64EncodedImage)

Working PoC:

struct VCFBuilder {
private let name: String? = "Name1"
private let company: String? = "Company"
private let title: String? = "Title1"
private let phone: String? = "555-1234-1234"
private let address: String? = "aaa"
private let notes: String? = "PROnotes"
private let web: String? = "web"
private let blog: String? = "bolgg"
private let socialMedia1: String? = "social1"
private let socialMedia2: String? = "social2"
private let socialMedia3: String? = "social3"
private let email: String? = "email@mail.com"
private let imageUrl: String? = "http://0.gravatar.com/avatar/3f009d72559f51e7e454b16e5d0687a1"

func vcf() -> String? {

do {
let url = URL(string: self.imageUrl!)
let data = try Data(contentsOf: url!).base64EncodedString()
return generateVCF(withEncodedImage: data)
} catch {
print(error)
return nil
}
}

// TODO: Don't !
private func generateVCF(withEncodedImage imageBase64: String) -> String {
return """
BEGIN:VCARD
VERSION:3.0
N:\(self.name!);
FN:\(self.name!)
ORG:\(self.company!)
TITLE:\(self.title!)
TEL;TYPE=WORK,VOICE:\(self.phone!)
ADR;TYPE=WORK:;;\(self.address!)
NOTE:\(self.notes!)
PHOTO;ENCODING=b;TYPE=JPEG:\(imageBase64)
item1.URL:\(self.web!)
item2.URL:\(self.blog!)
item3.URL:\(self.socialMedia1!)
item4.URL:\(self.socialMedia2!)
item5.URL:\(self.socialMedia3!)
EMAIL;TYPE=PREF,INTERNET:\(self.email!)
END:VCARD
"""
}
}

let vcf = VCFBuilder().vcf()
print(vcf)


How to import VCF 3.0 card in iOS/macOS:

  • Manually using Contacts.app
  • iOS <= 9: ABPersonCreatePeopleInSourceWithVCardRepresentation(_:_:) (docs)
  • iOS >= 9: CNContactVCardSerialization.contacts(with:) (docs)
guard let data = VCFBuilder().vcf()?.data(using: .utf8),
let contact = try? CNContactVCardSerialization.contacts(with: data).first
else { return }

print(contact.imageData!.base64EncodedString())

Imported using iOS' Contacts.app:

Sample Image

Exporting all contacts in one .vcf file using Contacts.Framework in Objective - C

You can use this method to get all the contacts in .vcf file. It return the same output that you get using AddressBook.framework.

- (void)getContacts {
NSMutableArray *contactsArray=[[NSMutableArray alloc] init];
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!granted) {
dispatch_async(dispatch_get_main_queue(), ^{
});
return;
}
NSMutableArray *contacts = [NSMutableArray array];

NSError *fetchError;

CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[[CNContactVCardSerialization descriptorForRequiredKeys], [CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName]]];

BOOL success = [store enumerateContactsWithFetchRequest:request error:&fetchError usingBlock:^(CNContact *contact, BOOL *stop) {
[contacts addObject:contact];
}];
if (!success) {
NSLog(@"error = %@", fetchError);
}

// you can now do something with the list of contacts, for example, to show the names

CNContactFormatter *formatter = [[CNContactFormatter alloc] init];

for (CNContact *contact in contacts) {

[contactsArray addObject:contact];
// NSString *string = [formatter stringFromContact:contact];

//NSLog(@"contact = %@", string);
}

//NSError *error;
NSData *vcardString =[CNContactVCardSerialization dataWithContacts:contactsArray error:&error];

NSString* vcardStr = [[NSString alloc] initWithData:vcardString encoding:NSUTF8StringEncoding];
NSLog(@"vcardStr = %@",vcardStr);

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *folderPath = [paths objectAtIndex:0];
NSString *filePath = [folderPath stringByAppendingPathComponent:@"Contacts.vcf"];
[vcardStr writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}];
}


Related Topics



Leave a reply



Submit