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

5

10

I'm new to swift and methods I am finding are deprecated regarding my issue. I'm building a directory app and I'm pulling contact data from an API, not from the phone's address book.

In iOS, if you go to your address book, you can select a contact and choose 'Share Contact' which brings up a share sheet. I want this exact functionality in my app.

I think I've got Share Sheets figured out, and here's my code for that:

    @IBAction func actShare(sender: AnyObject) {

    let activityViewController = UIActivityViewController(activityItems: ["text" as NSString], applicationActivities: nil)
    presentViewController(activityViewController, animated: true, completion: {})
}

I want to to change "text" as NSString to be a vCard, as that is the object that iOS shares from the address book, right? Assuming I'm right, I want to create a vCard from my own app's contact object in order to share it to appropriate apps (email, sms, etc).

How can I achieve that in Swift? If I'm wrong, please correct me and show me what I need to do. Thanks.

EDIT: Okay, here's my changes.

@IBAction func actShare(sender: AnyObject) {
    do {
        var contactData = NSData()
        try contactData = CNContactVCardSerialization.dataWithContacts([createContact()])

        let activityViewController = UIActivityViewController(activityItems: [contactData as NSData], applicationActivities: nil)
        presentViewController(activityViewController, animated: true, completion: {})
    } catch {
        print("CNContactVCardSerialization cannot save address")
    }

and

func createContact() -> CNMutableContact {
    let contactCard = CNMutableContact()
    contactCard.givenName = "John"
    contactCard.familyName = "Doe"
    contactCard.emailAddresses = [
        CNLabeledValue(label: CNLabelWork, value: "[email protected]")
    ]

    return contactCard
}

However, when I click the share button and it brings up my share sheet, I select the application I want to share to and it doesn't add/attach the contact data as intended. How do I accomplish this?

Fishbowl answered 12/7, 2016 at 17:31 Comment(0)
A
14

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:"[email protected]")
    let workEmail = CNLabeledValue(label:CNLabelWork, value:"[email protected]")
    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

Acherman answered 12/7, 2016 at 20:58 Comment(4)
Thank you - I just updated my initial post with a new question. I think your answer will resolve it, though. I'll try it out and get back to you. Much appreciated!Fishbowl
Thank you so much! That worked perfectly. I understand it now. I do have one last question, however. When I share the contact, the .vcf has a long name like "6DD3C04D-08C1-44C7-8A7E-F08C6F0CB5CD.vcf". Can I get that changed to JohnnyAppleseed.vcf, just like you get when you share a contact from your own address book? EDIT: Oh, I got it! I overlooked .URLByAppendingPathComponent(NSUUID().UUIDString). Thank you so much!Fishbowl
CNContactFormatter will help you there. I have updated the code to show how you might use it. It will fall back to the UUID string if the contact does not have a name (for whatever reason), or if you share multiple contacts at once (in which case using the first name is probably not desired).Acherman
Glad to help! Please mark the answer as accepted if it addressed your question.Acherman
W
3

You may use a CNContact (requires iOS 9):

let contact = CNMutableContact()
    contact.givenName = "John"
    contact.familyName = "Doe"
    contact.emailAddresses = [
      CNLabeledValue(label: CNLabelWork, value: "[email protected]")
    ]
Woehick answered 12/7, 2016 at 18:21 Comment(0)
D
2

It is very simple to create contact from your iOS app and share over the external apps.

First you need to create Share Button Action like below :-

-(void)shareContactAction:(UIButton*)sender
{  
    CNMutableContact *selectedCon = [[CNMutableContact alloc] init];
    selectedCon.givenName = @"ABC";
    selectedCon.familyName = @"XYZ";
    NSString *phoneNum = @"+91-XXXXXXXXXXX"
    CNLabeledValue *contactNum1 = [CNLabeledValue labeledValueWithLabel:CNLabelHome value:[CNPhoneNumber phoneNumberWithStringValue:phoneNum]];
    selectedCon.phoneNumbers = @[contactNum1];
    NSString *url=[self saveContactDetailsToDocDir:selectedCon];
    UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[@"Contact", [NSURL fileURLWithPath:url]] applicationActivities:nil];
    [self presentViewController:activityViewController animated:YES completion:nil];     
}

Than you can call the function below which will return the path of the contact card in above button event.

- (NSString *)saveContactDetailsToDocDir:(CNContact *)contact {

    NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory,     NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString * contactCardPath = [documentsDirectory stringByAppendingString:@"/vCardName.vcf"];
    NSArray *array = [[NSArray alloc] initWithObjects:contact, nil];
    NSError *error;
    NSData *data = [CNContactVCardSerialization dataWithContacts:array error:&error];
    [data writeToFile:contactCardPath atomically:YES];

    return contactCardPath;
}
Duckworth answered 1/6, 2017 at 7:39 Comment(0)
M
1

Updated for Swift 4:

@IBAction func buttonTapped(button: UIButton) {

    let contact = createContact()

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

func shareContacts(contacts: [CNContact]) throws {

    guard let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
    return
    }

    var filename = NSUUID().uuidString

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

    if let fullname = CNContactFormatter().string(from: contact) {
        filename = fullname.components(separatedBy: " ").joined(separator: "")
    }
    }

    let fileURL = directoryURL
    .appendingPathComponent(filename)
    .appendingPathExtension("vcf")

    let data = try CNContactVCardSerialization.data(with: contacts)

    print("filename: \(filename)")
    print("contact: \(String(describing: String(data: data, encoding: String.Encoding.utf8)))")

    try data.write(to: fileURL, options: [.atomicWrite])

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

    present(activityController, animated: true, completion: nil)
}

func createContact() -> CNContact {

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

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

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

    let homeEmail = CNLabeledValue(label:CNLabelHome, value:"[email protected]")
    let workEmail = CNLabeledValue(label:CNLabelWork, value:"[email protected]")
    contact.emailAddresses = [homeEmail, workEmail]

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

    return contact
}
Mariken answered 4/10, 2018 at 16:30 Comment(1)
These methods work for the most part but I can't get a thumbnail or contact image to show. I get this error QLThumbnailErrorDomain Code=0 "Could not generate a thumbnail" Im using let image = UIImage(named: "ExistingContactPlaceHolderPic") contact.imageData = image?.pngData() Any ideas?Sternum
C
0

Successful approach:

First get all Contacts then save it and after this make .vcf file and also share the .vcf in Emails, WhatsApp, Messaging, Skype and save in iPhone files....

enter image description here

@IBAction func vcfPressed(_ sender: UIButton) {

    let arrayContacts = self.fetchAllContacts()
    self.saveContactsInDocument(contacts: arrayContacts)
     let contact = fetchAllContacts()
     do {
     try shareContacts(contacts: contact)
     }
     catch {
     }
}


func shareContacts(contacts: [CNContact]) throws {

    guard let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
 return}

    var filename = NSUUID().uuidString
    if let contact = contacts.first, contacts.count == 1 {
    if let fullname = CNContactFormatter().string(from: contact) {
    filename = fullname.components(separatedBy: " ").joined(separator: "")}}
    let fileURL = directoryURL.appendingPathComponent(filename).appendingPathExtension("vcf")
    let data = try CNContactVCardSerialization.data(with: contacts)
    print("filename: \(filename)")
    print("contact: \(String(data: data, encoding: String.Encoding.utf8))")
    try data.write(to: fileURL, options: [.atomicWrite])
    let activityViewController = UIActivityViewController(activityItems: [fileURL],applicationActivities: nil)
    present(activityViewController, animated: true, completion: {})
 }


func fetchAllContacts() -> [CNContact] {

    var contacts : [CNContact] = []
    let contactStore = CNContactStore()
    let fetchReq = CNContactFetchRequest.init(keysToFetch: [CNContactVCardSerialization.descriptorForRequiredKeys()])
    do {
        try contactStore.enumerateContacts(with: fetchReq) { (contact, end) in
            contacts.append(contact)
        }}
    catch  {print("Failed to fetch")}
    return contacts
}

enter image description here

Cavicorn answered 20/8, 2020 at 7:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.