I am sharing an audio recording via the UIActivityViewController. When the audio file shares via email or iMessage, it shows the underlying name of the audio file without any apparent way of changing it.
NSArray *activityItems = [NSArray arrayWithObjects:self.audioPlayer.url, nil];
UIActivityViewController *avc = [[UIActivityViewController alloc]
initWithActivityItems:activityItems
applicationActivities:nil];
[self presentViewController:avc
animated:YES completion:nil];
If I don't use the UIActivityViewController and just use MFMessageComposeViewController directly, than I can use
[composer addAttachmentURL:self.audioPlayer.url withAlternateFilename:#"Piano Song.m4a"];
Is it possible to have an alternate file name with the UIActivityViewController?
You can create a hard link to the file (so that you don't have to copy the actual file) with any name you want in the temporary directory and pass it to the UIActivityViewController instead of the file.
- (NSURL *)createLinkToFileAtURL:(NSURL *)fileURL withName:(NSString *)fileName {
NSFileManager *fileManager = [NSFileManager defaultManager];
// create a path in the temp directory with the fileName
NSURL *tempDirectoryURL = [[NSFileManager defaultManager] temporaryDirectory];
NSURL *linkURL = [tempDirectoryURL URLByAppendingPathComponent:fileName];
// if the link already exists, delete it
if ([fileManager fileExistsAtPath:linkURL.path]) {
NSError *error;
[fileManager removeItemAtURL:linkURL error:&error];
if (error) {
// handle the error
}
}
// create a link to the file
NSError *error;
BOOL flag = [fileManager linkItemAtURL:fileURL toURL:linkURL error:&error];
if (!flag || error) {
// handle the error
}
return linkURL;
}
Use it like this:
NSURL *fileURL = ...;
NSString *desiredName = ...;
NSURL *linkURL = [self createLinkToFileAtURL:fileURL withName:desiredName];
UIActivityViewController *viewController = [[UIActivityViewController alloc] initWithActivityItems:#[linkURL] applicationActivities:nil];
[self presentViewController:viewController animated:YES completion:nil];
Hope this helps! Good luck!
Very nice, timaktimak. Thank you.
Here is the same in Swift:
private func createLinkToFile(atURL fileURL: URL, withName fileName: String) -> URL? {
let fileManager = FileManager.default // the default file maneger
let tempDirectoryURL = fileManager.temporaryDirectory // get the temp directory
let linkURL = tempDirectoryURL.appendingPathComponent(fileName) // and append the new file name
do { // try the operations
if fileManager.fileExists(atPath: linkURL.path) { // there is already a hard link with that name
try fileManager.removeItem(at: linkURL) // get rid of it
}
try fileManager.linkItem(at: fileURL, to: linkURL) // create the hard link
return linkURL // and return it
} catch let error as NSError { // something wrong
print("\(error)") // debug print out
return nil // and signal to caller
}
}
No, not possible. Could you not just rename the file before sharing it?
Related
I want to let the user choose the folder, where I'd save a new file.
To achieve that, I use document picker, and set the document type to public.folder and inMode UIDocumentPickerModeOpen.
After user opens the document picker and selects the desired folder, in didPickDocumentsAtURLs callback I get the NSUrl object, which has permissions to modify the file at that url (in this case, it's an url to a folder).
There is my issue. I have the url with access permission to a folder, however, to create a file I ussualy need to have the filename.extension in the url. If I were to modify the NSUrl object I've received from the document picker, or convert it to NSString, my guess is I lose the access permission and createFileAtPath method always fails.
What method do I need to use, or what configuration document picker do I need, in order to create a new file in the path that the user selected? I attach my current code:
- (void)openDocumentPicker:(NSString*)pickerType
{
//Find the current app window, and its view controller object
UIApplication* app = [UIApplication sharedApplication];
UIWindow* rootWindow = app.windows[0];
UIViewController* rootViewController = rootWindow.rootViewController;
//Initialize the document picker
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:#[pickerType] inMode:UIDocumentPickerModeOpen];
//Assigning the delegate, connects the document picker object with callbacks, defined in this object
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
//Call the document picker, to the view controller that we've found before
[rootViewController presentViewController:documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
//If we come here, user successfully picked a file/folder
[urls[0] startAccessingSecurityScopedResource]; //Let the os know we're going to use the file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsDirectory = urls[0].absoluteString;
NSString *newFilePath = [documentsDirectory stringByAppendingPathComponent:#"test.txt"];
NSError *error = nil;
if ([fileManager createFileAtPath:newFilePath contents:[#"new file test" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]){
NSLog(#"Create Sucess");
}
else{
NSLog(#"Create error: %#", error);
}
[urls[0] stopAccessingSecurityScopedResource]; //Let the os know we're done
}
Any leads would be kindly appreciated!
This is solution in swift, please try and let me knwo if any problem
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]){
var imgData: Data?
if let url = urls.first{
imgData = try? Data(contentsOf: url)
do{
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let destURLPath = documentDirectory.appendingPathComponent(url.lastPathComponent)
try imgData?.write(to: URL(fileURLWithPath: destURLPath))
print("FILES IS Writtern at DOcument Directory")
}catch{
}
}
}
To answer my own question, I'll leave a fully working code bellow.
My main issue was, that when you're using "public.folder" document type, you need to call startAccessingSecurityScopedResource with the url of the chosen folder, not with the modified link (file the user chose + NewFileName.extension)
- (void)openDocumentPicker
{
//This is needed, when using this code on QT!
//Find the current app window, and its view controller object
/*
UIApplication* app = [UIApplication sharedApplication];
UIWindow* rootWindow = app.windows[0];
UIViewController* rootViewController = rootWindow.rootViewController;
*/
//Initialize the document picker. Set appropriate document types
//When reading: use document type of the file, that you're going to read
//When writing into a new file: use #"public.folder" to select a folder, where your new file will be created
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:#[#"public.folder"] inMode:UIDocumentPickerModeOpen];
//Assigning the delegate, connects the document picker object with callbacks, defined in this object
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
//In this case we're using self. If using on QT, use the rootViewController we've found before
[self presentViewController:documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
//If we come here, user successfully picked a single file/folder
//When selecting a folder, we need to start accessing the folder itself, instead of the specific file we're going to create
if ( [urls[0] startAccessingSecurityScopedResource] ) //Let the os know we're going use this resource
{
//Write file case ---
//Construct the url, that we're going to be using: folder the user chose + add the new FileName.extension
NSURL *destURLPath = [urls[0] URLByAppendingPathComponent:#"Test.txt"];
NSString *dataToWrite = #"This text is going into the file!";
NSError *error = nil;
//Write the data, thus creating a new file. Save the new path if operation succeeds
if( ![dataToWrite writeToURL:destURLPath atomically:true encoding:NSUTF8StringEncoding error:&error] )
NSLog(#"%#",[error localizedDescription]);
//Read file case ---
NSData *fileData = [NSData dataWithContentsOfURL:destURLPath options:NSDataReadingUncached error:&error];
if( fileData == nil )
NSLog(#"%#",[error localizedDescription]);
[urls[0] stopAccessingSecurityScopedResource];
}
else
{
NSLog(#"startAccessingSecurityScopedResource failed");
}
}
This was also being discussed at apple forums:
Thread name: "iOS Creating a file in a public folder"
Thread link:
https://developer.apple.com/forums/thread/685170?answerId=682427022#682427022
I need to share a .zip file via email / iTunes / other ways (such as message, AirDrop). I can already send a zipped file via email and iTunes, but when I try to send the zip file using UIActivityViewController, it doesn't show any file.
This is the code:
-(void) sendAllToApp {
NSString *dpath=NSTemporaryDirectory();
NSString *zipfile=[dpath stringByAppendingPathComponent:[NSString stringWithFormat:#"All_Reports_of_Project_%#.zip",project.displayName]];
[SSZipArchive createZipFileAtPath:zipfile withFilesAtPaths:zippedURL];//zipfile is the path that I store zip file data,zippedURL is the paths of files t.
NSData *zipData=[[NSFileManager defaultManager]contentsAtPath:zipFile];
NSURL *url =[NSURL fileURLWithPath:zipfile];
[zipData writeToURL:url atomically:NO];
if(zipData != nil) {
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[url] applicationActivities:nil];
activityViewController.excludedActivityTypes = #[UIActivityTypePrint];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
activityViewController.popoverPresentationController.sourceView = self.navigationController.view;
activityViewController.popoverPresentationController.sourceRect = CGRectMake(self.navigationController.view.bounds.size.width/2, self.navigationController.view.bounds.size.height/4, 0, 0);
}
[self.navigationController presentViewController:activityViewController animated:true completion:nil];
activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
// When completed flag is YES, user performed specific activity
};
[self clearTmpDirectory];
}
else {
[self showError];
[self clearTmpDirectory];
}
}
I set items to #[url] and #[zipData] both.
When using URL, I can't use AirDrop.
When using zip data, I get a .data file in my MacBook. If I change the .data file to .zip, it will become the correct file that I want to share.
So how can I share the .zip file correctly?
We had the same issue. The only fix that did work for us was to convert the .zip to a .tar file, which can be shared using a NSURL containing its path.
In my app, I want to implement the ability to export the multiple pdf files.
For now, I can export an only pdf file using the code below:
// get local path url
NSURL *url = [self getFileURLWithIndexPath:indexPath];
if(url) {
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];
if(!fileExists) {
NSLog(#"%#", [url path]);
}
self.docController = [UIDocumentInteractionController interactionControllerWithURL:url];
[self.docController setDelegate:self];
BOOL canOpenFile = [self.docController presentOpenInMenuFromRect:self.view.frame inView:self.view animated:YES];
if(!canOpenFile) {
// No reader PDF
}
}
The url is the local URL path.
This method will popup where I can choose iBooks to export.
But I have no idea how to export multiple files, can anyone help me...?
Thank!
You can use UIActivityViewController to export multiple files and can open in UIDocumentationInteractionController, it provides in-app support for managing user interactions with files in the local system.
NSArray *dataItems = #[pdf1, pdf2, pdf3];
UIActivityViewController *activityViewController =
[[UIActivityViewController alloc] initWithActivityItems:dataItems
applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:^{
}];
I am using following code to create a folder/file under the shared
container path. Which will help both app extension and the extension containing app can access the data.
code to get the shared container url location:
+(NSURL*)getSharedContainerURLPath
{
NSFileManager *fm = [NSFileManager defaultManager];
NSString *appGroupName = APP_EXTENSION_GROUP_NAME; /* For example */
NSURL *groupContainerURL = [fm containerURLForSecurityApplicationGroupIdentifier:appGroupName];
return groupContainerURL;
}
code to create a directory
+(void)createDirAtSharedContainerPath
{
NSString *sharedContainerPathLocation = [[self getSharedContainerURLPath] absoluteString];
NSString *directoryToCreate = #"user_abc";
//basically this is <shared_container_file_path>/user_abc
NSString *dirPath = [sharedContainerPathLocation stringByAppendingPathComponent:directoryToCreate];
BOOL isdir;
NSError *error = nil;
NSFileManager *mgr = [[NSFileManager alloc]init];
if (![mgr fileExistsAtPath:dirPath isDirectory:&isdir]) { //create a dir only that does not exists
if (![mgr createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(#"error while creating dir: %#", error.localizedDescription);
} else {
NSLog(#"dir was created....");
}
}
}
the above code not raising any error it says success but i am not able to find the folder under the shared container path. Any idea that might be appreciated
I just made my code work by changing the following code
NSString *sharedContainerPathLocation = [[self getSharedContainerURLPath] absoluteString];
to
NSString *sharedContainerPathLocation = [[self getSharedContainerURLPath] path];
For Swift
func createProjectDirectoryPath(path:String) -> String
{
let containerURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.abc")
let logsPath = containerURL!.URLByAppendingPathComponent(path)
//print(logsPath.path);
do {
try NSFileManager.defaultManager().createDirectoryAtPath(logsPath.path!, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
NSLog("Unable to create directory \(error.debugDescription)")
}
return logsPath.path!
}
To Use
var strSavePath : String = self.createProjectDirectoryPath("Large")
Note: After your app group is setup this above code is useful to create folder.
#Hardik Thakkar code for Swift 5
This function create directory in shared app group container and return path to it.
func createProjectDirectoryPath(path:String) -> String
{
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.Notifications.appGroupIdentifier)
let logsPath = containerURL!.appendingPathComponent(path)
//print(logsPath.path);
do {
try FileManager.default.createDirectory(atPath: logsPath.path, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
print("Unable to create directory \(error.debugDescription)")
}
return logsPath.path
}
I'm sending an email, with a PDF attachment, while using UIDocumentInteractionController, like this:
I start by showing the PDF file
-(void)showPDFFile
{
NSURL *url = [NSURL fileURLWithPath:_filePath];
if (url) {
_documentInteractionController =
[UIDocumentInteractionController interactionControllerWithURL:url];
[_documentInteractionController setDelegate: self];
[_documentInteractionController presentPreviewAnimated:YES];
}
}
- (UIDocumentInteractionController *)setupControllerWithURL:(NSURL *)fileURL
usingDelegate:(id <UIDocumentInteractionControllerDelegate>)interactionDelegate {
UIDocumentInteractionController *interactionController =
[UIDocumentInteractionController interactionControllerWithURL: fileURL];
[interactionController setDelegate: interactionDelegate];
return interactionController;
}
When the PDF file is shown, the user clicks the "Export" option and the iOS's "Open with" view appears.
Clicking the email now opens a View Controller ready to send an email.
How would I set the To: CC/BCC and Subject fields programatically?
Thank you!
You can assign the mail's subject by using the UIDocumentInteractionController name property:
_documentInteractionController.name = #"My custom mail subject";
Unfortunately this is the only attribute I've figured out that can be configured via UIDocumentInteractionController.
Unfortunately Florians answer didn't work for me. I had to copy the file locally and then set the URL to the local file. Setting the name only changed the title on the preview, not the filename or subject in the email.
i.e.
NSFileManager* fileManager = [NSFileManager defaultManager];
NSError* err = nil;
NSString* newPath = [appDocumentsFolder stringByAppendingPathComponent:name];
if (![fileManager copyItemAtPath:[[NSURL URLWithString:path] path] toPath:newPath error:&err]) {
// handle error
}
NSURL *fileURL = [NSURL fileURLWithPath:newPath];
_controller = [UIDocumentInteractionController interactionControllerWithURL:fileURL];