NSFileManager startDownloadingUbiquitousItemAtURL not working correctly on iOS14 - ios

I have an App in the AppStore since 2013 and it always worked flawlessly. It creates and syncs files on iCloud.
Since iOS14 startDownloadingUbiquitousItemAtURL only downloads a fraction of the existing files. If I start the App in the simulator and iOS14 (and of course on the real devices as well). If I start the App in a simulator using iOS12 or 13 it works as expected.
I don't find anything on the web regarding possible changes in the startDownloadingUbiquitousItemAtURL method.
Here's the code in question:
...
BOOL tt = [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:cloudFileUrl error:&error] ;
if (error != nil)
{
NSLog(#"ERROR Loading %#", cloudFileUrl) ;
}
...
tt is true and error is nil, so it seems the sync process has started correctly
However, If I try to access the files with
NSArray *dirContent = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:cloudFileUrl includingPropertiesForKeys:[[NSArray alloc] initWithObjects:NSURLNameKey, nil] options:NSDirectoryEnumerationSkipsHiddenFiles error:&error];
The the array dirContent contains only 3 or 4 elements, even though the folder contains 10 files.
Any idea what the problem could be? I have opened a bug with Apple as well.

Ok, the solution ist that this call does not read some files when they are binary. Alleged text files were found.
NSArray *dirContent = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:cloudFileUrl includingPropertiesForKeys:[[NSArray alloc] initWithObjects:NSURLNameKey, nil] options:NSDirectoryEnumerationSkipsHiddenFiles error:&error];
I've replaced the call with this
NSArray *dirContent = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:cloudFileUrl includingPropertiesForKeys:[[NSArray alloc] initWithObjects:NSURLNameKey, nil] options:0 error:&error];
and now it works again.

Related

App Crashes at startup on App Store review - works perfectly on devices

I have an app that crashes on startup in the app store review, but works perfectly otherwise on devices and on the simulators. Here are the specifics.
The app is written for IOS 7, and is being tested on IOS 8.x. I symbolicated the crash logs from Apple, and it is crashing on the first attempt to access information stored in the pre-populated Core Data sqlite db.
This code does the db copy at startup:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CC.sqlite"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *storePath = [documentsDirectory stringByAppendingPathComponent: #"CC.sqlite"];
// Check if the sqlite store exists
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) {
NSLog(#"sqlite db not found... copy into place");
// copy the sqlite files to the store location.
NSString *bundleStore = [[NSBundle mainBundle] pathForResource:#"CC" ofType:#"sqlite"];
[[NSFileManager defaultManager] copyItemAtPath:bundleStore toPath:storePath error:nil];
}
else {
NSLog(#"Already exists");
}
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{ NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"} };
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"error %#, %#", error, [error userInfo]);
}
return __persistentStoreCoordinator;
}
We have run this a couple of hundred times on various IOS devices (by several different people) without any problems at all, but invariably get a startup crash at Apple.
It is clear to me from the crash logs that the app (during app store review) is trying to access a non-existant sqlite db through Core Data, but I have no idea why this is happening only at Apple, and why I cannot reproduce the error. I am not sure what other info to add to the question but will happily update as required.
Any advice gratefully received....
I will answer my own question here, although the code changes I made while trying to solve the problem makes posting the code not very helpful.
I had to put the startup database operations in a completion block. This involved moving everything into my initial startup view controller, and disabling my tab bar buttons while the database was initialized.
What was happening was certain items in the Core Data sqlite database were being required before it was completely preloaded, thus the crash.
More on blocks can be found here

Finding out if the device is locked, from a Notification Widget

I'd like to know if the device is locked when I'm loading my Notification/Today widget, so I can show the widget appropriately. (it's financial, and we don't want to show balances on a locked phone)
On devices with TouchID, I can just try to access the Keychain, and if I get
errSecInteractionNotAllowed
back, it's locked. All good. This doesn't work on devices without touchID (but with a PIN). I've found a few things, which recommend using
[[UIApplication sharedApplication] protectedDataAvailable]
However I don't have [UIApplication sharedApplication] in a widget.
Any ideas where and how to do this? I just need a yes/no: is the device locked.
Thanks
[UPDATE: here's the code I have]
Getting the filename:
+ (NSString *)lockedDeviceFilename {
NSURL *directoryUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:USER_DEFAULTS_GROUP_NAME];
return [directoryUrl.path stringByAppendingPathComponent:#"security.dummy"];
}
Writing / creating the file (in the app, not the extension:
NSError *error = nil;
NSString *documentPath = [FOOStorageGatekeeper lockedDeviceFilename];
[[NSFileManager defaultManager] removeItemAtPath:documentPath error:&error];
BOOL created = [[NSFileManager defaultManager] createFileAtPath:documentPath
contents:[#"super secret file contents. we only care about the permissions" dataUsingEncoding:NSUTF8StringEncoding]
attributes:#{NSFileProtectionKey : NSFileProtectionComplete}];
Reading:
BOOL isReadable = [[NSFileManager defaultManager] fileExistsAtPath:[FOOStorageGatekeeper lockedDeviceFilename]];
NSLog(#"isReadable? %#", isReadable ? #"YES" : #"NO");
It's always able to read the file, even on a TouchID device with the screen locked. If I look at the attributes, it shows the NSFileProtectionKey is set to NSFileProtectionComplete... but I can STILL READ IT :(
Update: found it. Marking Ian's answer as correct
Create a file with NSFileProtectionComplete while your app is running and then attempt to access it from your extension. If you can't access it, the screen is locked.
[[NSFileManager defaultManager] createFileAtPath:someFilePath
contents:[#"Lock screen test." dataUsingEncoding:NSUTF8StringEncoding]
attributes:#{NSFileProtectionKey: NSFileProtectionComplete}];
EDIT: Final steps included to complete solution and consolidate answers. (Remaining work provided by Nic Wise.)
NSData *data = [NSData dataWithContentsOfURL:[FOOStorageGatekeeper lockedDeviceUrl] options: NSDataReadingMappedIfSafe error:&error];
if (error != nil && error.code == 257) {
NSLog(#"**** the keychain appears to be locked, using the file method");
return YES;
}
The other method, using errSecInteractionNotAllowed also works, but only for TouchID devices.
I found the answer (indirectly) here (rego with the iOS dev program most likely needed)
Finally, after 3-4 days of looking, found the answer. It was more in how I was reading the result back. Ian is right: I need to create the file using createFileAtPath, but then read it back using
NSData *data = [NSData dataWithContentsOfURL:[FOOStorageGatekeeper lockedDeviceUrl] options: NSDataReadingMappedIfSafe error:&error];
if (error != nil && error.code == 257) {
NSLog(#"**** the keychain appears to be locked, using the file method");
return YES;
}
The other method, using errSecInteractionNotAllowed also works, but only for TouchID devices.
I found the answer (indirectly) here (rego with the iOS dev program most likely needed)
I tried that and my file was always readable (in lock screen or not).
I found this document :
https://www.apple.com/business/docs/iOS_Security_Guide.pdf
It appeared that the files are locked 10 seconds after the device is locked.
Knowing that, you can create the files from the extensions, and it seems to work.

delete files from database permanently ios

I am Making an Audio recorder(m4a extension files). I am Giving a particular URL for the output of the recorded File(in directory).
I am able to play it, save the path of the file in database and can retrieve it later. EVery thing is going Fine. BUT I am not able to delete the saved/unsaved files. Every time I record an audio , the file is taking a permanent space. Am not able to delete them.
I tried it over internet(stackoverflow ofcourse). I got Links like this: I have video URL and want to delete it from the iPhone using this URl
But they are showing COCOA ERROR 4 when ever i try to delete them using codes like this: [[NSFileManager defaultManager] removeItemAtPath:strPath error:&error];
Please suggest, and reply
You typically accomplish this for resources you've saved in your Apps documents directory like this:
unlink([pathForURL UTF8String]);
where pathForURL is an NSString that describes the path to the resource you're deleting.
This is the path earlier i was getting , at which i was unable to write file
/var/mobile/Applications/8584F54E-75D2-4833-8826-29C125E53DBC/Library/Documentation/291013193758w.png
This morning, I just run my code once again ,. now its showing path
/var/mobile/Applications/DDA14123-6A88-4756-B2E4-C4A3AA39AA5B/Documents/291013081335test.png
on this path am able to write my file
the Difference between two paths is that, first one is of Library/Documentation , where as second one is of Documents
dont know the difference, but it is working now
There may be case that file path which you provide is not correct. If its correct then try following with URL, it might solve your issue
NSString *str= [outputFieldURL absoluteString];
NSError *error;
NSURL *url = [NSURL URLWithString:str];
BOOL success = [[NSFileManager defaultManager] removeItemAtURL:url error:&error];
if (!success) {
NSLog(#"Error removing file at path: %#", error.localizedDescription);
}
else
{
NSLog(#"File removed at path: %#", error.localizedDescription);
}
}
Before deleting the file you have to check file there or not :
NSFileManager* manager = [[NSFileManager alloc] init];
if ([manager fileExistsAtPath:path]) {
[manager removeItemAtPath:path error:&error];
}

Providing Initial Data with Core Data in iOS

Here's what I need: To have a sqlite file populated with example entities that I made on the iPhone simulator, and then copy that file when the app initially runs for all my users.
What I've done:
I created a bunch of entries within the simulator.
I found the sqlite file attached to my app within the iPhone Simulator iOS folder on the MAC.
From the three files, .sqlite, .sqlite-shm, .sqlite-wal I simply copied the .sqlite file to my xCode project.
When I ran the app, the .sqlite file showed up empty!
How do I fix this?
Thank you!
EDIT:
What significance does the .sqlite-wal and .sqlite-shm have?
Why do they exist and why did not exist prior to iOS7?
first steps R OK but then U have to load the database
U need smth like this:
- (void)copyPreparedDatabase{
__persistentStoreCoordinator = nil;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"DATABASE.sqlite"];
NSString *storePath = [storeURL path];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"DATABASE" ofType:#"sqlite"];
if (defaultStorePath) {
NSError *error = nil;
if ([fileManager fileExistsAtPath:storePath]) {
[fileManager removeItemAtPath:storePath error:&error];
}
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:&error];
NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:storePath error:&error]) {
}
}
}
And then U call it from - (NSPersistentStoreCoordinator *)persistentStoreCoordinator from
AppDelelate.m
Advice: Do some custom switch like
#define IMPORT_PREPARED_DATABASE
do it like this:
if (![fileManager fileExistsAtPath:storePath] && !IMPORT_PREPARED_DATABASE) { //&& 1==2
[self copyPreparedDatabase];
}
so U can control when to build new prepared database or when to use existing one....
Note:
When U build new prepared database sto simulator, copy database and paste it over the old one...
This tutorial can be a big help.
You made almost everything, but you are missing an important step. You need to copy the sqlite to your xcode project then you need to check in your persistentStoreCoordinator if the sqlite file exists in your Documents area. If not, you need to copy it. Jump to "Doctor, you’re needed in Xcode" section in that tutorial :).

writeToFile:atomically: does not work on iOS 6.1.3?

I have a plist (NSDictionary) which I intended can be changed by user data input. Here is what I have done.
NSString *path = (the path for the plist)
NSMutableDictionary *plistFile = [[NSDictionary dictionaryWithContentOfFile:path] mutableCopy];
[plistFile setObject:(an NSString object) forKey:(an NSString key)];
[plistFile writeToFile:path atomically:YES];
So this is what I have coded. It works well on iPad 3 (new iPad) (iOS 6.1.2) and my XCode (4.6) simulator (iOS 6). However, it does not work on my iPad mini (iOS 6.1.3). I have found the problem which is in the last step. When I wrote
BOOL success = [plistFile writeToFile:path atomically:YES];
NSLog(#"%#",#(success));
The console always prints 0 which means it does not succeed. But on my iPad 3 and simulator it prints 1, which means success.
That is all I can describe because there is no exception being thrown out or other output. By the way, my iPad 3 (on which it works) is jailbroken but the iPad mini is not. Nevertheless, I use my developer account to codesign on both devices. Can anyone help me? Or else can anyone point to me a new solution?
Try to use
NSError * error = nil;
BOOL success = [plistFile writeToFile:path options:NSDataWritingAtomic error:&error];
NSLog(#"Success = %d, error = %#", success, error);
And then check what error occures. Maybe it can helps you.
And you can write only to the document directory. You may have some troubles with your path.
I can't see from your code if you have done this, but your path needs to be the sandboxed app directory.
NSArray* pathList = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString* path = [[NSString alloc] initWithFormat:#"%#", [pathList objectAtIndex:0]];

Resources