C. Meadows C. Meadows - 4 months ago 269
iOS Question

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: "john.doe@email.com")
]

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?

Answer

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