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: "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?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
C. Meadows
  • 145
  • 1
  • 11

5 Answers5

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:"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

Luke Van In
  • 5,215
  • 2
  • 24
  • 45
  • 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! – C. Meadows Jul 12 '16 at 21:07
  • 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! – C. Meadows Jul 12 '16 at 21:16
  • `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). – Luke Van In Jul 12 '16 at 21:26
  • Glad to help! Please mark the answer as [accepted](http://stackoverflow.com/help/accepted-answer) if it addressed your question. – Luke Van In Jul 12 '16 at 21:29
3

You may use a CNContact (requires iOS 9):

let contact = CNMutableContact()
    contact.givenName = "John"
    contact.familyName = "Doe"
    contact.emailAddresses = [
      CNLabeledValue(label: CNLabelWork, value: "john.doe@email.com")
    ]
pesch
  • 1,976
  • 2
  • 17
  • 29
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;
}
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:"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
}
jfulton
  • 77
  • 1
  • 10
  • 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? – Kurt L. Feb 20 '21 at 11:14
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

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
M Hamayun zeb
  • 448
  • 4
  • 10