I am downloading a file using NSURLSessionDownloadTask. it get downloaded and saved. and I can display data normally.
Unfortunitly I only have the iOS simulator to test. What is happenning if I close the app with the stop button on Xcode. then I relaunch, the file is no longer exists.
But, If I closed it by removing it from apps. running list by clicking cmd + shift + H twice. and reluanch it by tapping app. on simulator. I find the file.
BOOL found = [[NSFileManager defaultManager] fileExistsAtPath:path];
Is this a simulator problem? and I shouldn't worry about it in a real device?
Actually, I just managed to test on iPhone. exactly the same behaviour!! Any explanation.
I call this on the NSURLSessionDownloadTask which I sent a destination block that returns the destination to save:
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(NSProgress * __autoreleasing *)progress
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
Destination block code:
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSLibraryDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
Try to log documentsDirectoryURL and you will see that it is different each time you launch the app. Solution is to save between launches not an absolute URL, but a path relative to NSLibraryDirectory directory. I had the same problem and I have solved it with two methods:
// [self uploadFolder] returns a folder in NSLibraryDirectory
+ (NSString*)relativePathForURL:(NSURL*)url
{
NSURL *uploadFolderURL = [self uploadFolder];
if ([url.baseURL isEqual:uploadFolderURL])
{
return url.relativeString;
}
else if ([url.absoluteString hasPrefix:uploadFolderURL.absoluteString])
{
return [url.absoluteString substringFromIndex:uploadFolderURL.absoluteString.length];
}
return nil;
}
+ (NSURL*)urlForRelativePath:(NSString*)path
{
return [NSURL URLWithString:path relativeToURL:[self uploadFolder]];
}
They should be used following way:
Download file and move it to NSLibraryDirectory folder with some URL
savedURL.
Call relativePath = [self relativePathForURL:savedURL] and save it.
When app is relaunched call savedURL = [self urlForRelativePath:relativePath]
savedURL is valid now.
Heres the solution;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
//location is the temporary "destination" which you returned (can be Temp directory) and it now contains the downloaded file. Now copy the file to a new location (eg documents directory) for future use.
BOOL fileCopied = [[[NSFileManager defaultManager] copyItemAtURL:location toURL:documentsDirectoryURLForSavingFileForFutureUe error:&error];
}
Related
I am trying to get size of the file by its path. Those files are saved videos from my app. i used the below code its returning the error saying there is no file or directory.
NSString *urlPath = [self.localPathArray objectAtIndex:indexPath.row];
NSError *err;
NSDictionary * properties = [[NSFileManager defaultManager] attributesOfItemAtPath:urlPath error:&err];
This is how am saving the file path after downloading the file
- (void)URLSession:(NSURLSession *)session assetDownloadTask:
(AVAssetDownloadTask *)assetDownloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *localPath = location.relativePath;
NSLog(#"localPath: %#", localPath);
[[NSUserDefaults standardUserDefaults]setValue:self.localPathArray forKey:#"AssetPath"];
}
See the error screenshot and my app storage details screenshot below
The path with reference to root directory is called absolute. The path with reference to current directory is called relative
so try
NSString *localPath = location.path;
instead of
NSString *localPath = location.relativePath;
for e.g
- (void)URLSession:(NSURLSession *)session assetDownloadTask:
(AVAssetDownloadTask *)assetDownloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *localPath = location.path;
NSLog(#"localPath: %#", localPath);
[self.localPathArray addObject: localPath];
[[NSUserDefaults standardUserDefaults]setValue:self.localPathArray forKey:#"AssetPath"];
}
for sample see this answer already answered in Stack overflow
Under normal circumstances, downloading (video) files will be saved under the location path (.tmp), then move the file (.tmp) to our target folder using the following de;egate method.
But I want to do the downloading and playing, how can I change the file path(location) to the target path(destinationURL) before I download it.
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *destinationFileName = downloadTask.originalRequest.URL.lastPathComponent;
NSURL *destinationURL = [self.downloadDirURL URLByAppendingPathComponent:destinationFileName];
if([fileManager fileExistsAtPath:[destinationURL path]])
{
[fileManager removeItemAtURL:destinationURL error:nil];
}
BOOL success = [fileManager moveItemAtURL:location toURL:destinationURL error:&error];
}
But I want to do the downloading and playing, how can I change the file path(location) to the target path(destinationURL) before I download it
You can't. What you are doing is correct for a download task: download to where it downloads to (which is no concern of yours), and immediately move it to a useful location as soon as the download is complete.
(Note, however, that you do not need to download a video file merely in order to play it. You can just start playing the file across the Internet. So perhaps the problem here is that you are downloading in the first place.)
I want to copy a file from the iOS 11 Files app to my local app sandbox. For testing purposes it is assumed that the file is locally available in the Files app (downloaded from iCloud to the local storage). The file extension is registered with my app and when a file is pressed in the Files app then my app receives the file URL from the Files app:
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSURL *nsUrl; // comes from Files app. For instance "file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/test.rar"
NSURL *targetUrl; // file in my app's document directory
NSError *coordinatorError = nil;
[fileCoordinator coordinateReadingItemAtURL:nsUrl options:NSFileCoordinatorReadingWithoutChanges error:&coordinatorError byAccessor:^(NSURL *newURL)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
//if ([fileManager fileExistsAtPath: [nsUrl path]])
{
NSLog(#"Copy from %# to %#", newURL, targetUrl);
NSError *copyError = nil;
[fileManager copyItemAtURL:newURL toURL:targetUrl error:©Error];
if (!copyError)
{
// OK
}
else
{
NSLog(#"Files app error: %#", copyError);
}
}
}];
But the operation fails with this output:
2017-11-22 09:30:28.685127+0100 test[434:40101] Copy from file:///private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/test.rar
to file:///var/mobile/Containers/Data/Application/01BB33E6-2790-0FD0-8270-000/Documents/test.rar
2017-11-22 09:30:28.687174+0100 test[434:40101] Files app error: Error Domain=NSCocoaErrorDomain Code=257 "The file “test.rar” couldn’t be
opened because you don’t have permission to view it."
UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/test.rar,
NSUnderlyingError=0x1c084abf0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
Is there something special required to get read access to the external file?
Regards,
Here is how you can access the files and stop when are done with it.
//To gain access
[nsUrl startAccessingSecurityScopedResource]
and
//To stop access
[nsUrl stopAccessingSecurityScopedResource]
i have the same issue to Copy file from iOS 11 Files app to sandbox . finally i solved my issue this link
check here
and the sample code .
[fileURL startAccessingSecurityScopedResource];//fileURL ---> Which FileURL you want to copy
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSFileAccessIntent *readingIntent = [NSFileAccessIntent readingIntentWithURL:fileURL options:NSFileCoordinatorReadingWithoutChanges];
[fileCoordinator coordinateAccessWithIntents:#[readingIntent] queue:[NSOperationQueue mainQueue] byAccessor:^(NSError *error) {
NSData *filePathData;
if (!error)
{
// Always get URL from access intent. It might have changed.
NSURL *safeURL = readingIntent.URL;
// here your code to do what you want with this
}
[fileURL stopAccessingSecurityScopedResource];
}];
I'm developing an app extension for open mode for my document management application. I have already implemented the import mode which is working fine. But in the open mode , when a third party application tries to open any documents from my storage provider, the following methods of file provider is executing multiple times,kind of an inifinite execution and in turn resulting in a memory warning exception.
- (instancetype)init
- (void)startProvidingItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *))completionHandler
And also for your reference the complete code fo file provider as follows
- (NSFileCoordinator *)fileCoordinator {
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] init];
[fileCoordinator setPurposeIdentifier:[self providerIdentifier]];
return fileCoordinator;
}
- (instancetype)init {
self = [super init];
if (self) {
[self.fileCoordinator coordinateWritingItemAtURL:[self documentStorageURL] options:0 error:nil byAccessor:^(NSURL *newURL) {
// ensure the documentStorageURL actually exists
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtURL:newURL withIntermediateDirectories:YES attributes:nil error:&error];
}];
}
return self;
}
- (void)providePlaceholderAtURL:(NSURL *)url completionHandler:(void (^)(NSError *error))completionHandler {
// Should call + writePlaceholderAtURL:withMetadata:error: with the placeholder URL, then call the completion handler with the error if applicable.
NSString* fileName = [url lastPathComponent];
NSURL *placeholderURL = [NSFileProviderExtension placeholderURLForURL:[self.documentStorageURL URLByAppendingPathComponent:fileName]];
NSUInteger fileSize = 0;
// TODO: get file size for file at <url> from model
[self.fileCoordinator coordinateWritingItemAtURL:placeholderURL options:0 error:NULL byAccessor:^(NSURL *newURL) {
NSDictionary* metadata = #{ NSURLFileSizeKey : #(fileSize)};
[NSFileProviderExtension writePlaceholderAtURL:placeholderURL withMetadata:metadata error:NULL];
}];
if (completionHandler) {
completionHandler(nil);
}
}
- (void)startProvidingItemAtURL:(NSURL *)url completionHandler:(void (^)(NSError *))completionHandler {
// Should ensure that the actual file is in the position returned by URLForItemWithIdentifier:, then call the completion handler
NSError* error = nil;
__block NSError* fileError = nil;
//getting the actual fiile from the shared container
NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.company.test.NBox"];
storeURL = [storeURL URLByAppendingPathComponent:[url.path lastPathComponent]];
NSData* fileData = [NSData dataWithContentsOfFile:[storeURL path]];
// TODO: get the contents of file at <url> from model
//Writing the file data to the documentStorage location
//[self.fileCoordinator coordinateWritingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
[fileData writeToURL:url options:0 error:&fileError];
//}];
if (error!=nil) {
completionHandler(error);
} else {
completionHandler(fileError);
}
}
- (void)itemChangedAtURL:(NSURL *)url {
// Called at some point after the file has changed; the provider may then trigger an upload
// TODO: mark file at <url> as needing an update in the model; kick off update process
NSLog(#"Item changed at URL %#", url);
}
- (void)stopProvidingItemAtURL:(NSURL *)url {
// Called after the last claim to the file has been released. At this point, it is safe for the file provider to remove the content file.
// Care should be taken that the corresponding placeholder file stays behind after the content file has been deleted.
[self.fileCoordinator coordinateWritingItemAtURL:url options:NSFileCoordinatorWritingForDeleting error:NULL byAccessor:^(NSURL *newURL) {
[[NSFileManager defaultManager] removeItemAtURL:newURL error:NULL];
}];
[self providePlaceholderAtURL:url completionHandler:NULL];
}
Thanks,
Vsh
I'm also trying to develop an app extension for open mode. I haven't been successful yet but I don't get the infinite execution. Looking at your code, it's possible that storeURL in startProvidingItemAtURL: points to something inside your container. If so, then the assignment to fileData would trigger an infinite recursion.
As a test, try setting fileData with a test message like this:
NSString *message = [NSString stringWithFormat:#"This is a test."];
NSData *fileData = [NSKeyedArchiver archivedDataWithRootObject:message];
If that works, then it's a problem with storeURL and you'll have to figure out some different location to get the data.
(Incidentally, I noticed that you commented out the file coordinator in startProvidingItemAtURL:. I also ended up doing that to prevent deadlocks and because there's a note in the documentation that says "Do not use file coordination inside this method." But it's very confusing because the template code for file providers puts the file coordinator in that method!)
I want to have my app save the documents it creates to iCloud Drive, but I am having a hard time following along with what Apple has written. Here is what I have so far, but I'm not for sure where to go from here.
UPDATE2
I have the following in my code to manually save a document to iCloud Drive:
- (void)initializeiCloudAccessWithCompletion:(void (^)(BOOL available)) completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.ubiquityURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (self.ubiquityURL != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"iCloud available at: %#", self.ubiquityURL);
completion(TRUE);
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"iCloud not available");
completion(FALSE);
});
}
});
}
if (buttonIndex == 4) {
[self initializeiCloudAccessWithCompletion:^(BOOL available) {
_iCloudAvailable = available;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:selectedCountry];
NSURL* url = [NSURL fileURLWithPath: pdfPath];
[self.manager setUbiquitous:YES itemAtURL:url destinationURL:self.ubiquityURL error:nil];
}];
}
I have the entitlements set up for the App ID and in Xcode itself. I click the button to save to iCloud Drive, and no errors pop up, the app doesn't crash, but nothing shows up on my Mac in iCloud Drive. The app is running on my iPhone 6 Plus via Test Flight while using iOS 8.1.1.
If I run it on Simulator (I know that it won't work due to iCloud Drive not working with simulator), I get the crash error: 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[3]'
Well, you've got me interested in this matter myself and as a result I've spent way to much time on this question, but now that I've got it working I hope it helps you as well!
To see what actually happens in the background, you can have a look at ~/Library/Mobile Documents/, as this is the folder where the files eventually will show up. Another very cool utility is brctl, to monitor what happens on your mac after storing a file in the iCloud. Run brctl log --wait --shorten from a Terminal window to start the log.
First thing to do, after enabling the iCloud ability (with iCloud documents selected), is provide information for iCloud Drive Support (Enabling iCloud Drive Support). I also had to bump my bundle version before running the app again; took me some time to figure this out. Add the following to your info.plist:
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.YOUR_BUNDLE_IDENTIFIER</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
<key>NSUbiquitousContainerName</key>
<string>iCloudDriveDemo</string>
</dict>
</dict>
Next up, the code:
- (IBAction)btnStoreTapped:(id)sender {
// Let's get the root directory for storing the file on iCloud Drive
[self rootDirectoryForICloud:^(NSURL *ubiquityURL) {
NSLog(#"1. ubiquityURL = %#", ubiquityURL);
if (ubiquityURL) {
// We also need the 'local' URL to the file we want to store
NSURL *localURL = [self localPathForResource:#"demo" ofType:#"pdf"];
NSLog(#"2. localURL = %#", localURL);
// Now, append the local filename to the ubiquityURL
ubiquityURL = [ubiquityURL URLByAppendingPathComponent:localURL.lastPathComponent];
NSLog(#"3. ubiquityURL = %#", ubiquityURL);
// And finish up the 'store' action
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) {
NSLog(#"Error occurred: %#", error);
}
}
else {
NSLog(#"Could not retrieve a ubiquityURL");
}
}];
}
- (void)rootDirectoryForICloud:(void (^)(NSURL *))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
if (rootDirectory) {
if (![[NSFileManager defaultManager] fileExistsAtPath:rootDirectory.path isDirectory:nil]) {
NSLog(#"Create directory");
[[NSFileManager defaultManager] createDirectoryAtURL:rootDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(rootDirectory);
});
});
}
- (NSURL *)localPathForResource:(NSString *)resource ofType:(NSString *)type {
NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *resourcePath = [[documentsDirectory stringByAppendingPathComponent:resource] stringByAppendingPathExtension:type];
return [NSURL fileURLWithPath:resourcePath];
}
I have a file called demo.pdf stored in the Documents folder, which I'll be 'uploading'.
I'll highlight some parts:
URLForUbiquityContainerIdentifier: provides the root directory for storing files, if you want to them to show up in de iCloud Drive on your Mac, then you need to store them in the Documents folder, so here we add that folder to the root:
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
You also need to add the file name to the URL, here I copy the filename from the localURL (which is demo.pdf):
// Now, append the local filename to the ubiquityURL
ubiquityURL = [ubiquityURL URLByAppendingPathComponent:localURL.lastPathComponent];
And that's basically it...
As a bonus, check out how you can provide an NSError pointer to get potential error information:
// And finish up the 'store' action
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) {
NSLog(#"Error occurred: %#", error);
}
If you are intending to work with UIDocument and iCloud, this guide from Apple is pretty good:
https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/Introduction/Introduction.html
EDITED:
Don't know of any better guide of hand, so this may help:
You will need to fetch the ubiquityURL using the URLForUbuiquityContainerIdentifier function on NSFileManager (which should be done asynchronously).
Once that is done, you can use code like the following to create your document.
NSString* fileName = #"sampledoc";
NSURL* fileURL = [[self.ubiquityURL URLByAppendingPathComponent:#"Documents" isDirectory:YES] URLByAppendingPathComponent:fileName isDirectory:NO];
UIManagedDocument* document = [[UIManagedDocument alloc] initWithFileURL:fileURL];
document.persistentStoreOptions = #{
NSMigratePersistentStoresAutomaticallyOption : #(YES),
NSInferMappingModelAutomaticallyOption: #(YES),
NSPersistentStoreUbiquitousContentNameKey: fileName,
NSPersistentStoreUbiquitousContentURLKey: [self.ubiquityURL URLByAppendingPathComponent:#"TransactionLogs" isDirectory:YES]
};
[document saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
}];
You'll also want to look into using NSMetadataQuery to detect documents uploaded from other devices and potentially queue them for download, and observing the NSPersistentStoreDidImportUbiquitousContentChangesNotification to find about changes made via iCloud, among other things.
** Edit 2 **
Looks like you are trying to save a PDF file, which is not quite what Apple considers a "document" in terms of iCloud syncing. No need to use UIManagedDocument. Remove the last 3 lines of your completion handler and instead just use NSFileManager's
setUbiquitous:itemAtURL:destinationURL:error: function. The first URL should be a local path to the PDF. The second URL should be the path within the ubiquiuty container to save as.
You may also need to look into NSFileCoordinator perhaps.
I think this guide from Apple may be the most relevant:
https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/iCloud/iCloud.html