C. Meadows
C. Meadows

Reputation: 185

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

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?

Upvotes: 10

Views: 9516

Answers (5)

M Hamayun zeb
M Hamayun zeb

Reputation: 518

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

Upvotes: 0

jfulton
jfulton

Reputation: 77

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
}

Upvotes: 1

Mohammad Kamran Usmani
Mohammad Kamran Usmani

Reputation: 878

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;
}

Upvotes: 2

Luke Van In
Luke Van In

Reputation: 5265

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

Upvotes: 14

pesch
pesch

Reputation: 1996

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]")
    ]

Upvotes: 3

Related Questions