I have a file on the iOS iPhone emulator (Download folder).
How can my app get the permission to read this file, in Objective-C?
I can get the file path with the following:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentsDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docsDirectory stringByAppendingPathComponent:#"file.txt"];
NSLog(#"%#", filePath);
But how can I read it?
Your application is sandboxed and can access files freely only within its own container. For accessing user personal files, you should leverage UIDocumentPickerViewController class, and let the user pick the file (him/her)self:
UIDocumentPickerViewController *pickerVC = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:#[
[UTType typeWithFilenameExtension:#"xml"]
]];
pickerVC.delegate = self;
[self presentViewController:pickerVC animated:YES completion:nil];
Then you can access whatever the user selected in the delegate method:
#pragma mark UIDocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
NSLog(#"%#", [NSString stringWithContentsOfURL:urls.firstObject
encoding:NSUTF8StringEncoding
error:nil]);
}
Related
I am sending database file successfully between iOS devices with the following code:
-(void) doSendDatabase {
UIView *viewTemp = [[UIView alloc] init];
viewTemp.frame = CGRectMake(0.0f, 0.0f, 300, 300);
NSString *currentDatabaseName;
// This is the full path and file name with ext
currentDatabaseName = [self.databases objectAtIndex:[[mainTableView indexPathForSelectedRow] row]];
NSURL *url = [[NSURL alloc] initFileURLWithPath:currentDatabaseName];
UIActivityViewController * airDrop = [[UIActivityViewController alloc]
initWithActivityItems:#[url]
applicationActivities:nil];
airDrop.popoverPresentationController.sourceView = self.view;
[self presentViewController:airDrop
animated:YES
completion:nil];
[url release];
[airDrop release];
[viewTemp release];}
This code works and the database successfully gets sent from the sending iOS device to the receiving device. However, the databases are stored in the Documents/Inbox folder (by design I suppose). I simply want to move the received database files from the Inbox folder up one level into the Documents folder. From what I'm reading I need to handle this in openURL in the App Delegate - but am not sure how to go about this. Any help would be greatly appreciated.
Thank you.
Ok - here's what I did to resolve the problem.
(1) I created a handleInboxItems method in the App Delegate.
-(bool) handleInboxItems {
bool success = YES;
// Get the DBAccess object
DBAccess *dbAccess = [[DBAccess alloc] init];
// Get the Func object
Func *funcObject = [[Func alloc] init];
NSMutableArray *docDatabases;
// get a list of all database files in the Documents/Inbox folder ans store them in the inboxDatabases array
NSMutableArray *inboxDatabases = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *inboxDirectory = [documentsDirectory stringByAppendingPathComponent:#"Inbox"];
NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:inboxDirectory];
for (NSString *inboxFileAndPath in directoryEnumerator)
{
//check to see if any of the files in the inbox folder end in the database extension - if so then save it in the inboxDatabases array
if ([[inboxFileAndPath pathExtension] isEqualToString:#“dbext”])
{
[inboxDatabases addObject:[inboxDirectory stringByAppendingPathComponent:inboxFileAndPath]];
}
}
// now go through the inboxDatabases array and copy them from the Documents/Inbox folder to the Documents folder
// loop through all inbox database and see if any of the database names already exist in Documents - if so then we need to tack on a sequential number
for (NSString *inboxDatabaseFileAndPath in inboxDatabases)
{
NSString *inboxDatabaseName = [[inboxDatabaseFileAndPath lastPathComponent] stringByDeletingPathExtension];
// Get the databases array from the DBAccess class (from the Documents folder) - need to get each time since we are moving files in there
docDatabases = [dbAccess getAllDatabases];
// does the inbox database already exist in the documents folder?
NSUInteger arrayIndex = [docDatabases indexOfObject:[funcObject databaseNameToFullPathName:allTrim(inboxDatabaseName)]];
int i = 0;
while (arrayIndex != NSNotFound)
{
++i;
NSString *tempDatabaseName = [NSString stringWithFormat:[inboxDatabaseName stringByAppendingString:#" %d"],i];
// see if the database (with sequential number) already exists
arrayIndex = [docDatabases indexOfObject:[funcObject databaseNameToFullPathName:allTrim(tempDatabaseName)]];
if (arrayIndex == NSNotFound)
{
// it does not exist, we can use this name
inboxDatabaseName = tempDatabaseName;
}
}
// give it full path and extension
NSString *docDatabaseFileAndPathToWrite = [funcObject databaseNameToFullPathName:allTrim(inboxDatabaseName)];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
success = [fileManager copyItemAtPath:inboxDatabaseFileAndPath toPath:docDatabaseFileAndPathToWrite error:&error];
if (success)
{
// delete the inbox database file
success = [fileManager removeItemAtPath:inboxDatabaseFileAndPath error:&error];
if (!success)
{
NSAssert1(0,#"Failed to delete inbox database:'%#'.",[error localizedDescription]);
}
}
else
{
NSAssert1(0,#"Failed to copy inbox database to documents folder:'%#'.",[error localizedDescription]);
}
}
[dbAccess release];
[funcObject release];
[inboxDatabases release];
return success;}
(2) Added a call to this new method in the didFinishLaunchingWithOptions in the App Delegate just in case there is anything stuck in the inbox upon startup.
(3) I added the openURL method to the App Delegate in order to call handleInboxItems. After done, I send a notification so that I can refresh my database list.
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
bool success = [self handleInboxItems];
if (success)
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIF_DATABASE_AIRDROPPED object:self];
return success;}
That's it - works as I need it to.
I have have a app working great in ISO7 which has a Public.filename-extension in the info.plist to associate sqlite files, a week ago I could email a sqlite file to the users of the app, they could select the file use the "open in" option to update the app data. Since the upgrade to ISO8 the sqlite file is still associated and give the "Open in" option but when selected it fires up the app, but the data is not updated anymore, anyone else had this problem ?
I fixed the issue by not saving to the NSBundle mainBundle
- (void)handleOpenURL:(NSURL *)url
{
NSData *dbFile = [[NSData alloc] initWithContentsOfURL:url];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSError *writeError = nil;
NSString *filePathx = [documentsPath stringByAppendingPathComponent:databaseName];
[dbFile writeToFile:filePathx atomically:YES];
if (writeError) {
NSLog(#"Error writing file: %#", writeError);
}
[self refresh];
}
I use this code to save some PDF data to a file, send it to another app using the "Open In" menu, then delete the file when that's done:
- (void)openIn:(NSData *)fileData {
// save the PDF data to a temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
BOOL result = [fileData writeToFile:filePath atomically:TRUE];
if (result) {
NSURL *URL = [NSURL fileURLWithPath:filePath];
UIDocumentInteractionController *controller = [[UIDocumentInteractionController interactionControllerWithURL:URL] retain];
controller.delegate = self;
[controller presentOpenInMenuFromBarButtonItem:self.openInButton animated:TRUE];
}
}
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller {
// when the document interaction controller finishes, delete the temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
This has worked fine until iOS 8. Now, the file is created and I can verify that it contains the correct content, the Open In menu appears, I can select an app, and the delegate method runs and cleans up the file. But instead of iOS switching to the selected app and copying the file into it as it did before, the Open In menu simply closes when I select an app, and the file is not copied.
This works if I give the UIDocumentInteractionController an existing file. It also works if I use the provided fileData but change the destination filename to the filename of an existing file. This suggests a permissions problem -- as if new files are created in iOS 8 with default permissions that UIDocumentInteractionController can't read.
Does anyone know what's happening and how I can work around it?
It looks like the order of operations has changed slightly in iOS 8. DidDismissOpenInMenu used to run after the file was finished sending, but now it runs after the file begins sending. This means my cleanup code was sometimes running before the file was finished sending, leaving no file to send. I figured this out after noticing that smaller files were being sent okay; apparently the processing for smaller files was finishing before my cleanup code got them, but the processing for larger files was not.
To ensure the correct timing, but also clean up files that are created when the user opens the DocumentInteractionController and then dismisses the controller without doing anything, I changed my methods like this:
- (void)openIn:(NSData *)fileData {
// save the PDF data to a temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
BOOL result = [fileData writeToFile:filePath atomically:TRUE];
if (result) {
self.sendingFile = FALSE;
NSURL *URL = [NSURL fileURLWithPath:filePath];
UIDocumentInteractionController *controller = [[UIDocumentInteractionController interactionControllerWithURL:URL] retain];
controller.delegate = self;
[controller presentOpenInMenuFromBarButtonItem:self.openInButton animated:TRUE];
}
}
- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application {
// the user chose to send the file, so we shouldn't clean it up until that's done
self.sendingFile = TRUE;
}
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller {
if (!self.sendingFile) {
// the user didn't choose to send the file, so we can clean it up now
[self openInCleanup];
}
}
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
// the user chose to send the file, and the sending is finished, so we can clean it up now
[self openInCleanup];
self.sendingFile = FALSE;
}
- (void)openInCleanup {
// delete the temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
Update for iOS 11
Before iOS 11, it seems that the operating system kept a copy of the file available until the receiving app was finished reading it, even though my cleanup function ran as soon as the file was sent out from my app. In iOS 11, this changed and the receiving app fails to read the file because my app deletes it before that's done. So now instead of saving the temporary file to Documents and using the openInCleanup method to delete it immediately, I'm saving the temporary file to tmp and emptying the tmp folder next time the app launches. This approach should also work with older iOS versions. Just remove openInCleanup, change Documents to tmp in the paths, and add this to applicationDidFinishLaunching:
// clear the tmp directory, which will contain any files saved for Open In
NSString *tmpDirectory = [NSString stringWithFormat:#"%#/tmp", NSHomeDirectory()];
NSArray *tmpFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpDirectory error:NULL];
for (NSString *tmpFile in tmpFiles) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#/%#", tmpDirectory, tmpFile] error:NULL];
}
After reading this post, I already hoped to have found the solution to a similar problem:
For me, as of iOS 8, sharing was only working with Mail.app. It was failing for Dropbox, etc.
Turns out it was something else:
On my interactionController I was setting an annotation like this:
interactionController.annotation = #"Some text"
For unknown reasons, this prevented Dropbox to open at all. There were no error messages or anything. Removing this line solved the issue.
I am new to developing iOS apps and I wish to enable my app to download and read an XML file that will update each time the app is opened.
This is what I have so far:
NSURL * url = #"http://192.168.100.161/UploadWhiteB/wh.txt";
NSData * data = [NSData dataWithContentsOfURL:url];
if (data != nil) {
NSLog(#"\nis not nil");
NSString *readdata = [[NSString alloc] initWithContentsOfURL:(NSData *)data ];
Maybe you can use this code in your AppDelegate didFinishLaunchingWithOptions method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *localPath = [path objectAtIndex:0]; //The apps document directory
NSString* fileURL = #"http://192.168.100.161/UploadWhiteB/wh.txt";
NSData* data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
if (!(data==nil)) {
[data writeToFile:[localPath stringByAppendingPathComponent:#"wh.txt"] atomically:YES];
NSLog(#"Did download file: wh.txt");
}
}
Then you can get the data by using this code in another class:
-(NSString *) getDocumentDirectory {
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [path objectAtIndex:0]; //The apps document directory
}
NSData *xmlData = [NSData dataWithContentsOfFile:[[self getDocumentDirectory] stringByAppendingPathComponent:#"wh.txt"]];
if(!(xmlData==nil)) {
//Do your stuff
}
The didFinishLaunchingWithOptions method will run every time the app starts and download (and replace) the file wh.txt from your server to the file wh.txt on your device (in your apps document folder).
Then if you wan't to read the file you can use the NSXMLParser. See:
How to Parse XML Files in Xcode and
NSXMLParser example
Does it solve your problem?
If not, provide more information and I will edit my answer.
I am added dropbox support to my app, everything works perfect, the authentication, the login/out, but I can't upload files. I am using
NSArray *p = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* docDir = [p objectAtIndex:0];
NSString *localPath = docDir;
NSString *filename = #"Documents";
NSString *destDir = #"/";
[[self restClient] uploadFile:filename toPath:destDir
withParentRev:nil fromPath:localPath];
to upload my applications /Document directory to Dropbox, but nothing is uploaded. I don't get any error message. The directory Apps/My-Applications-Name is created, but with no content inside.
Anyone know why?
I also have this two delegate methods, but I still don't get a log. I also have <DBRestClientDelegate> behind the #interface … ():
- (void)restClient:(DBRestClient*)client uploadedFile:(NSString*)destPath
from:(NSString*)srcPath metadata:(DBMetadata*)metadata {
NSLog(#"File uploaded successfully to path: %#", metadata.path);
}
- (void)restClient:(DBRestClient*)client uploadFileFailedWithError:(NSError*)error {
NSLog(#"File upload failed with error - %#", error);
}
It sounds like Documents is a directory, but uploadFile is used to upload a file. I don't think there's a method in the Core API to upload the contents of a folder, so you'll have to walk through the folder and upload each file individually.
Did you implement the Dropbox delegate methods? Uploadcompleted and uploadfailedwitherror? You also need to specify in your app delegate that you are the Dropbox delegate in order to implement those methods. Make sure your local path is correct, your upload path is.
Add DBRestClientDelegate to your app delegate or whatever class uploads
NSArray *p = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentDirectory = [p objectAtIndex:0];
NSString *filename = #"png.png";
NSString *destDir = #"/";
[[self restClient] uploadFile:filename
toPath:destDir
withParentRev:nil
fromPath:[documentDirectory stringByAppendingPathComponent:filename] ];
the "fromPath" must be the FULL PATH of the source file. The API Document is WRONG.
Did you check that you're making setting the delegate from the main thread? Otherwise, your thread likely doesn't have a runloop and the delegate callbacks will never be called.
UPDATE my problem was ARC destroying the DBRestClient object before the upload was complete.