I'm using NSCoding to manage a custom object with a few different fields, one of them images. I'm only targeting iOS 5.0+ and have switched to ARC. I have everything working but am focused on performance--I haven't seen a question like this asked, so here it goes:
I transform the UIImage into NSData and add it to the main NSCoding file (a plist, if it matters) for storage on the disk. If there is more than one image, the image names become sequential (e.g. image1, image2, image3.) I then use the image both in a UITableView (as a resized thumbnail) and in a detail view. The negative side to this is that the plist balloons in size, which means slow initial load times when I use it because it's loading all of the NSData at once.
What is the best way to eliminate this problem and only force the loading of one image at a time?
What I've thought of:
I write the NSData to the disk, add an array to the plist, and add only a reference to the filename of each image to the array. I suppose I'd then reference the image filename at specified position, find it on the disk, and use it?
Any and all thoughts would be most welcome. I'm more stuck on the conceptual implementation than anything else and, funnily enough, this is not an oft-discussed topic.
Thanks,
EDIT:
As requested below, here's an example of taking an image and turning it into NSData:
UIImage *originalImage;
NSData *imageData = UIImagePNGRepresentation(originalImage);
//I save it all to the app's document directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//I am using the images in a tableView and thus found it easiest to append the row number to each image filename.
//'y' below is just an integer that corresponds to the number of items in the master array
NSString *fileName = [NSString stringWithFormat:#"image%d.png",y];
documentsDirectory = [documentsDirectory stringByAppendingPathComponent:fileName];
[imageData writeToFile:documentsDirectory atomically:YES];
// NSLog(#"The filename is %#", fileName);
//newObject is an instance of my NSCoding object
[newObject setImageName: fileName];
Your suggestion is sound. Imagine a relational database. I would never save the images in one field as a blob. A filesystem is a very good place to save binary date in large amounts. It also gives you easy ways to duplicate files and so on.
So saving a reference in your plist will make parsing really fast and lazy loading an easy task to process images only when you need them.
iOS Programming: The Big Nerd Ranch Guide has a perfect example of this solution in chapter 14 of the 3rd Edition. Just download the code that is accessible from their site for a completed solution.
iOS Programming 3rd edition
Download Solutions
Related
I've seen various questions here pertaining to saving NSArrays/NSDictionaries, but I'm a bit confused about what to do when some of the subelements are UIImages.
To give a little context, the app is essentially a blog-type app. When the user is composing a new entry, their post can contain the following:
Up to 3 images from their photo album
Text
Location
In essence, I'm trying to implement a "Save Draft" functionality to the app if the user decides to temporarily cancel their blog post. When the user cancels the blog post, they will be asked in a UIActionSheet if they would like to save their draft. When the user wants to post again, they can begin from where they left off with their saved draft.
At this point, I would need to save these following objects:
1) NSArray of selected photos
---> contains NSDictionaries (up to 3)
--------> UIImage (large sized version)
--------> UIImage (thumbnail sized version)
2) NSDictionary of NSValues (just some view x,y position data)
3) Text -- NSString data of the blog text they have written
4) Location text -- NString data of their current location
Given that I need to save the above 1~4 data in order to make the "Save Draft" functinality, what is the best way to do this? Should I make a special class to hold all of this data? Also, do I first need to make the UIImages into NSData before I can save them to disk?
Thank you!!
Yes a class/model like structure make more sense and easier to handle as well.
Something like-
Interface Blogdata
NSArray *selectedPhoto;
NSDictionary *positionValues;
NSString *blogText;
NSString *locationText;
and then you can make one more model for photo data;
Interface Photodata
NSDictionary *photo;
UIImage *largeImage;
UIImage *thumbImage;
All of the properties that you mentioned seem like they would belong in a Blog class. Are they already grouped together? A Blog object could capture the state of the draft with variables (properties) of the object being the four things you mentioned. You can then save the Blog object as NSData and read it when the user wants the draft again.
The advantage of this is that you only have to worry about saving one object, instead of having to think about saving four each time (and retrieving them).
The easiest way would be to save the images to the apps documents folder and save the NSArray of filenames and other data that can be represented as text in a drafts.plist.
filenameStr = [NSString stringWithFormat:#"image1.png"];
fileManager = [NSFileManager defaultManager];
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
imagePath = [documentsDirectory stringByAppendingString:#"/"];
imagePath = [imagePath stringByAppendingString:filenameStr];
NSData *imageData = UIImagePNGRepresentation(myImage);
[imageData writeToFile:imagePath atomically:YES];
After doing some research I am a little confused. I can grab a single image from a url if I know the exact directory for example,
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.myUrl.com/pic/workaholics.png"]]];
UIImageView *view1 = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:view1];
This of coarse works fine, but what if you didn't know the exact path. In Linux you could always do some sort of recursive search but with iOS I can't figure it out. For example, in the "pic" directory that I specified in the latter case what if there was other directories called pic1,pic2,pic3 etc and each directory had an arbitrary number of png's. What I am having a hard time wrapping my head around is how can I recursively search the "pic" directory to extract all directories contents that match .png. I would of coarse then store this is NSData and convert to a string and then store the contents in an array. I'm very much confused at this point. Any help would be greatly appreciated.
Your difficulty here will be getting a directory listing from a web server - since you are retrieving the images over the web. Many web servers will not provide a directory listing as a security measure against hacking attempts.
if you control the web server, you can reconfigure it to provide a directory listing - then make an NSData object from the directory level URL (http://www.myUrl.com/pic/) - and parse this to pull out all the files within the directory. You can then request each individual picture and add it to an array yourself.
if you don't control the web server, but can write to the directory, you could achieve a similar implementation by creating a manifest file (XML-plist would be a good format) which lists the filenames contained in the directory. You'd need to update the manifest any time you added a new picture to the directory though.
otherwise - you have to do some pretty dirty/hacky things, like assuming there are no more than n pictures, all pictures have the filename "pic_m.png" and then iterating through a loop from 0 to n attempting to retrieve each file until you hit a failure.
UIImage needs an explicit thing (URL or filepath) to get its data from.
One potential solution is to implement searching for your .png files via NSFileManager's enumeratorAtPath: method. The reference guide I've linked to even has a code fragment you can use to to create UIImages (just switch the #"doc" to #png and instead of doing scanDocument, do your UIImage * image = ... thing).
I'll leave it as an exercise to figure out how to do it recursively, but I hope my answer helps you out!
NSURL *url = [NSURL
URLWithString:#”www.ArticleDean.com\images\sample.jpg”];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] intiWithData:data];
try this one
In my application I need to capture image as well as save it on local library and on server using FTP. Now I need to follow a format for image name while saving it on the server. I am able to capture and save the image on local library. But I am unable to find any method to change the name of the Image. Suppose I need to rename it as Productname-UserId.png Is there any way? Kindly help.
Thank you.
UIImagePNGRepresentation(UIImage*) is likely what you're looking for. You can save a UIImage as a PNG file in the Application Documents folder, then upload that to a server. The code to do this is quite trivial, so if you could post the code you're trying to use, that would be helpful to understanding, and recommending a solution for you.
Here's a short clipping from my code that does exactly this:
UIImage * image; // Some image you want to send
NSString * docDirWithSlash = [[self applicationDocumentsDirectory] stringByAppendingString:#"/"];
NSString * pngFile = [docDirWithSlash stringByAppendingString:file]; // <-- Change the string "file" to reflect the name you want.
[UIImagePNGRepresentation(image) writeToFile:pngFile atomically:YES];
// Send pngFile to the server here
Where applicationDocumentsDirectory looks like this:
- (NSString *) applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
return documentsDirectoryPath;
}
The real question here is where do you want the file name to be specific? On the device or on the server. In case of device use Charles answer, otherwise you should look at http protocol(file upload part). Actually it doesn't matter what name the file have on you local device, as when you send it to the server over http, you can provide any name. The tricky part here is that the server you are uploading to should take that parameter into account when saving that file. So if you are uploading to a server that you don't have hand on and it does some self naming convention - you probably stuck. If it's yours - look at how you're saving files on the server side and if you are taking in account that "filename" parameter...P.S. And don't forget to pass that argument to the upload request :)
My app parses an xml file from my server, but I want to store parsed xml file and next start of my app, controller initially should load stored xml file, then controller should parse it again to check that there may be an update I did on xml file, if there is, new elements parsed should also be stored again.
I am referring to those app such as magazines, newspaper apps. When you open those kind of apps, it loads stored data that was downloaded previous session. Yet, after it loads, it starts to update the data, and it stores new update again.
Where do I start? What do you guys suggest?
Thanks in advance...
You can use CoreData or SQLite (use Objective-C wrapper FMDB https://github.com/ccgus/fmdb) to persist your XML. Then update the database everytime you see a unique id. Depends on how your XML data is.
It's actually quite easy to store to the documents directory. For example:
NSData *data; //this is your xml file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docs = [paths objectAtIndex:0];
NSString *filename = [NSString stringWithFormat:#"test.xml"];
NSString *path = [docs stringByAppendingPathComponent:filename];
[data writeToFile:path atomically:YES];
Then to retrieve it later, you can get the path like above, but retrieve the file instead of writing it:
NSData *data = [NSData dataWithContentsOfFile:path];
either CoreData or SQLite can do the trick
Hi all i am trying to save image in the bundle which i have currently on my view,but the problem is that i can only save one image,if i want to save the another image it replaces the old.I am not getting how to save the multiple images in the bundle then.
Here is my code.
- (void)writeImageToDocuments:(UIImage*)image
{
NSData *png = UIImagePNGRepresentation(image);
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSError *error = nil;
[png writeToFile:[documentsDirectory stringByAppendingPathComponent:#"image.png"] options:NSAtomicWrite error:&error];
}
Please Help me out, how to save multiple images, files e.t.c in bundle
Thanks in advance
You're not saving into a bundle, you're saving into your app's documents directory. There's no bundle aspect to it.
You're using the filename #"image.png" for every file that you save. Hence each new write overwrites the old one. In fact, you write each file twice. To save multiple files, use different file names.
It's also bad form to pass a numeric constant as the 'options:' parameter of NSData writeToFile:options:error: (or indeed, any similar case). The value '3' includes an undefined flag, so you should expect undefined behaviour and Apple can legitimately decline to approve your application. Probably you want to keep the NSAtomicWrite line and kill the one after it.
If you're just looking to find the first unused image.png filename, the simplest solution would be something like:
int imageNumber = 0;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *pathToFile;
do
{
// increment the image we're considering
imageNumber++;
// get the new path to the file
pathToFile = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithFormat:
#"image%d.png", imageNumber]];
}
while([fileManager fileExistsAtPath:pathToFile]);
/* so, we loop for as long as we keep coming up with names that already exist */
[png writeToFile:pathToFile options:NSAtomicWrite error:&error];
There's one potential downside to that; all the filenames you try are in the autorelease pool. So they'll remain in memory at least until this particular method exits. If you end up trying thousands of them, that could become a problem — but it's not directly relevant to the answer.
Assuming you always add new files but never remove files then this problem is something you could better solve with a binary search.
File names searched will be image1.png, image2.png, etc.