9

I know i can add things like text, URL, images to UIActivityViewController , but how to add my current location with a thumbnail of my location like in the tweet shown below ? in other words how to share coordinates "latitude,longitude" in UIActivityViewController ?

Thanks

a tweet with location

rmaddy
  • 314,917
  • 42
  • 532
  • 579
4mahmoud
  • 813
  • 2
  • 19
  • 31
  • 1
    Make screenshot of your app with your current location, with size you want to make, and center will be your location, and then add that image to `UIActivityViewController`. – user2545330 Nov 19 '13 at 06:38
  • 1
    That's what i did but when a person clicks the shared picture it will be shown as a picture not as a link to the location, thanks – 4mahmoud Nov 30 '13 at 11:35

8 Answers8

7

I have a complete example of code and all the important classes/methods to do this. I know the answer is very late, but this may help someone in the future:

- (void)shareTab
{
    PFGeoPoint *geoPoint = self.vidgeoObject[kParseLocation];
    NSString *coordinates = [NSString stringWithFormat:@"http://maps.apple.com/maps?q=%f,%f",  geoPoint.latitude, geoPoint.longitude];
    CLLocation *userLocation = [[CLLocation alloc] initWithLatitude:geoPoint.latitude longitude:geoPoint.longitude];
    CLGeocoder *geocoder;
    geocoder = [[CLGeocoder alloc]init];

    [geocoder reverseGeocodeLocation:userLocation completionHandler:^(NSArray *placemarks, NSError *error)
    {
        CLPlacemark *rootPlacemark = placemarks[0];
        MKPlacemark *evolvedPlacemark = [[MKPlacemark alloc]initWithPlacemark:rootPlacemark];

        ABRecordRef persona = ABPersonCreate();
        ABRecordSetValue(persona, kABPersonFirstNameProperty, (__bridge CFTypeRef)(evolvedPlacemark.name), nil);
        ABMutableMultiValueRef multiHome = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);

        bool didAddHome = ABMultiValueAddValueAndLabel(multiHome, (__bridge CFTypeRef)(evolvedPlacemark.addressDictionary), kABHomeLabel, NULL);

        if(didAddHome)
        {
            ABRecordSetValue(persona, kABPersonAddressProperty, multiHome, NULL);

            NSLog(@"Address saved.");
        }

        NSArray *individual = [[NSArray alloc]initWithObjects:(__bridge id)(persona), nil];
        CFArrayRef arrayRef = (__bridge CFArrayRef)individual;
        NSData *vcards = (__bridge NSData *)ABPersonCreateVCardRepresentationWithPeople(arrayRef);

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


        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory

        NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"pin.loc.vcf"];
        [vcardString writeToFile:filePath
                              atomically:YES encoding:NSUTF8StringEncoding error:&error];

        NSURL *url =  [NSURL fileURLWithPath:filePath];
        NSLog(@"url> %@ ", [url absoluteString]);


        // Share Code //
        NSArray *itemsToShare = [[NSArray alloc] initWithObjects: url, nil] ;
        UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:itemsToShare applicationActivities:nil];
        activityVC.excludedActivityTypes = @[UIActivityTypePrint,
                                             UIActivityTypeCopyToPasteboard,
                                             UIActivityTypeAssignToContact,
                                             UIActivityTypeSaveToCameraRoll,
                                             UIActivityTypePostToWeibo];

        if (UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPhone)
        {
            [self presentViewController:activityVC animated:YES completion:nil];
        } else
        {
        popControl = [[UIPopoverController alloc] initWithContentViewController:activityVC];
        }

    }];

}
Stunner
  • 12,025
  • 12
  • 86
  • 145
jmercouris
  • 348
  • 5
  • 17
  • This works for making an address record with a geocoded address, but I can't open it in maps directly. I noticed that the coordinates string you create at the top goes unused in the rest of the code. Is this supposed to be included somewhere in the ABRecordRef perhaps? – Mark Bridges May 27 '15 at 22:39
  • Hello Mark, I believe you can open it in maps directly. And yes, you are correct that string is unused throughout, it was part of an old method that involved creating a URL to an apple maps location. – jmercouris Jun 03 '15 at 21:50
  • Is there any particular reason to write atomically? – whoKnows Oct 11 '15 at 17:55
5

I have created a Swift alternative to this that persists a vCard file to the caches directory that you can checkout at: https://gist.github.com/naturaln0va/e1fed3f1d32ecf951aac.

The vCard structure is similar to whoKnows' answer above.

let vCardString = [
    "BEGIN:VCARD",
    "VERSION:3.0",
    "N:;Shared Location;;;",
    "FN:Shared Location",
    "item1.URL;type=pref:http://maps.apple.com/?ll=\(coordinate.latitude),\(coordinate.longitude)",
    "item1.X-ABLabel:map url",
    "END:VCARD"
].joinWithSeparator("\n")
naturaln0va
  • 755
  • 6
  • 8
2

To share location like that you need to create a vcf file for that location... I just spent my whole day trying to do the same and gathering informations here and there managed to find out that apple does it in maps using a file name like "address you share.loc.vcf"

You basically need to get the current location using the CLLocationManager, then use a CLGeocoder to get the address from the location you retrieved with the Location Manager.

The CLGeocoder gets you a CLPlacemark that you can make into a MKPlacemark.

You will use the addressDictionary stored in there

With this info you can create an ABRecordRef using ABPersonCreate, store all the data for that location in there and then get the vcd data using ABPersonCreateVCardRepresentationWithPeople

You'll need to also add a map url, just share a location with yourself from maps.app and look into the .vcf for the url structure...

transform that data into an NSString and save to file.

Create an NSURL to that file and share it with the UIActivityViewController...

Pretty tedious work but it's definitely working great for me...

Muvimotv
  • 853
  • 9
  • 14
  • 2
    Thanks for the answer but it would be very appreciated if you share your working code here. thank you – 4mahmoud Nov 30 '13 at 11:33
  • When sharing the loc.vfc I don't get the title or image that I want. It is shared as a map location, but looks like a contact card in the activity controller. – Johan Feb 12 '20 at 14:12
2

My variant in Swift 2

       let passDictionaryInfo = placeMark.addressDictionary as! [String: AnyObject]

       @IBAction func shareLocation(sender: UIBarButtonItem) {
//        print(receivedDictionary)
        let postalAdress = CNMutablePostalAddress()
        postalAdress.street = receivedDictionary["Name"] as! String
        postalAdress.city = receivedDictionary["City"] as! String
        postalAdress.state = receivedDictionary["State"] as! String
        postalAdress.postalCode = receivedDictionary["ZIP"] as! String
        postalAdress.country = receivedDictionary["Country"] as! String
        postalAdress.ISOCountryCode = receivedDictionary["CountryCode"] as! String

        let streetName = receivedDictionary["Name"] as! String

        let urlAddress = receivedDictionary["FormattedAddressLines"] as! [String]
//        print(urlAddress)
        let postalContact = CNLabeledValue(label: streetName, value: postalAdress)
        let urlAddressContact = CNLabeledValue(label: "map url", value: "http://maps.apple.com/maps?address=\(urlAddress.description)")

        let contact = CNMutableContact()
        contact.contactType = .Organization
        contact.organizationName = streetName
        contact.departmentName = streetName
        contact.postalAddresses = [postalContact]
        contact.urlAddresses = [urlAddressContact]

        // create path

        let directory = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)

        let path = directory.first!.path!.stringByAppendingString("/\(streetName).loc.vcf")
//        print(path)

        do {
            let contactData = try CNContactVCardSerialization.dataWithContacts([contact])

            contactData.writeToFile(path, atomically: true)

            let url = NSURL(fileURLWithPath: path)
//            print(url)
            let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
            presentViewController(activityViewController, animated: true, completion: nil)
        } catch {
            print("CNContactVCardSerialization cannot save address")
        }
    }
Alexander Khitev
  • 6,417
  • 13
  • 59
  • 115
1

The other two answers use the complex AddressBook framework to create a vCard. It's easier to do it like this:

NSString *vcardString = [NSString stringWithFormat:@"BEGIN:VCARD\n"
                         "VERSION:3.0\n"
                         "    N:;Shared Location;;;\n"
                         "   FN:Shared Location\n"
                         "item1.URL;type=pref:http://maps.apple.com/?ll=%f,%f\n"
                         "item1.X-ABLabel:map url\n"
                         "END:VCARD", lat, lng];

Then save it to a file, get the NSURL of that file, and share it with UIActivityViewController as demonstrated in artillery129's answer.

whoKnows
  • 905
  • 2
  • 9
  • 27
  • This way it works perfect, and the code is much simpler. Thanks for sharing! – Valerii Lider Dec 30 '15 at 17:15
  • By the way I was not able to mimic Maps-like behavior, when sharing location through Messages.app, by using code from other answers. I mean - when you share vcf file, made by AB framework - its actually creates person record. So Message.app threats this file as AddressBook Person record. Therefore - it is not showing piece of the map, but person record instead. – Valerii Lider Dec 30 '15 at 17:23
1

I've updated @naturaln0va's answer to Swift 3:
https://gist.github.com/ElegyD/510a17904917a7e7326254b824da1b2f

Just copy & paste somewhere and use it like so:

let coordinate = CLLocationCoordinate2D(latitude: 52.520007, longitude: 13.404954)
let vCardURL = LocationVCard.vCardUrl(from: coordinate, with: "Berlin")
let activityViewController = UIActivityViewController(activityItems: [vCardURL], applicationActivities: nil)
present(activityViewController, animated: true, completion: nil)
ElegyD
  • 4,393
  • 3
  • 21
  • 37
0

The correct way is to make the card using Data, not a file:

Swift 4

    import MobileCoreServices

    let locationTitle: String = ...
    let coordinate: CLLocationCoordinate2D = ...

    let vCardString = [
        "BEGIN:VCARD",
        "VERSION:4.0",
        "N:;\(locationTitle);;;",
        "FN:\(locationTitle)",
        "item1.URL;type=pref:http://maps.apple.com/?ll=\(coordinate.latitude),\(coordinate.longitude)",
        "item1.X-ABLabel:map url",
        "END:VCARD"
        ].joined(separator: "\n")

    guard let vCardData = vCardString.data(using: .utf8) else { return }

    var items = [Any]()
    let vCardActivity = NSItemProvider(item: vCardData as NSData, typeIdentifier: kUTTypeVCard as String)
    items.append(vCardActivity)
    items.append(locationTitle)
    let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
    present(activityViewController, animated: true, completion: nil)
Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44
0

Solution using Swift 4.2 and CNContactVCardSerialization

 func vCardFilePath(from placemark: CLPlacemark) -> String? {
        let cnAddress = CNMutablePostalAddress()
        cnAddress.city = placemark.locality ?? ""
        cnAddress.country = placemark.country ?? ""
        cnAddress.postalCode = placemark.postalCode ?? ""
        cnAddress.isoCountryCode = placemark.isoCountryCode ?? ""
        cnAddress.state = placemark.administrativeArea ?? ""
        cnAddress.street = placemark.thoroughfare ?? ""
        let labelledAddress = CNLabeledValue<CNPostalAddress>(label: CNLabelOther, value: cnAddress)

        let abPerson = CNMutableContact()
        abPerson.givenName = locationName ?? ""
        abPerson.postalAddresses = [labelledAddress]

        if let coords = position?.coordinate,
            let loc = locationName?.addingPercentEncoding(withAllowedCharacters: .whitespacesAndNewlines) {
            let url = "https://maps.apple.com/?ll=\(coords.latitude),\(coords.longitude)&q=\(loc)" as NSString
            let urlAddress = CNLabeledValue<NSString>(label: CNLabelHome, value: url)
            abPerson.urlAddresses = [urlAddress]
        }

        guard let vCards = try? CNContactVCardSerialization.data(with: [abPerson]) else {
            print("Couldn't extract vCards data")
            return nil
        }
        let vCardString = String(data: vCards, encoding: .utf8)

        //Store the vCard file
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let documentsDirectory = paths[0]
        let filePath = "\(documentsDirectory)/shareLocation.vcf"

        do {
            try vCardString?.write(toFile: filePath, atomically: false, encoding: .utf8)
        } catch _ {
            print("Couldn't write location .vcf file")
            return nil
        }
        return filePath
    }
Mikael
  • 2,355
  • 1
  • 21
  • 45