I'm loading an image to a custom cell and I build up an array throughout my code from XML which I want to set to my UIImageView on my nib file.
My array gets built up to totalArray and then I do the following in my cellForRowAtIndexPathT:
newObj1=[totalArray objectAtIndex:indexPath.row];
aCell.lblRouteText.text = newObj1.routeText;
And the following to load my image works (static):
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://imageurl.jpg"]]];
aCell.imgRoute.image = image;
However, when I try to put my array in using the following:
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[newObj1.routeImage]]];
I get an error of identifier expected
Any tips?
Tom
You're writing [newObj1.routeImage] where you just need newObj1.routeImage, i.e. your final line should probably read:
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:newObj1.routeImage]];
When you use square brackets (and not in the context of a C array), you should always have something of the form [x y], which means 'send the message y to the receiver x'.
Note on threading: be careful about using [NSData dataWithContentsOfURL:], because it blocks while the URL contents are fetched. If you call this on the main thread, you will block your UI and the app will be unresponsive.
(Btw, if you're posting error messages, it's best to copy and paste the full actual error line(s) from the console if possible -- it maximises our chance of understanding the problem.)
Related
I've written a custom view controller class that displays a map with annotations. When an annotation is pressed a callout is displayed and a thumbnail image is shown in the left part of the annotation callout. This class asks a delegate to provide the image that is displayed.
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
[(UIImageView *)view.leftCalloutAccessoryView setImage:[self.delegate mapViewController:self imageForAnnotation:view.annotation]];
}
The delegate class retrieves the image from the network. To protect the UI from being unresponsive a new thread is created to download the image using GCD.
- (UIImage *)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation
{
NSURL *someURL = [[NSURL alloc] initWithString:#"a URL to data on a network"];
__block UIImage *image = [[UIImage alloc] init];
dispatch_queue_t downloader = dispatch_queue_create("image downloader", NULL);
dispatch_async(downloader, ^{
NSData *imageData = [NSData dataWithContentsOfURL:someURL]; // This call can block the main UI!
image = [UIImage imageWithData:imageData];
});
return image;
}
Why does the image never display? I was assuming that since the delegate method returns a pointer to an image that at a time in the future is set to valid image data that the thumbnail would eventually update itself. This apparently is not the case...
You're creating a block that's executing asynchronously. This means your code created the block to execute, then immediately returned 'image', which was pointing to the new image you created when you initialized the variable: __block UIImage *image = [[UIImage alloc] init];
Remember that when you return an object from a method, you're actually just returning a pointer to that object.
Sometime after the pointer to this new image was returned. The block runs and assigns the pointer to the image it retrieved to the local variable 'image', which is now out of scope of the method (though the block still has it). So now the block has this reference to the image it got but that reference will go away when the block finishes.
One way to fix this would be to run the block synchronously, but that would be defeating the point of dispatching the image retrieval process. What you need to do is provide a block to the function it can call once the image is retrieved, namely assigning the image where it needs to be. This would look something like this:
- (void)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation withImageBlock:(void (^)(UIImage *))block{
{
NSURL *someURL = [[NSURL alloc] initWithString:#"a URL to data on a network"];
__block UIImage *image = [[UIImage alloc] init];
dispatch__object_t currentContext = dispatch_get_current_queue();
dispatch_queue_t downloader = dispatch_queue_create("image downloader", NULL);
dispatch_async(downloader, ^{
NSData *imageData = [NSData dataWithContentsOfURL:someURL]; // This call can block the main UI!
image = [UIImage imageWithData:imageData];
dispatch_async(currentContext, ^{
block(image);
});
});
}
Please note that I grab the current queue context so that I can execute the block given to me on the same thread it was given to me on. This is really important since the block passed to me could contain UIKit methods, which can only be performed on the main thread.
Unfortunately, what you're trying to do here is not easy at all. You'd need to create a "future" (http://en.wikipedia.org/wiki/Futures_and_promises) and return that, which ObjC/Cocoa do not have a built in implementation of.
The best you can likely do here is either a) have the caller pass in a block that runs on completion of the download and updates the UI, or b) return a placeholder image and schedule a block to replace the image after it finishes downloading. Both of those will require restructuring your code somewhat. The latter also requires your downloading code to know how to update your UI, which is a bit unfortunate in terms of increasing coupling.
I'm trying to use a url as a UIImage in the OpenFlow API.
NSString *imageUrl = [[[newsEntries objectAtIndex:0] objectForKey: #"still"] retain];
NSURL *url2 = [NSURL URLWithString:imageUrl];
NSData *photoData = [NSData dataWithContentsOfURL:url2];
UIImage *imageUI = [UIImage imageWithData:photoData]
UIImageView *myImage = [UIImageView initWithFrame:imageUI];
[(AFOpenFlowView *)self.view setImage:[UIImage imageNamed:myImage]];
[imageUr release];
[(AFOpenFlowView *)self.view setNumberOfImages:3];
I have tried it like this, but no success. The only way I got this API working was using the imageNamed type. The initwithData has no success.
So how can I change this NSString to finally become a imageNamed method?
A UIImageView is different from a UIImage.
Change this line: [(AFOpenFlowView *)self.view setImage:[UIImage imageNamed:myImage]];
To this: [(AFOpenFlowView *)self.view setImage:[UIImage imageNamed:imageUI]];
and it should work.
You have several significant errors here. It appears that you may need to read about C/Objective-C and types.
It sounds like you are asserting that, specifically, the line
UIImage *imageUI = [UIImage imageWithData:photoData]
is not working. The code up to that point actually looks okay (though it is not necessary to retain and then release the imageUrl variable.
Once you have created your UIImage, you should be able to pass it directly to your AFOpenFlowView:
[(AFOpenFlowView*)self.view setImage:imageUI forIndex:0];
The line
UIImageView *myImage = [UIImageView initWithFrame:imageUI];
has two errors (and is unnecessary anyway). First, -initWithFrame takes a CGRect as its argument, not a UIImage. UIImageView does have an initialization method -initWithImage, which is probably what you intended. But either way, methods that start with "init" are instance methods, not class methods, so you have to call them on actual instances of a UIImageView, like this:
UIImageView *myImage = [[UIImageView alloc] initWithImage:imageUI];
Note that this will leak memory unless you autorelease or release it.
The following line, you correctly try to give a UIImage to your AFOpenFlowView, but you attempt to create that UIImage by passing a UIImageView to the +imageNamed method. +imageNamed takes an NSString that contains the name of the image, and passing anything else to it won't work.
You must always be aware of what kind of object a method is expecting to receive, and make sure you find some way to give it that kind of thing.
Probably what you are looking for here is something like this:
NSString *imageUrl = [[newsEntries objectAtIndex:0] objectForKey: #"still"];
NSString *imageName = [imageUrl lastPathComponent];
[(AFOpenFlowView *)self.view setImage:[UIImage imageNamed:imageName]];
I'm implementing an image browser, using a UIScrollView. Due to memory costranints, I've to implement image dynamic loading (I don't want use CATiled Layers because it forces user remaining waiting to load every tile).
I've tried in a coupe of ways:
- (UIImageView*) ldPageView{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
NSError *error;
NSData *imData = [NSData dataWithContentsOfURL:ldPageRef options:NSDataReadingUncached error:&error];
UIImage *im = [[UIImage alloc] initWithData:imData];
ldView = [[UIImageView alloc] initWithImage:im] ;
[ldView setFrame:pageRect];
[pool release]; // Release the objects in the pool.
return ldView;
}
And even in this way
- (UIImageView*) ldPageView{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
CGDataProviderRef provider = CGDataProviderCreateWithURL ((CFURLRef)ldPageRef);
CGImageRef d = CGImageCreateWithJPEGDataProvider(provider,nil, true,kCGRenderingIntentDefault);
UIImage *im = [[UIImage alloc] initWithCGImage:d];
ldView = [[[UIImageView alloc] initWithImage:im] autorelease];
[im release];
CGDataProviderRelease(provider);
CGImageRelease(d);
[ldView setFrame:pageRect];
[pool release]; // Release the objects in the pool.
return ldView;
}
But every time I try it both on simulator and on iPad, memory explodes. I've runned my code with Instruments and no leak is reported. ldView is an istance variable and it is deallocated togheter with ldPageRef on object dealloc (which is called for sure).
I've also tried setting NSURLCache sharedCache to nil or to zero, but it is still happening.
I've read Memory management guide, but everythimg seems ok to me.
Please help
Try using
UIImage *im = [UIImage imageWithData:imData];
rather than
UIImage *im = [[UIImage alloc] initWithData:imData];
Always avoid allocs if possible otherwise you must ensure that you manually release the object.
More than likely it is how you are creating your UIImage. Try creating your image as such..
[UIImage imageWithData:imData];
instead of
[[UIImage alloc] initWithData:imData];
This will return a autoreleased object(it is a class method) so that you will not have to try to release it yourself later.
You are never releasing your alloc'd objects. You need to change:
[[UIImage alloc] initWithData:imData];
[[[UIImageView alloc] initWithImage:im];
to:
[[[UIImage alloc] initWithData:imData] autorelease];
[[[UIImageView alloc] initWithImage:im] autorelease] ;
Indeed I have found a memory leak in UIImageView. You just never pay any attention to it, since you may open images from the App package all the time, and these images are being cached by iOS.
But if you download a number of images from the network (say 40 iPhone-camera photos), save them to the documents and open them over and over again in a sub view controller, the memory leak applies. You do not need to have 40 different images, it is enough to load one image again and again.
Your test app is with ARC disabled and an image is being loaded from file and displayed in a sub view controller, every time that controller is being pushed.
In your sub view controller you'll create an UIImageView and assign the UIImage object to the image view's .image property. When you leave the sub view controller, you release the image view correctly. On an iPhone 4s your app won't open more than 80 images. Mine crashed at around 70 images (with iOS 6.1). And have a look on the intruments app, before the crash. The memory is full of CFMalloc blocks.
The solution I found is simple: Set the image property to nil, before you release the image view. Again, look at the instruments app. It now looks like what you'd expect and your app does no longer crash.
I think the same leak applies to whatever the UIWebView uses to display images and Apple doesn't know about it.
I have saved my images in database with type BLOB. Now I am extracting these images from database using sql stmt and saving it in a NSData object. see the following code snippet
NSData *img = [[NSData alloc] initWithBytes:sqlite3_column_blob(loadInfostmt, 4) length: sqlite3_column_bytes(loadInfostmt, 4)];
self.birdImage = [UIImage imageWithData:img];
After getting image I tried setting it at image attribute of UIImageView to load it to image view. I used following techniques but nothing seems to work.
1.
self.imgPikr = [[UIImageView alloc] initWithImage:brd.birdImage];
[self.imgPikr setImage:brd.birdImage];
2.
[self.imgPikr = [[UIImageView alloc] init];
self.imgpikr.image = brd.birdImage;
3.
[self.imgPikr = [[UIImageView alloc] init];
[self.imgpikr setImage:brd.birdImage];
Here imgPikr is a UIImageView object and birdImage is object of UIImage class.
every other data is displayed in view correctly except the image.
I dont have any error or warning or runtime exception but still cant load image.
Please tell me the solution. I have been working on it since a week. but couldnt find anything working.
Any answer or experimentation is welcomed.
thanx in advance
Option 1 is the best option, since -[UIImageView initWithImage:] will automatically resize the imageView to be the same size as the passed image (and it is also unnecessary to setImage: if you use this initializer).
However, as #Robot K points out in the comments, you still need to add self.imgPikr as a subview of a view that is visible on the screen.
Additionally, if your imgPikr property is declared as (retain), then you have a memory leak.
I have done research and tried several times to release the UIImage memory and have been unsuccessful. I saw one other post on the internet where someone else was having this same issue. Everytime imageScaledToSize is called, the ObjectAlloc continues to climb.
In the following code I am pulling a local image from my resource directory and resizing it with some blur. Can someone provide some help on how to release the memory of the UIImages called....scaledImage and labelImage. This is the chunk of code where the iPhone Intruments has shown to have the ObjectAlloc build up. This chunk of code is called several times with an NSTimer.
//Get local image from inside resource
NSString * fileLocation = [[NSBundle mainBundle] pathForResource:imgMain ofType:#"jpg"];
NSData * imageData = [NSData dataWithContentsOfFile:fileLocation];
UIImage * blurMe = [UIImage imageWithData:imageData];
//Resize and blur image
UIImage * scaledImage = [blurMe _imageScaledToSize:CGSizeMake(blurMe.size.width / dblBlurLevel, blurMe.size.width / dblBlurLevel) interpolationQuality:3.0];
UIImage * labelImage = [scaledImage _imageScaledToSize:blurMe.size interpolationQuality:3.0];
imgView.image = labelImage;
You can wrap the calls in an NSAutoreleasePool, where their results will be pooled. Then you can call [pool drain] on that pool and its contents will be released, including the images.
Note however that you will not be able to use the images outside of the NSAutoreleasePool's scope, so you code might want to look something like:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
UIImage * scaledImage = [blurMe _imageScaledToSize:CGSizeMake(blurMe.size.width / dblBlurLevel, blurMe.size.width / dblBlurLevel) interpolationQuality:3.0];
UIImage * labelImage = [scaledImage _imageScaledToSize:blurMe.size interpolationQuality:3.0];
UIImage * imageCopy = [[UIImage alloc] initWithCGImage:labelImage.CGImage]; // Gives a non-autoreleased copy of labelImage
[pool drain]; // deallocates scaledImage and labelImage
imgView.image = imageCopy; // retains imageCopy
UPDATE:
If the above is still giving you problems, please see the solution I posted to this question. The question involves rotating an image 90 degrees instead of scaling it, but the premise is the same (it's just the matrix transformation that is different). Using code like in the answer I posted should give you greater control over your memory management and steer you away from using undocumented APIs like _imageScaledToSize.