Im attempting to save some images to a camera roll, all the images are in a array.
if (buttonIndex == 1) {
int done = 0;
NSMutableArray *copyOfImages = [[NSMutableArray alloc]initWithArray:saveImagesToArray];
while (!done == 1){
if (copyOfImages.count == 0) {
done = 1;
}
if (copyOfImages.count > 0){
[copyOfImages removeLastObject];
}
UIImage *image = [[UIImage alloc]init];
image = [copyOfImages lastObject];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
}
because i dont know how many images there can be i use a while loop
Im testing this with 8 images, the array shows a count of 8 so thats good.
When i try saving the images this comes up in the console.
-[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
Out of the 8 images im trying, only 5 show up in the camera roll.
Any help would be great.
Thanks :)
Why are you calling [copyOfImages removeLastObject]?
Every time you go through that look are you destroying the last object, which is strange because you haven't added it to the roll yet. Take out that line and see if you look works.
Also, rather than using a for loop, use the following pattern:
for (id object in array) {
// do something with object
}
This will enumerate though the objects in the array, stopping when it reaches the end. Just be sure not to modify the array while you are doing this.
I had this same issue and I resolved it by ensuring that the images are saved sequentially. I think there may be some kind of race condition going on.
Basically, I did:
UIImageWriteToSavedPhotosAlbum([self.images objectAtIndex:self.currentIndex], self,
#selector(image:didFinishSavingWithError:contextInfo:), nil);
Then in image:didFinishSavingWithError:contextInfo:, I increment currentIndex and try the next image, if there are any left.
This has worked for me in every case so far.
I had the same exact problem. No matter how much pictures i added to the share activityController, maximum of 5 were saved.
i have found that the problem was to send the real UIImage and not URL:
NSMutableArray *activityItems = [[NSMutableArray alloc] init];
for(UIImage *image in imagesArray) {
[activityItems addObject:image];
}
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
To expand on Bill's answer, UIImageWriteToSavedPhotosAlbum seems to do its writing on some unknown thread asynchronously - but also there is some hidden limit to the number of images it can write at once. You can even tease out write busy-type errors if you dig in deep.
Tons more info here:
Objective-C: UIImageWriteToSavedPhotosAlbum() + asynchronous = problems
I also agree with Bill that serializing your writes is the only reasonable/reliable answer
I have seen.
Related
I used multi column tableView !For one screen I have 16 cells,and If I have 3000 data ,and I got the thumbnail image from database( I use the open source -FMDB),and when load this table,I open only one thread to download the thumbnail, and save to database.
When the cell is visible,I got thumbnail from db,if it had been downloaded.
But I found that I got data from database is very slow,about 0.15~1.3 seconds for one cell, and I have 16 cell for one screen. So it block my main thread?
Any suggestion to help me solve the problem?
Thanks in advance.
This is the code I get thumbnail~
-(NSData *)queryVideoSmallThumbnailData:(NSString *)theSourceUrl
{
if(theSourceUrl == nil)
return nil;
NSString *query = #"SELECT SMALL_THUMBNAIL_DATA FROM FIELDS WHERE SOURCE_URL";
query = [query stringByAppendingString:#"=\""];
query = [query stringByAppendingString:theSourceUrl];
query = [query stringByAppendingString:#"\""];
NSData * data = nil;
FMResultSet *rs = [database executeQuery:query];
while ([rs next])
{
data = [rs dataForColumn:#"SMALL_THUMBNAIL_DATA"];
break;
}
[rs close];
return data;
}
This is the table view refresh image code.
image = [thumbCache getThumbFromCache:record.sourceUrl]
if (image == nil)
{
DBVideoThumb *thumbDB = [[DBManager getInstance] getDBVideoThumb];
NSData *data = [thumbDB queryVideoSmallThumbnailData:record.sourceUrl];
image = [UIImage imageWithData:data];
if (image != nil)
[thumbCache addThumbToCache:record.sourceUrl image:image];
}
The size of your database may affect your query speed. So, when your table View dragging,you may change your thumbnail to small one. a blur one, as small as possible.
Create a Index for your database as can improve your query speed from n to log2 n.
For example :Create index urlIndex on Fields(url);
If you're using this FMDB I suggest you (re)read the instructions. The author points out that using a single instance of FMBD on multiple threads is a bad idea. This is a common problem for anything that has to maintain data integrity.
Assuming you are using FMDB, have you tried creating multiple instances of FMDB on the same store, one for each thread? You can apply this to other answers posted here, inside an asynchronous block. Remember to close each instance when you're done and check that there is a lock on the database to ensure atomic writes.
If you're open to other solutions, Core Data might be of use if you're modelling an object graph you'd like to persist.
I faced same problem during my app development but I used following code to solve this issue. May be its help you. If you get any trouble let me know.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSData *imageData ;
imageData = [UICommonUtils imageDataFromString:profile.Photo];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
if([imageData length] > 1) {
cell.ProfileImage.image = [UIImage imageWithData:imageData];
} else {
cell.ProfileImage.image = [UIImage imageNamed:kDefaultProfileImage];
}
});
imageData = nil;
});
I think [UIImage imageWithData:filename] may be a cause. its performance, as I believe, is far lower than imageNamed:. or imagewithContentOfFile:. My solution is saving it as file in another thread, then show it in main thread after saving done. if the video is not changed, next time will directly show file without query database.
So I am making an app really quick, and even though it isn't the best solution, it appears to be working for the most part. So I have a simple image view that pulls an image out of an NSMutableArray arr. I create the array and populate it inside of ViewDidLoad in this manner:
arr = [[NSMutableArray alloc] init];
[arr addObject:[UIImage imageNamed:#"181940jpg"]];
[arr addObject:[UIImage imageNamed:#"168026.jpg"]];
[arr addObject:[UIImage imageNamed:#"168396.jpg"]];
[arr addObject:[UIImage imageNamed:#"168493_.jpg"]];
ANd I continue doing that for 130 images. Obviously this is a big array. I don't know if this is like a really bad way to do it, but if it is, I am open to suggestions! As I go through the array with some simple back and forward buttons pulling images out of the array based on a simple counter variable things work ok until image 45-ish. The app breaks down and the console says this:
* ERROR: ImageIO 'ImageProviderCopyImageBlockSetCallback' header is not a CFDictionary...
Would it help if I broke my images up into separate arrays? Am I just putting too much into the array? What am I missing here, trust me, I am all ears.
Thanks
EDIT: Here is some more information
This is how I am sifting through the array, using a UISegmentedControl set on the top of the screen:
-(void) pickedOne{
if(segmentedControl.selectedSegmentIndex == 1){
NSLog(#"hey");
if(position < [arr count]-1){
position++;//This is my global counter variable
UIImage * img = [arr objectAtIndex:position];
[imageView setImage:img];
[img release];
}
}else if(segmentedControl.selectedSegmentIndex ==0){
if(position >0){
position--;
UIImage * img = [arr objectAtIndex:position];
[imageView setImage:img];
[img release];
}
}
}
It doesn't appear to be a problem with memory management, but then again, I don't consider myself a pro at that by any means...
In terms of the mutable array, what you put into it is just a pointer to some other object. It is those other objects you have to worry about and, yes, you have too many of them (more likely than not).
ERROR: ImageIO 'ImageProviderCopyImageBlockSetCallback'
header is not a CFDictionary...
Would it help if I broke my images up
into separate arrays? Am I just
putting too much into the array? What
am I missing here, trust me, I am all
ears.
That sounds more like you have an over-release problem and are passing something bogus to the ImageIO APIs.
And, in fact, that is exactly what you have:
UIImage * img = [arr objectAtIndex:position];
[imageView setImage:img];
[img release];
That release is spurious; it does not balance a retain anywhere in your code. objectAtIndex: does not return a retained object and the UIView will take care of retaining/releasing the image internally.
Remove that release (and the other one, too)!
You still need to worry about memory consumption. At a size of 42K each (not an unreasonable size, but entirely made up), 130 images will weigh in at ~6MB or so, prior to any decompression or other expansion that occurs as a part of storage.
The devices are quite memory constrained.
I've read a lot of UIScrollView with UIImageView threads here or other googled pages. But I still cannot get the problem I'm confronting. I'm having a cold right now. Hope I can still make it clear, lol. Here is the problem:
I'm building one app which mainly uses UIScrollView to display a few images. Here the amount counts, not the size, which is averagely 100KB(I even converted PNG to jpg, not sure whether it helps or not). With no more than 10 images, my app crashes with memory warning. This is the first time I encounter memory issue, which surprised me as the compiled app is less than 10MB.
At the very beginning, I load all the images on launch, looping all names of image files and do
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imgName]];
[scrollview addSubview:imageView];
[imageView release];
If I'm right, I think after launch, all the images are in memory, right? But the funny thing here is, the app could launch without any problem(at most a level 1 memory warning). After I scroll a few images, it crashed. I checked leaks for sure and also allocations. No leak and allocation almost had no change during scrolling.
So, is there something special done by imageNamed rather than cache?
And then, yes, I turned to lazy load.
For fear of checking page and loading images on demand might jerk the scrolling(which was proved true), I used a thread which runs a loop to check offset of the scroll view and load/unload images.
I subclassed UIImageView by remembering the image name. It also contains loadImage and unloadImage which will be executed on that thread.
- (void)loadImage {
/if ([self.subviews count] == 0) {
UIImageView iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:self.imageName]];
[self performSelectorOnMainThread:#selector(renderImage:) withObject:iv waitUntilDone:NO];
//[self addSubview:iv];
[iv release];
}*/
if (self.image == nil) {
//UIImage *img = [UIImage imageNamed:self.imageName];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[self.imageName stringByDeletingPathExtension] ofType:[self.imageName pathExtension]]];
// image must be set on main thread as UI rendering is main thread's responsibility
[self performSelectorOnMainThread:#selector(renderImage:) withObject:img waitUntilDone:NO];
[img release];
}
}
// render image on main thread
- (void)renderImage:(UIImage*)iv {
//[self addSubview:iv];
self.image = iv;
}
(void)unloadImage {
self.image = nil;
//[(UIView*)[self.subviews lastObject] removeFromSuperview];
}
You can see the commented code that I've played with.
In unloadImage, if I write [self.image release], then I get EXC_BAD_ACCESS, which is unexpected, as I think alloc and release are matched here.
The app still crashes with no leak. The initWithContentsOfFile version even crashed earlier than imageNamed version, and made the scrolling not that smooth.
I run the app on device. By checking allocations, I found imageNamed version used much less memory than initWithContentsOfFile version, though they both crash. Instruments also showed that the allocated images were 2,3 or 4, which indicated the lazy load did do his job.
I checked PhotoScroller of WWDC2010, but I don't think it solvs my problem. There is no zooming or huge picture involved.
Anybody helps! Thank you in advance.
The crash log says nothing. The app crashes mostly after memory warning level = 2. And if run on simulator, there will be no problem.
It doesn't matter which format do you use for your images. They're converted to bitmaps when you display them.
I'd suggest to use the technique similar to that one which is used by UITableView (hide the image and free the memory it uses when it disappears from the screen and instantiate the image only when you need to show it).
As an alternate way – if you need to show these images in a grid – you might take a look to a CATiledLayer.
Anyhow, loading all the images to the memory is not the best idea :)
You can load all the images to an array. And you can design a view having one image view and try the below code:
array name: examplearray and view name :exampleview
-(void)updateImagesInScrollView
{
int cnt = [examplearray count];
for(int j=0; j< cnt; ++j)
{
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"exampleview"
owner:self
options:nil];
UIView *myView = [nibContents objectAtIndex:0];
exampleview * rview= (exampleview *)myView;
/* you can get your iamge from the array and set the image*/
rview.imageview.image = yourimage;
/*adding the view to your scrollview*/
[self.ScrollView addSubview:rview];
}
/* you need to set the content size of the scroll view */
self.ScrollView.contentSize = CGSizeMake(X, self.mHorizontalScrollView.contentSize.height);
}
I am developing an game in which i have to load images on finger swipe. For this i loaded all image 22 of size (788x525) at loading of the view itself. Then on finger swipe i just added image on view.
This works fine on iTouch but on iPhone game crashes after showing near about 12 images.
This is how i added images, graphicsArray is NSMUTableArray.
for ( int i=0; i<=totalNoofPages;i++){
NSString * graphicsFileName = #"Page";
graphicsFileName =
[graphicsFileName
stringByAppendingString:[NSString stringWithFormat:#"%d", i]];
UIImage *image =
[[UIImage alloc] initWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:graphicsFileName
ofType:#"jpg"]];
GraphicsImages =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 480,320 )];
GraphicsImages.image = image;
[image release];
[GraphicsImages setCenter:center];
GraphicsImages.transform = CGAffineTransformMakeRotation(M_PI/2);
[graphicsArray insertObject:GraphicsImages atIndex:i];
}
and on finger swipe i write,pageNo is number of page that will be displayed.
GraphicsImages = [graphicsArray objectAtIndex:pageNo];
[self addSubview:GraphicsImages];
Is there any way to release the previous loaded image from sub view as it might be kept on stack or is it due to large image size????
Please help me in this situation.
Thanks in advance.
Regards,
Vishal.
Are you crashing due to lack of memory?
You might try pre-loading the previous, current & next images only, and release them as soon as possible. Loading 22 images in memory takes up quite a lot of space.
Can you provide the error that you're getting when the app crashes? Knowing what exception is being triggered will help a lot in figuring out exactly what's going on.
I can tell you that you aren't releasing the GraphicsImages variable in your for loop, so that's causing memory leaks. I imagine that that's not enough to cause a crash, however. In any case, do this:
[ graphicsArray insertObject:GraphicsImages ];
[ GraphicsImages release ];
Note that you don't need to specify the insertion index in your case because the items will be added sequentially. Also, when you insert an object into an array, it is retained, so you can safely release it afterwards.
I have a sequence of images needed to display in a short time (PNG sequence). There are totally 31 PNGs in total, each with file size about 45KB. I have already loaded them with the following codes:
imgArray = [[NSMutableArray alloc] init];
for(int i = 0; i <= 30; i++) {
NSString * filename = [NSString stringWithFormat:#"img_000%.2d.png", i];
UIImage *temp = [UIImage imageNamed:filename];
[imgArray addObject:temp];
[temp release];
temp = nil;
}
I use the following codes for displaying the images:
CGImageRef image = [(UIImage *)[imgArray objectAtIndex:imgFrame] CGImage];
imgLayer.contents = (id)image;
if(imgFrame < 29) {
imgFrame++;
} else {
imgFrame = 0;
imgLayer.hidden = TRUE;
[imgTimer invalidate];
}
where imgLayer is a CALayer. (imgTimer is a repeating timer with interval 0.03s)
But I found that when I call the images out, it is very laggy at the first time. Except the 1st appearance, other appearance has no problem.
Is it related to preloading images? Or are my images too big in file size?
The reason for your lags are hard to tell without profiling data. But here is a trick that might help: Join all your images into one large file. Try to make it rectangular (maybe 6x6 in your case or 4*8). Then load this single file and crop each image out for display (i.e. create an image for display with the size of a tile and copy one tile from the big image after the other into the display image).
Images are loaded when they are used and displayed.
If you use the time profiler in Instruments, you will the lag you're experiencing. If you then zoom to that lag and look at what is causing it, you will usually see that "copyImageBlockSetPNG" is the function taking time, right before "inflate".
What you must find a way to do is create your images and force load them before you need them. That's another story apparently.