Save a NSArray to iCloud and retrieve it - ios

I have an app that currently saves a NSMutableArray to a file in the documents directory on the iPad. This is saved by
[residentData writeToFile:filePath atomically:NO];
and is read back in by;
residentData = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
I need to write this file to iCloud, as the data needs to be available across several devices. I have tried the following to create the file in iCloud;
NSData *myFileData = [NSKeyedArchiver archivedDataWithRootObject:residentData];
[fileManager createFileAtPath:[icloudURL path] contents:myFileData attributes:nil];
Which does indeed create a file in the correct place. I am struggling though to understand how to read this file back into my NSMutableArray (residentData). I have tried;
residentData = [NSKeyedUnarchiver unarchiveObjectWithData:myFileData];
But this doesn't decode into the array correctly. Please can someone show me the error of my ways - I suspect a fundamental misunderstanding on my part of NSData is to blame, but any pointers would be welcome.

It finally clicked, my approach was wrong. I don't need to use any of the above. Once I have done the;
[fileManager setUbiquitous:YES itemAtURL:currentPlace destinationURL:icloudURL error:&error];
The system takes care of everything else - i.e. just accessing the file normally with
residentData = [[NSMutableArray alloc] initWithContentsOfURL:icloudURL];
Has worked.

Related

Write Files into iOS App Bundle, or Show 2 directory's content?

In my existing app, I have about 450 very small PDF files that are lyrics to songs. They are all stored in a directory called (appropriately) "ThePDFs". The downside with this is that every time I want to add a new set of lyrics, I have to release an update to the app. What I'd love to do is use something like the Parse server to point toward PDFs stored online, and allow the user to select those and add them to their existing list.
However, I don't think it is possible or allowed for me to take an outside PDF and write them into that directory called "ThePDFs"? If it's not allowed, what would be a good way to accomplish this, in such a way that all the songs are in one list? I know I could download them into a documents folder for the app, but then I have songs in multiple locations, and accessing them all in one tableview might be more difficult. I suppose at first launch of the app, I could have all the PDFs copy from the bundled section into the documents directory, and search to it from that?
I know I have a lot of options, I'm just curious if there's something I'm missing.
UPDATE
So, I'm trying to do the first option of leaving PDFs where they are, and make a PLIST that would have an item with each song's name, along with the location for where it is in the file system. Then, when new songs are added, it would do the same, and I could read from the PLIST file to get all songs and be able to open them, no matter where they are. My issue right now is getting the initial PLIST set up, and the logic behind what I would do for each subsequent addition. The issue I get is
NSDictionary initWithObjects:forKeys:]: count of objects (0) differs from count of keys (1)'
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"mastersonglist.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path]) {
path = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:#"mastersonglist.plist"] ];
}
NSError *error = nil;
NSMutableDictionary *data;
if ([fileManager fileExistsAtPath: path]) {
data = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
}
else {
// If the file doesn’t exist, create an empty dictionary
data = [[NSMutableDictionary alloc] init];
}
NSBundle *bundle = [NSBundle mainBundle];
self.files = [bundle pathsForResourcesOfType:#"pdf" inDirectory:#"thepdfpowerpoints"];
NSMutableArray *names = [NSMutableArray arrayWithCapacity:[self.files count]];
for (NSString *path in self.files) {
[names addObject:[[path lastPathComponent] stringByDeletingPathExtension]];
}
for (NSString *test in names) {
// [data setObject:names forKey:#"thename"];
// [data writeToFile:path atomically:YES];
NSDictionary *innerDict;
NSLog(#"NAMES%#", names);
innerDict = [NSDictionary dictionaryWithObjects:
[NSArray arrayWithObjects: names, path, nil]
forKeys:[NSArray arrayWithObjects:#"Names", #"Path", nil]];
NSLog(#"DICT%#", innerDict);
NSMutableDictionary *plistdictionary = [[NSMutableDictionary alloc]initWithContentsOfFile:path];
NSMutableArray *notes=[plistdictionary objectForKey:#"Questions"];
//NSLog(#"QUESTION %#", innerDict);
[notes addObject:innerDict];
NSLog(#"NOTES %#", notes);
NSDictionary *outerDict = [NSDictionary dictionaryWithObjects:
[NSArray arrayWithObjects: notes, nil]
forKeys:[NSArray arrayWithObjects:#"Questions", nil]];
id plist = [NSPropertyListSerialization dataFromPropertyList:(id)outerDict
format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[plist writeToFile:path atomically:YES];
}
First, you're correct, you cannot add files to the app bundle at run-time.
So, various ways to approach this, depending on what functionality you want to provide to the user.
First, you could:
include the existing PDFs in your app bundle
check a remote server for new PDFs on launch
download the new PDFs into a local app folder
generating a data list from both locations to display in a single table would really be a pretty straight-forward and not-at-all complex task.
Second, you could:
include NO PDFs in your app bundle
check a remote server for new PDFs on launch (on first launch they'd all be new)
download the PDFs (on a background thread, of course) into a local app folder
Now you only need to manage the files in one location.
Third, you could:
include NO PDFs in your app bundle
check a remote server for the list of available PDFs on launch
retrieve the PDFs on-demand when the user selects them
optionally "cache" them locally - so only retrieve if the PDF has not already been selected / downloaded
Edit
You don't really need to know the file locations...
I don't know what information you have associated with each PDF file, so, to simplify, let's assume you're displaying a list of PDF file names (no other data).
The general idea could be:
get the list of PDF files in the bundle
append the list of PDF files in the downloads folder
sort the full list alphabetically
user selects a PDF
does file downloadsPath/filename.pdf exist?
Yes? load it
No? load it from bundle
Same principal if you have some sort of data file - .plist, csv, json, whatever - that would have maybe filename, song title, artist, date, etc.
load the data for the PDF files included in the bundle
append the data for the PDF files in the downloads folder
sort the full list alphabetically
user selects a song
does file downloadsPath/filename.pdf exist?
Yes? load it
No? load it from bundle
No need to track the file location in your data -- just find it when you want to load it.

IOS Memory Management - writing video to temp file

I'm creating an app that plays back multiple videos and photos in a snapchat story fashion. I fetch each of these videos data and write it to a temp file in order to play with AVPlayer.
NSString *outputPath = [[NSString alloc] initWithFormat:#"%#%#", NSTemporaryDirectory(), [NSString stringWithFormat:#"output%i.mov", i]];
[data writeToFile:outputPath atomically:YES];
In viewWillDissapear I delete all of these files with:
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
for (NSString *file in tmpDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#%#", NSTemporaryDirectory(), file] error:NULL];
}
But when I look at how much memory my app is using, in settings, its around 300 mb. Is there a way to see what is actually taking up all this memory? I would think this is high since I'm not caching anything besides the currentUser data (name, profileImage).
Should I be doing something else to clean the temp files?
But this line is absolutely wrong:
[data writeToFile:outputPath atomically:YES];
The implication is that at some moment — this moment — you are holding an entire video in memory (as data). That is completely incorrect; it should never happen. You should download to a file on disk, and if necessary, write that file to another file; you should never attempt to load the entire video into memory at once.
As #peter mentioned, you can use streaming Video streaming.
Or else If you really want to download video file and store it into the disk space and want to utilize it later part then you should follow apple recommended way i.e NSFileHandle class to optimize your application heap memory while downloading larger files.
NSFileHandle

Terminated due to memory error iOS?

I am working on a project where I need to manage videos. I need to rename or delete video. For that we need to hold the video in NSDATA and then manage it.
But I am getting an error message as Terminated due to memory error on below statement.
Edited
NSData *data=[NSData dataWithContentsOfFile:self.path];
if (data){
BOOL success = [data writeToFile:videopath atomically:NO];
}
self.path contains the path of video file. It works in small size video (of 4-10 mins) But it crashes in large size video (bigger than 20-30 mins).
Please advice.
Use this code instead loading the video file to memory, Your code will work with small files but u gonna fail with big files.
if ( [[NSFileManager defaultManager] isReadableFileAtPath:source] ){
[[NSFileManager defaultManager] copyItemAtURL:source toURL:destination error:nil];}
You are maintaing the full video as NSData inside the application. Instead of using a Video file as NSData, copy the video to some where (e.g NSTempoaryDirectoy). You can delete or rename the Old video.
I am not sure what the requirement for you. from your question I understood that you need to rename your video file. for renaming, why we need to go for reading it as NSdata and again writing the same. for Renaming try the below code.
NSFileManager *filemanager = [NSFileManager defaultManager];
if ([filemanager fileExistsAtPath:filePath])
{
NSString *target = [[filePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:newnameofthefile];
[filemanager moveItemAtPath:filePath toPath:target error:nil];
}
I hope this may help you..

How can I save document files to the SQLite Database in iOS to view them later in the program?

I'm having a problem with the code I'm writing.
I'm writing an iOS program (I'm an iOS rookie) which basically requires me to use quick look framework to view some documents on the iPhone (*.doc, *.ppt, *.pdf etc..) which are stored in the database (Core Data - SQLite, nothing external). I need to add the files somehow, but since iOS isn't really allowing me to browse through its file system I can't find and save the documents i need in database. Which kinda blocks everything else i need to do until I can get those documents from the database. (to set up table views that list the files and the details about the files etc.)
This is for a class project so it doesn't need to be perfect condition, I just need to be able to browse through a few documents while I'm presenting the project. I can add all the documents I'm going to use at one time while I'm coding and I won't need to be able to add any new files when I'm using the program during the presentation. And I don't want it to make it more complicated if i don't have to. Like connecting to an external database with the files already saved in and use a php buffer-page to connect to that database or anything like that. I don't have the necessary server system to execute that php file. I want this operation to be done inside the phone.
The solutions I was able to think of so far:
Grab some random office files from the internet and save them into the database. Then use them later.
Create image scans of some office files and "cheat" by using the scanned image instead of actual documents.
I would really appreciate it if someone can tell me another and much easier way to handle this. Please just keep in mind that while I have a programming background with Java and C#, I'm still an iOS rookie and just barely moving on from scratching the surface. So it is likely that I don't know about something iOS provides by default and I'm just pulling my hair out for nothing.
I think thats it, I hope I didn't forget anything. If you need more details I'm going to be here and I can provide them almost instantly. Thanks everyone in advance for your help.
It sounds like NSFileManager will help you.
If you place your documents into your project tree, they will be available to your app. Use NSFileManager to copy them into the app's Documents folder using something like:
- (void)placeBundleFileInDocuments:(NSString *)filename
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:[[filename lastPathComponent] stringByDeletingPathExtension] ofType:[filename pathExtension]];
NSString *documentsFolderPath = [NSString stringWithFormat:#"%#/", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
NSString *path = [documentsFolderPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#", filename]];
if ([fileManager fileExistsAtPath:path])
return;
NSError *error = nil;
[fileManager copyItemAtPath:bundlePath toPath:path error:&error];
if (error) {
NSLog(#"Unable to copy file (%#).", error.localizedDescription);
}
}
Then, you can use NSFileManager to retrieve details about the files. You might find this method useful:
- (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error
I hope this helps!

Invalidating QLPreviewController "cache"

QLPreviewController seems to cache file contents based on the local file's URL. In my application, the file contents can be updated remotely and would cause the new contents to be downloaded.
If I view a file in QLPreviewController, update it remotely, then re-preview it, the file does not show up as updated.
The file is definitely updated on disk, and other controls show the correct updated file.
The workaround I'm using right now is to basically move a file when it's previewed to a unique filename (using timestamp), which will of course not be in the QLPreviewController's cache. However, this has other repercussions, for example, if the app is killed or it crashes (god forbid), I won't know "where" to find the downloaded file.
I'm looking for less invasive hacks, or solutions to making QLPreviewController refresh its cache. The APIs don't seem to expose anything, so don't be afraid to submit a hack if it's less gross than the one I've presented above (not including copying/moving the file to a guaranteed unique URL, which I am already utilizing).
Just ran into this issue myself. I solved it by recreating the QLPreviewController each time I reload an item with the same name as the currently viewed item. Creating a new QLPreviewController clears the cache.
I know this is an old question but someone might have the same problem and find this answer helpful.
You should use refreshCurrentPreviewItem after downloading complete
I had the same problem. Opening a locally generated CSV file.
I have my _previewController* setup as a #property of my controller. Then what i did:
- (void)viewDidLoad
{
[super viewDidLoad];
self.previewController = [[QLPreviewController alloc] init];
_previewController.delegate=self;
_previewController.dataSource=self;
}
- (void)previewCSV
{
[_previewController reloadData]; // this triggers a reload
[self presentModalViewController:_previewController animated:YES];
}
IN other solution that comes to mind (not tested).
Depending on your URL, you could add something like http://url?time=123456 to your URL. Like this you change the URL but without side effect. The time (or any other parameter) you can change on each request.
It's the ugliest bug in iOS. Cache management in iOS 5 and beyond. I think is the same reason that makes iCloud buggy, Share-at-Home crashing and so on. Bad cache managements and so worst synchronization systems.
Well, my solution for this was to store the download file in a folder and use the current date to name the folder. It is equivalent to #Rogier's solution, but this works always. You get a name for the folder, for example, with [[NSDate date] description]. Instead of saving the file replacing the old one, you delete previous file, delete previous folder and save new file in a new folder. It's working fine for me.
Just remove all files from tmp directory like this:
- (void)clearCache
{
NSString *tempPath = NSTemporaryDirectory();
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tempPath error:nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (int i = 0; i < [dirContents count]; i++) {
NSLog(#"Directory Count: %i", [dirContents count]);
NSString *contentsOnly = [NSString stringWithFormat:#"%#%#", tempPath, [dirContents objectAtIndex:i]];
[fileManager removeItemAtPath:contentsOnly error:nil];
}
}

Resources