Replace Data Container of an iOS .ipa File - ios

I have an app that downloads a whole bunch of data from over 100+ APIs upon successful login. I successfully download the data, and then use iExplorer to extract the data container folders (Documents, Library and Tmp) from the fully loaded application.
I would like to take a blank version of the original app, in .ipa format, and insert those data container folders into that fully loaded .ipa file. Then I will be able to take this new fully loaded .ipa, and use a deployment software to deploy it to a bunch of local user's devices. So everyone will have this fully loaded app.
Please, has anyone done this? Please provide some feedback, and don't argue with my methodology, because this has be done this way due to requirements. Maybe there is a step I'm missing? I'm not sure.

With the source code in hand, you can run the app in the simulator (no need for iExplorer), wait for it to download all the files and browse to the folder on your computer where the app was installed.
From there you can put aside any files you want along with their respective folders. If you're using Coredata there should be a SQLITE database file there somewhere (typically in your Application Support folder) and this might be all you need but it is hard to tell without looking at your implementation details.
Once you have the files you need set aside, add them to the app bundle via Xcode and create code to check whether files already exist (in which case you don't want to replace them), and if not copy all files needed from the bundle into their respective folders.
Here's some semi pseudo-code for you:
NSDictionary *userPrefs = [[NSUserDefaults standardUserDefaults] objectForKey:self.email];
if (![userPrefs[kInitialSetupCompleted] boolValue])
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *destinationFilePath = ...
NSURL *seedFilePath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:kCoreDataStoreName ofType:nil]];
NSError* err = nil;
if (![fileManager copyItemAtURL:seedPath toURL:destinationFilePath error:&err]) {
NSLog(#"Could not copy seed data. error: %#", err);
// Deal with error
} else {
// Set user defaults kInitialSetupCompleted to YES
}
}

Related

iCloud file versions and mystery files

I have been trying to retrofit a large app to have it store its files in iCloud. The app is not really "Document-based" but the files may be thought of as configuration or preference files.
To be honest I haven't followed all the Apple iCloud guidelines. Some really don't fit in with the flow of the app. When the app starts I read the files from the ubiquity directory (using normal file reads) and when I write them I use a normal file write to the ubiquity directory. When the app start, I also call:
[fileManager startDownloadingUbiquitousItemAtURL:url error:&error];
I do have an NSFilePresenter watching for changes in the ubiquity directory. It notifies me of file changes but there are never any conflict notifications.
My problem is that often when I upload a file to iCloud, it will create a separate file with a number appended. E.g.
MyFile.skyset
MyFile 2 .skyset
These seem to show up when more than one app has been writing MyFile.skyset to the ubiquity directory.
They don't seem to be conflicted file versions. If I use NSFileVersion to look for conflicts and other versions, I only see the one version of MyFile.skyset and it is not in conflict.
I can't find any documentation that explains what this "versioned" file is and what to do about it. Any thoughts as to what is going on here?
Well, I don't fully understand the "versions" but the problem was solved by changing how I updated the files in the ubiquity directory.
I changed from:
[fileMgr removeItemAtPath:dstPath error:nil];
[fileMgr copyItemAtPath:srcPath toPath:dstPath error:&error];
To:
NSData *data = [NSData dataWithContentsOfFile:srcPath];
[data writeToFile:dstPath atomically:NO];
I looks like the operation of deleting the file before copying the updated information to the ubiquity directory was confusing the system. After switching to the second form I'm no longer seeing this problem.
Hope this answer helps someone else.

iOS Application Library directory <uid> always gets changed

After reading Apple documentation, I used URLsForDirectory to obtain the Library destination within my app, and my objective is to download content from a hosted service & cache it in the Library folder, so that User cannot interact with the contents through iTunes.
NSArray *docPaths= [[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask];
NSLog(#"%#",[docPaths description]);
NSString *docPath=[(NSURL *)[docPaths objectAtIndex:0] path];
docPath = [docPath stringByAppendingPathComponent:#"audios"];
Running & executing the code several times(various simulators, and iOS 8.0 device) I realized that somehow the content being fetched seems to be no longer accessible, so I logged the library destination path, and after running app every time the destination path seems to have changed:
/var/mobile/Containers/Data/Application/83725F33-C7EA-4F89-B69F-0AECF26FA77A/Library/"
/var/mobile/Containers/Data/Application/4627FC86-C3A4-4A1A-9721-AF73D808433E/Library/"
/var/mobile/Containers/Data/Application/709CCA84-936A-4596-933A-D6779758FF85/Library/
Has anybody faced a similar issue? If so how did it got corrected? And is there anything I've missed out here?
I had the same issue. I think the variable part changes only when the code is recompiled (ie not if you just rerun without making changes), so should not affect a live app. But I decided in the end not to save the path - just to use the same code (as you use above) both when saving and retrieving the data. It seems to work, in spite of the fact that the path actually changes between runs (so the simulator must copy the files across, or rename the folder).

Copy iCloud file to sandbox

I'm looking for a better way to download a bunch of files from my iCloud container to my sandbox. This is what I currently use:
for (FileAttachments *fileLink in items) {
NSURL *cloudFileURL = [fileLink urlForCloudAttachmentWithName];
NSURL *fileURL = [backupCloudLocalDirectoryURL URLByAppendingPathComponent: fileLink.fileName];
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateReadingItemAtURL:fileURL options:NSFileCoordinatorReadingWithoutChanges error:&error
byAccessor:^(NSURL *newURL) {
NSError *blockError = nil;
[fileManager copyItemAtURL:cloudFileURL toURL:fileURL error:&blockError];
}];
}
}
Is there any problem with making a copy of the iCloud item this way? In production, I have some users complaining that all their files weren't downloaded. Is it better to use NSFileManager's startDownloadingUbiquitousItemAtURL:error instead? If so, why?
Thanks.
It's still not completely clear to me from comments how you're discovering the URLs, however some important details that affect your situation are:
Using coordinateReadingItemAtURL:block: has nothing to do with downloading files from iCloud. The purpose of NSFileCoordinator is to coordinate among readers and writers of a file so that, for example, you don't get two threads trying to write to the same file at the same time. You use file coordinators with iCloud because the iCloud system needs to read and write files and so does your app. Using a coordinator avoids corrupting the file, but again, has nothing to do with downloading the file.
To download a file from iCloud you need to use startDownloadingUbiquitousItemAtURL:error: and then wait until the file downloads. The normal flow here is:
a. Use NSMetadataQuery to find files that exist in the iCloud account
b. Use startDownloadingUbiquitousItemAtURL:error: to make sure they're available on the local device.
The reason you need to use this call is simply because that's how iCloud works on iOS. Files don't download until you ask them to download, and this is how you ask. [On OS X it's different, everything automatically downloads.]
You cannot simply copy from an iCloud URL to another location unless you already know that the file has been downloaded. The file-copy operation does not download the file, so if the file isn't available locally, the copy will fail.
You must use a metadata query to identify the files and their download status then, if they have not been downloaded initiate the download, and using the metadata query determine when the download is complete and then copy the file from the ubiquity container to the apps sandbox directory using a file coordinator. If you try copying the file while it's partially downloaded you may get some strange results.
I had the same problem with you.
My case is that: When network disconnects, iCloud service copy file from iCloud container to sandbox. When this line executes, it can not go into the block to copy file. This is the reason why this file can not be copied.
fileCoordinator coordinateReadingItemAtURL:fileURL options:NSFileCoordinatorReadingWithoutChanges error:&error byAccessor:^(NSURL *newURL)
My solution is: Before copying file from iCLoud container to sandbox, you must check network. If it is not available, don't execute this code (return method). If network connects, execute fileCoordinator and copy.
More info: When copy file from ICloud container to sandbox, method fileManager copyItemAtURL:toURL:error: is OK because I implement this method and it's good.
Hope this works.

coredata - deliver / setup default data

I use coreData in my iOS App. It's possible, that the user Add, Delete Data into the Database.
I have to deliver default data ( some different data-sets ).
At the moment, I'm creating the database by first Application launch. I read data from a csv file an create the database with it.
The csv is in the Application sandbox; the coreData (managedDocument) is in ApplicationDocument (creation on runtime...).
It works perfect for me - but I ask me, will Apple allow that, if I push the App to the AppStore?
There is nothing wrong with this approach and it can't be a reason for rejection. There is also another way to do it. You can create the database the way you do it now, copy the .sqlite file and provide it as your default database. Then copy it on app first run. The following code will do it:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent: #"YourDBName.sqlite"];
if (![fileManager fileExistsAtPath:storeURL.path]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"YourDBName" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storeURL.path error:NULL];
}
}
With this approach you will not need to include your csv file in your bundle.
Yes, apple does allow shipping a database populated by default.
the standard way to do it is to ship a default database in your bundle, then at launch time check if there is a database in your application documents directory, and if it does not exist, then copy the database from your bundle to the documents directory.

iOS how to do pre-filled Core data?

I need my application work as follows:
1- User download and startup the application.
2- The application will automatically fill the (based core data) with specific records.
3- The application will works ok.
4- When the user close the application and restart it.
5_ The application will not automatically fill the core data with the specific records because it already there.
6- The user will not be able to add/remove/update the data.
Is there a good technique to do that using only core data and accepted by Apple.
Populate the data in the simulator
Make a copy of the SQLITE database from the simulator folder
Add the database as a resource to your project.
Do something like this before initializing your Core Data stack:
code:
// Put down default db if it doesn't already exist
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:#"MyDataFile" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
Not shown, but critical: all the files you prepopulate data for must be marked as "do not backup", or you will be rejected, per the App Store guidelines. See this SO question for some details on how to do this: Why was my application still rejected after excluding files from iCloud backup using this code?
I can tell you one very easy way to do it. In the app delegate, create a variable in NSUserDefaults and change its value when the application is loaded for the first time. Then depending upon the value of NSUserDefaults, fill the data you want in the Core Data store. And this will happen only once because the value of NSUserDefaults variable is not going to change again.

Resources