How do I share files using share sheet in iOS? - ios

I want to share some files I have locally in my app using Share Sheet functionality on iPhone. I display the file in a UIWebView and when the user clicks the share sheet, I want to show options (email, WhatsApp, etc. ) to share the file displayed on the UIWebView. I know that we can use
func displayShareSheet(shareContent:String) {
let activityViewController = UIActivityViewController(activityItems: [shareContent as NSString], applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: {})
}
to share a string for example. How do I change this code to share documents?

Swift 4.2 and Swift 5
If you already have a file in a directory and want to share it, just add it's URL into activityItems:
let fileURL = NSURL(fileURLWithPath: "The path where the file you want to share is located")
// Create the Array which includes the files you want to share
var filesToShare = [Any]()
// Add the path of the file to the Array
filesToShare.append(fileURL)
// Make the activityViewContoller which shows the share-view
let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil)
// Show the share-view
self.present(activityViewController, animated: true, completion: nil)
If you need to make the file:
I'm using this extension to make files from Data (read the comments in the code for explanation how it works):
As in the typedef's answer, get the current documents directory:
/// Get the current directory
///
/// - Returns: the Current directory in NSURL
func getDocumentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
return documentsDirectory as NSString
}
Extension for Data:
extension Data {
/// Data into file
///
/// - Parameters:
/// - fileName: the Name of the file you want to write
/// - Returns: Returns the URL where the new file is located in NSURL
func dataToFile(fileName: String) -> NSURL? {
// Make a constant from the data
let data = self
// Make the file path (with the filename) where the file will be loacated after it is created
let filePath = getDocumentsDirectory().appendingPathComponent(fileName)
do {
// Write the file from data into the filepath (if there will be an error, the code jumps to the catch block below)
try data.write(to: URL(fileURLWithPath: filePath))
// Returns the URL where the new file is located in NSURL
return NSURL(fileURLWithPath: filePath)
} catch {
// Prints the localized description of the error from the do block
print("Error writing the file: \(error.localizedDescription)")
}
// Returns nil if there was an error in the do-catch -block
return nil
}
}
Examples how to use:
Share image-files:
// Your image
let yourImage = UIImage()
in png-file
// Convert the image into png image data
let pngImageData = yourImage.pngData()
// Write the png image into a filepath and return the filepath in NSURL
let pngImageURL = pngImageData?.dataToFile(fileName: "nameOfYourImageFile.png")
// Create the Array which includes the files you want to share
var filesToShare = [Any]()
// Add the path of png image to the Array
filesToShare.append(pngImageURL!)
// Make the activityViewContoller which shows the share-view
let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil)
// Show the share-view
self.present(activityViewController, animated: true, completion: nil)
in jpg-file
// Convert the image into jpeg image data. compressionQuality is the quality-compression ratio in % (from 0.0 (0%) to 1.0 (100%)); 1 is the best quality but have bigger filesize
let jpgImageData = yourImage.jpegData(compressionQuality: 1.0)
// Write the jpg image into a filepath and return the filepath in NSURL
let jpgImageURL = jpgImageData?.dataToFile(fileName: "nameOfYourImageFile.jpg")
// Create the Array which includes the files you want to share
var filesToShare = [Any]()
// Add the path of jpg image to the Array
filesToShare.append(jpgImageURL!)
// Make the activityViewContoller which shows the share-view
let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil)
// Show the share-view
self.present(activityViewController, animated: true, completion: nil)
Share text-files:
// Your String including the text you want share in a file
let text = "yourText"
// Convert the String into Data
let textData = text.data(using: .utf8)
// Write the text into a filepath and return the filepath in NSURL
// Specify the file type you want the file be by changing the end of the filename (.txt, .json, .pdf...)
let textURL = textData?.dataToFile(fileName: "nameOfYourFile.txt")
// Create the Array which includes the files you want to share
var filesToShare = [Any]()
// Add the path of the text file to the Array
filesToShare.append(textURL!)
// Make the activityViewContoller which shows the share-view
let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil)
// Show the share-view
self.present(activityViewController, animated: true, completion: nil)
Other files:
You can make a file from anything which is in Data format and as far as I know, almost everything in Swift can be converted into Data like String, Int, Double, Any...:
// the Data you want to share as a file
let data = Data()
// Write the data into a filepath and return the filepath in NSURL
// Change the file-extension to specify the filetype (.txt, .json, .pdf, .png, .jpg, .tiff...)
let fileURL = data.dataToFile(fileName: "nameOfYourFile.extension")
// Create the Array which includes the files you want to share
var filesToShare = [Any]()
// Add the path of the file to the Array
filesToShare.append(fileURL!)
// Make the activityViewContoller which shows the share-view
let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil)
// Show the share-view
self.present(activityViewController, animated: true, completion: nil)

I want to share my solution of UIActivityViewController and sharing text as a image file. This solution works for sharing via Mail and even Save to Dropbox.
#IBAction func shareCsv(sender: AnyObject) {
//Your CSV text
let str = self.descriptionText.text!
filename = getDocumentsDirectory().stringByAppendingPathComponent("file.png")
do {
try str.writeToFile(filename!, atomically: true, encoding: NSUTF8StringEncoding)
let fileURL = NSURL(fileURLWithPath: filename!)
let objectsToShare = [fileURL]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
self.presentViewController(activityVC, animated: true, completion: nil)
} catch {
print("cannot write file")
// failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding
}
}
func getDocumentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0]
return documentsDirectory
}

Here's the Swift 3 version:
let dictToSave: [String: Any] = [
"someKey": "someValue"
]
let jsonData = try JSONSerialization.data(withJSONObject: dictToSave, options: .prettyPrinted)
let filename = "\(self.getDocumentsDirectory())/filename.extension"
let fileURL = URL(fileURLWithPath: filename)
try jsonData.write(to: fileURL, options: .atomic)
let vc = UIActivityViewController(activityItems: [fileURL], applicationActivities: [])
self.present(vc, animated: true)
func getDocumentsDirectory() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
return documentsDirectory
}

Related

UIDocumentInteractionController change file name for Sharing

iam using the following code to show the sharing options for PDF
self.documentController = UIDocumentInteractionController(url: url)
self.documentController.name = "Test name" // not working
self.documentController.presentOptionsMenu(from: self.shareButton, animated: true)
the problem is that I save the PDF file name with datestamp to avoid having two files with the same name, but when the share options is being shown the actual file name appears,
is there is a way to show custom name instead of the actual filename (I don't want to copy the file to other place and rename it, waste of time and performance)
In such a situation, we can create a temporary folder which can contain the same file with lastPathExtension will be document.fileExtension and we can pass this newly file path to UIDocumentInteractionController.init(url: newFileUrl)
For Example:
func openUnsupportedFileWithPath(documentName : String, fileurl : URL, fileExtension : String, aDocument: SILDocumentDB? = nil, sourceView: UIView? = nil) -> Void {
// Create new temporary path
let paths: String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
var newFileUrl: String = paths.appending("/Downloads/TemporaryFolder)")
newFileUrl = newFileUrl.appendingFormat("%#","\(documentName)")
let destinationPathUrl : URL
do {
// Move newly filePath with new fileName and fileExtension
destinationPathUrl = URL(fileURLWithPath: destinanewFileUrltionPath)
try FileManager.default.moveItem(at: fileurl, to: destinationPathUrl)
} catch {
print(error)
}
//Pass newly filePath to UIDocumentInteractionController
documentInteractionController = UIDocumentInteractionController.init(url: newFileUrl)
documentInteractionController?.name = documentName
documentInteractionController?.delegate = self
let canPreview = documentInteractionController?.presentPreview(animated: true)
if (canPreview == false) {
let activityViewController = UIActivityViewController.init(activityItems: [fileurl], applicationActivities: nil)
activityViewController.setValue(documentName, forKey: "subject")
if ISIPAD {
activityViewController.popoverPresentationController?.sourceView = sourceView ?? self.view
}
self.present(activityViewController, animated: true, completion: nil)
}
}
And UIDocumentInteractionController get dismiss, remove the temporary filePath on documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) method.
public func documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) {
documentInteractionController = nil
let paths: String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath: String = paths.appending("/Downloads/TemporaryFolder)")
let _fileManager : FileManager = FileManager.default
if filePath.length > 0 {
if _fileManager.fileExists(atPath: filePath) {
do{
try _fileManager.removeItem(atPath: filePath)
}catch let error as NSError{
print("\(error.localizedDescription)")
}
}
}
}

How to share excel file with UIActivityView Controller or UIDocumentInteractionController?

I want to share excel file with UIAtivityViewController or UIDocumentInteractionController
let documentsPath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(self.stateMentType)
do {
_ = try data?.write(to: URL.init(fileURLWithPath: documentsPath), options: .atomicWrite)
}
catch {
print("Error in writing \(error)")
}
let documents = NSData(contentsOfFile: documentsPath)
if fileManager.fileExists(atPath: documentsPath) {
// let activityController = UIActivityViewController(activityItems: [documents!], applicationActivities: nil)
// self.present(activityController, animated: true, completion: nil)
let url = URL(fileURLWithPath: documentsPath)
let dc = UIDocumentInteractionController(url: url)
dc.uti = "public.data"
dc.presentOpenInMenu(from: self.view.bounds, in: self.view, animated: true)
// dc.presentOptionsMenu(from: self.view.bounds, in: self.view, animated: true)
}
I am using this code with UIDocumentInteractionController. I am also added some key in Info Tab.
When i share the excel file , sharing views is opened but file is not sharing. Please help me .

How can I share location from my app to WhatsApp in Swift 3

I'm trying to share a location from my iOS app to WhatsApp and I want it to look like this:
What I'm doing is sending vCard with this code :
func vCardURL(from coordinate: CLLocationCoordinate2D, with name: String?) -> URL {
let vCardFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("Shared Location.loc.vcf")
let vCardString = [
"BEGIN:VCARD",
"VERSION:3.0",
//"PRODID:-//Apple Inc.//iPhone OS 10.3.2//EN",
"N:;My Location;;;",
"FN:My Location",
"item1.URL;type=pref:https://maps.apple.com/?ll=50.359890\\,12.934560&q=My%20Location&t=m",
"item1.X-ABLabel:map url",
"END:VCARD"
].joined(separator: "\n")
do {
try vCardString.write(toFile: vCardFileURL.path, atomically: true, encoding: .utf8)
} catch let error {
print("Error, \(error.localizedDescription), saving vCard: \(vCardString) to file path: \(vCardFileURL.path).")
}
print(vCardString)
return vCardFileURL
} // end of function
// calling the methood above
let vURL = LocationVCard.vCardURL(from: self.newLocation.coordinate, with: "Berlin")
let activityViewController = UIActivityViewController(activityItems: [vURL], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
But I always end up with this style instead of what I want:
No need to save the vCard string to a file, just convert it to Data and wrap it with NSItemProvider with the correct type identifier public.vcard, and it'll work just fine:
let data = vCardString.data(using: .utf8)! as NSData
let item = NSItemProvider(item: data, typeIdentifier: "public.vcard")
let activityViewController = UIActivityViewController(activityItems: [item], applicationActivities: nil)

UIActivityViewController and UIImage preview

I'm trying to share an images using UIActivityViewController (on iMessage, Telegram, WhatsApp and other).
I'm doing:
let image = ... // an image
let activityViewController = UIActivityViewController(activityItems: [image], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
But the image is cropped very badly. I've also tried to resize the image before the user share action, using UIActivityItemSource, but the result change depending the user device and the image
How can I achieve a perfect preview on all device and apps? Any advice or reference?
Here an example of the final result:
The first image is the image shared as Sticker in iMessage and the second using UIActivityViewController
Try to compress image before use UIActivityViewController
var compressedImage: Data? = UIImageJPEGRepresentation(yourImage, 0.8)
var docsPath: String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as? String ?? ""
var imagePath: String = URL(fileURLWithPath: docsPath).appendingPathComponent("image.jpg").absoluteString
var imageUrl = URL.init(fileURLWithPath: imagePath)
do {
try compressedImage?.write(to: imageUrl, options: .atomic)
} catch {
print(error)
}
// save the file
var activityViewController = UIActivityViewController(activityItems: ["Check this image", imageUrl], applicationActivities: nil)

Save image to library from URL, rename, and share it using Swift

I want to share images to Instagram from my own iOS app and I am using Kingfisher throughout the project to download and cache images and show them in UIImageViews but this time I want to do something a little bit different.
Basically, I am getting a response from an API with the url of an image and I want to
Download the image to the library using the URL
There is a bunch of questions on this for objective-C using
UIImageWriteToSavedPhotosAlbum
but I am using Swift.
Rename it with .igo extension (instagram exclusive)
Not sure how to go about this, would depend on number 1.
Then I could share it doing something like
let image = UIImage(named: "downloadedImage")
let objectsToShare: [AnyObject] = [ image! ]
let activityViewController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
activityViewController.excludedActivityTypes = [ UIActivityTypeAirDrop, UIActivityTypePostToFacebook ]
self.presentViewController(activityViewController, animated: true, completion: nil)
or I could do it using the instagram hooks:
instagram://library?LocalIdentifier=\(localID)
Documentation on this is scarce specially for Swift. How can I go about this? a push in the right direction is all I need.
It sounds like you already know how to use Kingfisher and retrieving the UIImage from the URL. So what i'm about to provide is information for saving the image to the documents directory and retrieving it to share.
Retrieve the correct directory URL
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
Saving an image to that directory
func saveImage (image: UIImage, filename: String ){
print("Saving image with name \(filename)")
if let data = UIImagePNGRepresentation(image) {
let fullURL = getDocumentsDirectory().appendingPathComponent(filename)
try? data.write(to: fullURL)
}
}
Retrieving an image from the directory
func loadImageFromName(name: String) -> UIImage? {
print("Loading image with name \(name)")
let path = getDocumentsDirectory().appendingPathComponent(name).path
let image = UIImage(contentsOfFile: path)
if image == nil {
print("missing image at: \(path)")
}
return image
}
Sharing an image
func share(shareText shareText:String?,shareImage:UIImage?){
var objectsToShare = [AnyObject]()
if let shareTextObj = shareText{
objectsToShare.append(shareTextObj)
}
if let shareImageObj = shareImage{
objectsToShare.append(shareImageObj)
}
if shareText != nil || shareImage != nil{
let activityViewController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
present(activityViewController, animated: true, completion: nil)
}else{
print("There is nothing to share")
}
}
How to Use
let img: UIImage = UIImage() // Replace this with your image from your URL
saveImage(image: img, filename: "newImage.igo") //This is where you change your extension name
let newImage: UIImage = loadImageFromName(name: "newImage.igo") //Retrieve your image with the correct extension
share(shareText: "Image going to Instagram", shareImage: newImage) //Present Activity VC.
And as DFD pointed out, you can exclude certain items from the share VC to only allow what you need.
In my navigation controller, I have a UIBarButtonItem set to System item "Action". I then created the following IBAction code:
#IBAction func shareImage(_ sender: UIBarButtonItem) {
let context = CIContext()
let final = context.createCGImage(imgFinished, from: imgFinished.extent)
let shareImage = UIImage(cgImage: final!)
let vc = UIActivityViewController(activityItems: [shareImage], applicationActivities: [])
vc.excludedActivityTypes = [
UIActivityType.airDrop,
UIActivityType.assignToContact,
UIActivityType.addToReadingList,
//UIActivityType.copyToPasteboard,
//UIActivityType.mail,
//UIActivityType.message,
//UIActivityType.openInIBooks,
//UIActivityType.postToFacebook,
//UIActivityType.postToFlickr,
UIActivityType.postToTencentWeibo,
//UIActivityType.postToTwitter,
UIActivityType.postToVimeo,
UIActivityType.postToWeibo,
UIActivityType.print,
//UIActivityType.saveToCameraRoll
]
present(vc,
animated: true,
completion: nil)
vc.popoverPresentationController?.sourceView = self.view
vc.completionWithItemsHandler = {(activity, success, items, error) in
}
}
The excludedActivityTypes list is a complete/exhaustive list of available UIActivityTypes gleaned from code-complete or command-clicking on UIActivity type and going through the resulting struct. I uncomment those I wish to exclude - but leave them in for quick reference in the future. This will give you the popup you want.

Resources