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

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?

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
}

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

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

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
}

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....
#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
}

Related

how to share QR created image swift 4

I am creating QR code in swift and assigning it to an imageView
when I try to share that image with generated code, it does not shares that image,
func createCode()
{
let text = email
let data = text.data(using: .ascii, allowLossyConversion: false)
fillter = CIFilter(name: "CIQRCodeGenerator")
fillter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 5.0, y: 5.0)
CreatedImage = UIImage(ciImage: (fillter.outputImage?.transformed(by: transform))!)
imageCode.image = CreatedImage as UIImage
}
and this is share button
#IBAction func shareButtonPressed(_ sender: Any)
{
let activityItem: [UIImage] = [imageCode.image!]
let activity = UIActivityViewController(activityItems: activityItem as [UIImage], applicationActivities: [])
activity.popoverPresentationController?.sourceView = self.view
self.present(activity, animated: true, completion: nil)
}
it shows like it has nothing to share, it does not pick any bit of image
Have you created a variable to store the image somewhere e.g.
var generatedImage: UIImage?
Assuming then, that I have read your question correctly, in your creation function you can cast the image at the end of the function e.g:
generatedImage = imageCode.image
Then in your share function you could say:
guard let validQR = generatedImage else { return }
let activityItem: [UIImage] = [validQR]
let activity = UIActivityViewController(activityItems: activityItem as [UIImage], applicationActivities: [])
activity.popoverPresentationController?.sourceView = self.view
self.present(activity, animated: true, completion: nil)
I tested with an image from my Bundle e.g:
generatedImage = UIImage(named: "SCNPyramid")
And I was able to share the image :)
after searching all..
I cam I to know that make a programmatically screen shot of desired view, that is sent..
I have been having the same problem and solved it by first saving the generated qr code image to a file and then sharing the file url.
private func shareQRCode() {
guard let qrcode = self.qrCodeImage,
let data = qrcode.pngData(),
let url = self.saveInCache(data: data, fileName: "QRCode.png") else { return }
// set up activity view controller
let imageToShare = [url]
let activityViewController = UIActivityViewController(activityItems: imageToShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash
// work around to prevent dismissing current view after saving image
let tempController = TransparentViewController()
tempController.modalPresentationStyle = .overFullScreen
activityViewController.completionWithItemsHandler = { [weak tempController] _, _, _, _ in
if let presentingViewController = tempController?.presentingViewController {
presentingViewController.dismiss(animated: false, completion: nil)
} else {
tempController?.dismiss(animated: false, completion: nil)
}
}
present(tempController, animated: true) { [weak tempController] in
tempController?.present(activityViewController, animated: true, completion: nil)
}
}
Here is the code for saveInCache function:
private func saveInCache(data: Data, fileName: String) -> URL? {
let paths = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
let path = paths[0]
let fileUrl = path.appendingPathComponent(fileName)
let fileManager = FileManager.default
if self.pathExists(fileUrl) {
do {
try fileManager.removeItem(at: fileUrl)
} catch { return fileUrl }
}
guard fileManager.createFile(atPath: fileUrl.path, contents: data, attributes: nil) else {
return nil
}
return fileUrl
}
private func pathExists(_ path: URL) -> Bool {
let fileManager = FileManager.default
var isDir: ObjCBool = false
if fileManager.fileExists(atPath: path.path, isDirectory: &isDir) {
if isDir.boolValue {
// file exists and is a directory
return true
} else {
// file exists and is not a directory
return true
}
} else {
// file does not exist
return false
}
}
And here a simple Transparent View Controller for ActivityViewController work around:
final class TransparentViewController: UIViewController {
override func viewDidLoad() {
self.view.backgroundColor = .clear
}
}

Swift 4 - sharing location through UIActivityViewController

I'm trying to build an application where I can share an address and open that in any navigation app (Google maps, Apple maps, waze,...).
Below is the code that I currently have (after going through pages of google search results including dozens of stackoverflow questions)
#IBAction func navigeer(_ sender: Any) {
var items = [AnyObject]()
let latitude: Double = 52.033809
let longitude: Double = 6.882286
let locationTitle = "Navigate to this address"
let URLString = "https://maps.apple.com?ll=\(latitude),\(longitude)"
guard let cachesPathString = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first else {
print("Error: couldn't find the caches directory.")
return
}
if let url = NSURL(string: URLString) {
items.append(url)
}
let vCardString = [
"BEGIN:VCARD",
"VERSION:3.0",
"N:;Shared Location;;;",
"FN:Shared Location",
"item1.URL;type=pref:http://maps.apple.com/?ll=\(latitude),\(longitude)",
"item1.X-ABLabel:map url",
"END:VCARD"
].joined(separator: "\n")
let vCardFilePath = (cachesPathString as NSString).appendingPathComponent("vCard.loc.vcf")
let nsVCardData = NSURL(fileURLWithPath: vCardFilePath)
let shareItems:Array = [nsVCardData]
let activityController = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
present(activityController, animated:true, completion: nil)
}
When I run the application on my simulator I get the following:
After clicking the share button
Why don't I get app suggestions like Apple maps or Google maps? I also don't get why it suggests me to copy it to contacts..
Thanks in advance.
To share location using UIActivityViewController, Swift 5
func activityItems(latitude: Double, longitude: Double) -> [AnyObject]? {
var items = [AnyObject]()
let URLString = "https://maps.apple.com?ll=\(latitude),\(longitude)"
if let url = NSURL(string: URLString) {
items.append(url)
}
let locationVCardString = [
"BEGIN:VCARD",
"VERSION:3.0",
"PRODID:-//Joseph Duffy//Blog Post Example//EN",
"N:;Shared Location;;;",
"FN:Shared Location",
"item1.URL;type=pref:\(URLString)",
"item1.X-ABLabel:map url",
"END:VCARD"
].joined(separator: "\n")
guard let vCardData : NSSecureCoding = locationVCardString.data(using: .utf8) as NSSecureCoding? else {
return nil
}
let vCardActivity = NSItemProvider(item: vCardData, typeIdentifier: kUTTypeVCard as String)
items.append(vCardActivity)
return items
}
How to use?
if let shareObject = self.activityItems(latitude: 52.033809, longitude: 6.882286) {
//open UIActivityViewController
}
Reference Link : https://new.josephduffy.co.uk/posts/ios-share-sheets-the-proper-way-locations
Try this..
// if the device has google maps installed in it
if (UIApplication.shared.canOpenURL(NSURL(string:"comgooglemaps://")! as URL)) {
UIApplication.shared.openURL(NSURL(string:
"comgooglemaps://?saddr=&daddr=\(myLatitude),\(myLongitude)&directionsmode=driving")! as URL)
}
// if google maps is not installed, try apple map
else if (UIApplication.shared.canOpenURL(NSURL(string:"http://maps.apple.com/maps")! as URL)) {
// apple map
let url = "http://maps.apple.com/maps?saddr=\(from.latitude),\(from.longitude)&daddr=\(to.latitude),\(to.longitude)"
UIApplication.shared.openURL(URL(string:url)!)
}
// if apple map is also not there, it will show an appStore link to download apple map application.

How to share on instagram in ios?

I have to share image on instagram with caption but in Instagram nothing is coming. I used below code for sharing on instagram. Is there any changes in code of sharing. I also check the official page of Instagram but no code is given. https://www.instagram.com/developer/mobile-sharing/iphone-hooks/
Following code is working till ios10 but in ios11 it is not working any more.
File write successfully in document directory but problem was in UIDocumentInteractionController. It is not able to read file from document directory.
//MARK:
//MARK: share with instagram
func shareImageToInstagram(withImagePath imgPath:String,withStrTitle strTitle:String,withView view:UIView,withSender sender:UIButton! = nil) {
let instagramURL = URL(string: "instagram://app")
if UIApplication.shared.canOpenURL(instagramURL!) {
interactionController = UIDocumentInteractionController(url: URL.init(fileURLWithPath: imgPath))
interactionController?.uti = "com.instagram.photos"
interactionController?.annotation = NSDictionary.init(dictionaryLiteral: ("InstagramCaption",strTitle))
interactionController?.presentOpenInMenu(from: CGRect.zero, in: view, animated: true)
sender.isUserInteractionEnabled = true
}
}
//MARK:
//MARK: share with instagram
func downloadUserImageFromURL(withImageUrl imgURl : URL,withView view:UIView,withstrTitle strTitle:String,withSender sender:UIButton! = nil){
DispatchQueue.global(qos: .userInitiated).async {
do {
DispatchQueue.main.async {
SINGLETON.startLoadingActivity(view)
}
let data = try Data.init(contentsOf: imgURl) //make sure your image in this url does exist, otherwise unwrap in a if let check
DispatchQueue.main.async {
SINGLETON.stopLoadingActivity(view)
//create instance of NSFileManager
let paths: [Any] = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
//create an array and store result of our search for the documents directory in it
let documentsDirectory: String = paths[0] as? String ?? ""
//create NSString object, that holds our exact path to the documents directory
let fullPath: String = URL(fileURLWithPath: documentsDirectory).appendingPathComponent("insta.igo").absoluteString
//add our image to the path
if FileManager.default.fileExists(atPath: fullPath)
{
do {
try FileManager.default.removeItem(at: URL.init(string: fullPath)!)
} catch let error as NSError {
sender.isUserInteractionEnabled = true
print(error.localizedDescription)
}
}
do {
try data.write(to: URL.init(string: fullPath)!)
self.shareImageToInstagram(withImagePath: fullPath, withStrTitle: strTitle, withView: view,withSender: sender)
} catch let error as NSError {
sender.isUserInteractionEnabled = true
print(error.localizedDescription)
}
}
}
catch{
DispatchQueue.main.async {
SINGLETON.stopLoadingActivity(view)
}
}
}
}
You use wrong UTI: "com.instagram.photos" should be "com.instagram.photo".
Also don't forget to add URL scheme instagram into plist at Key LSApplicationQueriesSchemes.
Here you can find example of sharing into Instagram (method - (void)send).
Main code from there:
Objective-C:
// make a path into documents
NSString* homePath = [self _getpathToDocuments];
NSString* basePath = #"integration/instagram";
NSString* tmpFileName;
if ([self _isInstagramOnly]) {
tmpFileName = #"jumpto.igo";
} else {
tmpFileName = #"jumpto.ig";
}
NSString* dirPath = [NSString stringWithFormat:#"%#/%#", homePath, basePath];
NSString* docPath = [NSString stringWithFormat:#"%#/%#", dirPath, tmpFileName];
[[NSFileManager defaultManager] removeItemAtPath:docPath error:nil];
if ([[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil]) {
UIImage* tmpImg = [self _imageForSharing];
if([self _needResizeImage]){
tmpImg = [self _resizeImage:tmpImg];
}
NSData* imgData = [self generateImageData:tmpImg];
[[NSFileManager defaultManager] createFileAtPath:docPath contents:imgData attributes:nil];
NSURL* url = [NSURL fileURLWithPath:docPath isDirectory:NO ];
NSString *UTI = nil;
if ([self _isInstagramOnly]) {
UTI = #"com.instagram.exclusivegram";
} else {
UTI = #"com.instagram.photo";
}
NSString *captionString = #"Caption message";
UIDocumentInteractionController* dic = [UIDocumentInteractionController interactionControllerWithURL:documentFileURL];
dic.UTI = UTI;
dic.annotation = #{#"InstagramCaption" : captionString};
dic.delegate = self;
[self presentOpenInMenuFromRect:[self _getButtonRect] inView:self.view animated:YES];
}
Swift:
// Converted with Swiftify v1.0.6491 - https://objectivec2swift.com/
// make a path into documents
var homePath: String = _getpathToDocuments()
var basePath = "integration/instagram"
var tmpFileName = ""
if _isInstagramOnly() {
tmpFileName = "jumpto.igo"
}
else {
tmpFileName = "jumpto.ig"
}
var dirPath = "\(homePath)/\(basePath)"
var docPath = "\(dirPath)/\(tmpFileName)"
try? FileManager.default.removeItem(atPath: docPath)
if try? FileManager.default.createDirectory(atPath: dirPath, withIntermediateDirectories: true, attributes: nil) != nil {
var tmpImg: UIImage? = _imageForSharing()
if _needResizeImage() {
tmpImg = _resize(tmpImg)
}
var imgData = generateImageData(tmpImg)
FileManager.default.createFile(atPath: docPath, contents: imgData, attributes: nil)
var url = URL.fileURL(withPath: docPath, isDirectory: false)
var UTI: String? = nil
if _isInstagramOnly() {
UTI = "com.instagram.exclusivegram"
}
else {
UTI = "com.instagram.photo"
}
var captionString = "Caption message"
var dic = UIDocumentInteractionController(url: documentFileURL)
dic.uti = UTI
dic.annotation = ["InstagramCaption": captionString]
dic.delegate = self
presentOpenInMenu(from: _getButtonRect(), in: view, animated: true)
}

Modifing metadata from existing phAsset seems not working

In my App I want to make it possible, that the user sets an StarRating from 0 to 5 for any Image he has in his PhotoLibrary. My research shows, that there are a couple of ways to get this done:
Save the exif metadata using the new PHPhotoLibrary
Swift: Custom camera save modified metadata with image
Writing a Photo with Metadata using Photokit
Most of these Answers were creating a new Photo. My snippet now looks like this:
let options = PHContentEditingInputRequestOptions()
options.isNetworkAccessAllowed = true
self.requestContentEditingInput(with: options, completionHandler: {
(contentEditingInput, _) -> Void in
if contentEditingInput != nil {
if let url = contentEditingInput!.fullSizeImageURL {
if let nsurl = url as? NSURL {
if let imageSource = CGImageSourceCreateWithURL(nsurl, nil) {
var imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary?
if imageProperties != nil {
imageProperties![kCGImagePropertyIPTCStarRating] = rating as AnyObject
let imageData = NSMutableData(contentsOf: url)
let image = UIImage(contentsOfFile: url.path)
let destination = CGImageDestinationCreateWithData(imageData!, CGImageSourceGetType(imageSource)!, 1, nil)
CGImageDestinationAddImage(destination!, image!.cgImage!, imageProperties! as CFDictionary)
var contentEditingOutput : PHContentEditingOutput? = nil
if CGImageDestinationFinalize(destination!) {
let archievedData = NSKeyedArchiver.archivedData(withRootObject: rating)
let identifier = "com.example.starrating"
let adjustmentData = PHAdjustmentData(formatIdentifier: identifier, formatVersion: "1.0", data: archievedData)
contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!)
contentEditingOutput!.adjustmentData = adjustmentData
if imageData!.write(to: contentEditingOutput!.renderedContentURL, atomically: true) {
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest(for: self)
request.contentEditingOutput = contentEditingOutput
}, completionHandler: {
success, error in
if success && error == nil {
completion(true)
} else {
completion(false)
}
})
}
} else {
completion(false)
}
}
}
}
}
}
})
Now when I want to read the metadata from the PHAsset I request the ContentEditingInput again and do the following:
if let url = contentEditingInput!.fullSizeImageURL {
if let nsurl = url as? NSURL {
if let imageSource = CGImageSourceCreateWithURL(nsurl, nil) {
if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? {
if let starRating = imageProperties[kCGImagePropertyIPTCStarRating] as? Int {
rating = starRating
}
}
}
}
}
But I never get my rating because it says that the value of imageProperties[kCGImagePropertyIPTCStarRating] is nil.
I also tried the examples from the Answers I posted above, but I always get the same result.
I hope anybody knows, what I can do to change the Metadata.
Also, how can I change the Metadata from an PHAsset with the MediaType .video? I tried to achieve that through the AVAssetWriter and AVExportSession Objects, but in both cases it does not work. Here what I tried for Videos:
var exportSession = AVAssetExportSession(asset: asset!, presetName: AVAssetExportPresetPassthrough)
exportSession!.outputURL = outputURL
exportSession!.outputFileType = AVFileTypeQuickTimeMovie
exportSession!.timeRange = CMTimeRange(start: start, duration: duration)
var modifiedMetadata = asset!.metadata
let metadataItem = AVMutableMetadataItem()
metadataItem.keySpace = AVMetadataKeySpaceQuickTimeMetadata
metadataItem.key = AVMetadataQuickTimeMetadataKeyRatingUser as NSCopying & NSObjectProtocol
metadataItem.value = rating as NSCopying & NSObjectProtocol
modifiedMetadata.append(metadataItem)
exportSession!.metadata = modifiedMetadata
exportSession!.exportAsynchronously(completionHandler: {
let status = exportSession?.status
let success = status == AVAssetExportSessionStatus.completed
if success {
do {
let sourceURL = urlAsset.url
let manager = FileManager.default
_ = try manager.removeItem(at: sourceURL)
_ = try manager.moveItem(at: outputURL, to: sourceURL)
} catch {
LogError("\(error)")
completion(false)
}
} else {
LogError("\(exportSession!.error!)")
completion(false)
}
})
Sorry this isn't a full answer but it covers one part of your question. I noticed you are placing the StarRating in the wrong place. You need to place it in a IPTC dictionary. Also the properties data is stored as strings. Given you have the imageProperties you can add the star rating as follows and read it back using the following two functions
func setIPTCStarRating(imageProperties : NSMutableDictionary, rating : Int) {
if let iptc = imageProperties[kCGImagePropertyIPTCDictionary] as? NSMutableDictionary {
iptc[kCGImagePropertyIPTCStarRating] = String(rating)
} else {
let iptc = NSMutableDictionary()
iptc[kCGImagePropertyIPTCStarRating] = String(rating)
imageProperties[kCGImagePropertyIPTCDictionary] = iptc
}
}
func getIPTCStarRating(imageProperties : NSMutableDictionary) -> Int? {
if let iptc = imageProperties[kCGImagePropertyIPTCDictionary] as? NSDictionary {
if let starRating = iptc[kCGImagePropertyIPTCStarRating] as? String {
return Int(starRating)
}
}
return nil
}
As the imageProperties you get from the image are not mutable you need to create a mutable copy of these properties first before you can call the functions above. When you create your image to save use the mutable properties in your call to CGImageDestinationAddImage()
if let mutableProperties = imageProperties.mutableCopy() as? NSMutableDictionary {
setIPTCStarRating(imageProperties:mutableProperties, rating:rating)
}
One other point you are creating an unnecessary UIImage. If you use CGImageDestinationAddImageFromSource() instead of CGImageDestinationAddImage() you can use the imageSource you created earlier instead of loading the image data into a UIImage.

How to share longitude and latitude using UIActivityViewController

I'm trying to share a (longitude, latitude) associated with your's location in a UIActivityViewController so that the user can share the location in an SMS with someone else and it shows up as a clickable little map as shown below.
I know how to share an address as a text. Here is my code for sharing the address:
#IBAction func didTapShareLocation(_ sender: UIButton) {
guard let carAddress = self.adressLabel.text else {
return
}
let textToShare = "My car is at this address: \(carAddress)"
let objectsToShare = [textToShare] as [Any]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = sender
myParentVC?.present(activityVC, animated: true, completion: nil)
}
Here is a complete answer in Swift 3.1 which I put together after getting pieces of information from several places. I hope it helps someone.
#IBAction func didTapShareLocation(_ sender: UIButton) {
guard let carAddress = self.adressLabel.text, let lat = self.carCoordinates?.latitude, let lon = self.carCoordinates?.longitude else {
return
}
guard CLLocationCoordinate2DIsValid(self.carCoordinates!) else {
print("Location not valid!")
return
}
let carAddressString = "My car is at this address: \n\(carAddress)\n"
let vcardString = [
"BEGIN:VCARD",
"VERSION:3.0",
"N:;Shared Location;;;",
"FN:Shared Location",
"item1.URL;type=pref:http://maps.apple.com/?ll=\(lat),\(lon)",
"item1.X-ABLabel:map url",
"END:VCARD"
].joined(separator: "\n")
let directory = FileManager().urls(for: .cachesDirectory, in: .userDomainMask)
let path = directory.first!.path + "_vcard_for_location_sharing.loc.vcf"
do {
try vcardString.write(toFile: path, atomically: true, encoding: .ascii)
let url = NSURL(fileURLWithPath: path)
let objectsToShare = [url, carAddressString] as [Any]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = sender
self.present(activityVC, animated: true, completion: nil)
}
catch {
print("problem saving vcard: \(error.localizedDescription)")
}
}

Resources