I have the following code to get the UIImage from MPMediaItemPropertyArtWork to display the coverArt in my view. I get a non-NULL artwork from the mediaItem, however, when I try to extract the UIImage from the artwork, it returns NULL and I don't see anything in my UIImageView. Any help will be greatly appreciated.
CGSize artworkSize = CGSizeMake(30, 30);
UIImage *artworkImage;
MPMediaItemArtwork *artwork = [song valueForProperty: MPMediaItemPropertyArtwork];
if (artwork) {
NSLog(#"artwork available");
artworkImage = [artwork imageWithSize:artworkSize];
} else {
NSLog(#"artwork not available");
artworkImage = [UIImage imageNamed:#"EmptyAlbum.png"];
}
NSLog(#"artworkImage = %#", artworkImage);
coverArtView.image = artworkImage;
Try with this - have use a UItableview to display album details.
declare MPMediaItemCollection *userMediaCollection;
-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection {
[self setUserMediaCollection:mediaItemCollection];
[self dismissModalViewControllerAnimated:YES];
}
MPMediaItem *mItem = (MPMediaItem *)[userMediaCollection.items objectAtIndex:indexPath.row];
MPMediaItemArtwork *artWork = [mItem valueForProperty:MPMediaItemPropertyArtwork];
cell.imageView.image = [artWork imageWithSize:CGSizeMake(30, 30)];
where userMediaCollection is your MPMediaItemCollection
try this... it is work.
CGSize artworkSize = CGSizeMake(30, 30);
UIImage *artworkImage;
MPMediaItemArtwork *artwork = [song valueForProperty: MPMediaItemPropertyArtwork];
artworkImage = [artwork imageWithSize:artworkSize];
if (artworkImage == nil) {
NSLog(#"artwork not available");
artworkImage = [UIImage imageNamed:#"EmptyAlbum.png"];
}
coverArtView.image = artworkImage;
I know this question is a bit old by now, but for anyone finding this, this might help:
I was scratching my head over this for a few hours, because getting the artwork was working in one view controller in my app but not in my UITableViewController. My code looked exactly the same in both instances (and very similar to yours) except for ONE thing:
let albumArtImage = albumArt.imageWithSize(cell.imageView.bounds.size)
the SIZE! (Sorry for writing this in Swift, but you get the idea). It sounds stupid, but the MPMediaItemArtwork refused to give me a small image. I literally just changed it to a bigger size, like so:
let albumArtImage = albumArt.imageWithSize(CGSize(width: 150, height: 150))
and it worked. You can resize the UIImage to fit your needs later anyway.
If anyone finds this and this (stupid) solution works, please let me know via a reply or something.
How do you initialize you MPMusicPlayerController? It should be:
MPMusicPlayerController *player = [MPMusicPlayerController iPodMusicPlayer];
See answer: https://stackoverflow.com/a/26463261/883413
Appears to be an iOS bug. If the image returned is nil, try using the artwork size instead.
UIImage *image = [artwork imageWithSize:size];
if (image == nil) {
image = [artwork imageWithSize:artwork.bounds.size];
}
Related
I'm kinda puzzeled about image storage in iOS devices for an app i'm making.
My requirement is to load an Image onto a tableViewCell, lets say in the default Image space of a UITableViewCell and hence isnt a background image.
Now The user can either add an Image either via the PhotoDirectory or take an entirely new image.
If a new image is taken, where should that image be stored preferebly? In the default photo directory ? or in the documents folder of the app sandbox?
Because these are image files, I'm afraid that store images within the app bundle can make it pretty big, I'm afraid I dont wana cross the size limit.
Performance wise though... what would be a better option?
I have an app that also does some of the things you describe. My solutions was to create a singleton that I call my imageStore. You can find information about a singleton here
In this imageStore, I store all my "full size" images; however, like you I am concerned about the size of these images, so instead of using them directly, I use thumbnails. What I do is this. For each object that I want to represent in the table, I make sure the object has a UIImage defined that is about thumnail size (64x64 or any size you desire). Then an object is created, I create a thumbnail that I store along with the object. I use this thumbnail instead of the larger images where I can get a way with it, like on a table cell.
I'm not behind my Mac at the moment, but if you want I can post some code later to demonstrate both the singleton and the creation and usage of the thumbnail.
Here is my header file for the ImageStore
#import <Foundation/Foundation.h>
#interface BPImageStore : NSObject {
NSMutableDictionary *dictionary;
}
+ (BPImageStore *)defaultImageStore;
- (void)setImage:(UIImage *)i forKey:(NSString *)s;
- (UIImage *)imageForKey:(NSString *)s;
- (void)deleteImageForKey:(NSString *)s;
#end
Here is the ImageStore.m file - my Singleton
#import "BPImageStore.h"
static BPImageStore *defaultImageStore = nil;
#implementation BPImageStore
+ (id)allocWithZone:(NSZone *)zone {
return [[self defaultImageStore] retain];
}
+ (BPImageStore *)defaultImageStore {
if(!defaultImageStore) {
defaultImageStore = [[super allocWithZone:NULL] init];
}
return defaultImageStore;
}
- (id)init
{
if(defaultImageStore) {
return defaultImageStore;
}
self = [super init];
if (self) {
dictionary = [[NSMutableDictionary alloc] init];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(clearCach:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void) clearCache:(NSNotification *)note {
[dictionary removeAllObjects];
}
- (oneway void) release {
// no op
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax;
}
- (void)setImage:(UIImage *)i forKey:(NSString *)s {
[dictionary setObject:i forKey:s];
// Create full path for image
NSString *imagePath = pathInDocumentDirectory(s);
// Turn image into JPEG data
NSData *d = UIImageJPEGRepresentation(i, 0.5);
// Write it to full path
[d writeToFile:imagePath atomically:YES];
}
- (UIImage *)imageForKey:(NSString *)s {
// if possible, get it from the dictionary
UIImage *result = [dictionary objectForKey:s];
if(!result) {
// Create UIImage object from file
result = [UIImage imageWithContentsOfFile:pathInDocumentDirectory(s)];
if (result)
[dictionary setObject:result forKey:s];
}
return result;
}
- (void)deleteImageForKey:(NSString *)s {
if(!s) {
return;
}
[dictionary removeObjectForKey:s];
NSString *path = pathInDocumentDirectory(s);
[[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
}
#end
Here is where I use the image store. In my Object "player", I have a UIImage to store the thumbnail and I have an NSString to house a key that I create. Each original image I put into the store has a key. I store the key with my Player. If I ever need the original image, I get by the unique key. It is also worth noting here that I don't even store the original image at full size, I cut it down a bit already. After all in my case, it is a picture of a player and nobody has too look so good as to have a full resolution picture :)
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *oldKey = [player imageKey];
// did the player already have an image?
if(oldKey) {
// delete the old image
[[BPImageStore defaultImageStore] deleteImageForKey:oldKey];
}
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
// Create a CFUUID object it knows how to create unique identifier
CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);
// Create a string from unique identifier
CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueID);
// Use that unique ID to set our player imageKey
[player setImageKey:(NSString *)newUniqueIDString];
// we used Create in the functions to make objects, we need to release them
CFRelease(newUniqueIDString);
CFRelease(newUniqueID);
//Scale the images down a bit
UIImage *smallImage = [self scaleImage:image toSize:CGSizeMake(160.0,240.0)];
// Store image in the imageStore with this key
[[BPImageStore defaultImageStore] setImage:smallImage
forKey:[player imageKey]];
// Put that image onto the screen in our image view
[playerView setImage:smallImage];
[player setThumbnailDataFromImage:smallImage];
}
Here is an example of going back to get the original image from the imageStore:
// Go get image
NSString *imageKey = [player imageKey];
if (imageKey) {
// Get image for image key from image store
UIImage *imageToDisplay = [[BPImageStore defaultImageStore] imageForKey:imageKey];
[playerView setImage:imageToDisplay];
} else {
[playerView setImage:nil];
}
Finally, here is how I create a thumbnail from the original image:
- (void)setThumbnailDataFromImage:(UIImage *)image {
CGSize origImageSize = [image size];
CGRect newRect;
newRect.origin = CGPointZero;
newRect.size = [[self class] thumbnailSize]; // just give a size you want here instead
// How do we scale the image
float ratio = MAX(newRect.size.width/origImageSize.width, newRect.size.height/origImageSize.height);
// Create a bitmap image context
UIGraphicsBeginImageContext(newRect.size);
// Round the corners
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect cornerRadius:5.0];
[path addClip];
// Into what rectangle shall I composite the image
CGRect projectRect;
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio *origImageSize.height;
projectRect.origin.x = (newRect.size.width - projectRect.size.width) / 2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) / 2.0;
// Draw the image on it
[image drawInRect:projectRect];
// Get the image from the image context, retain it as our thumbnail
UIImage *small = UIGraphicsGetImageFromCurrentImageContext();
[self setThumbnail:small];
// Get the image as a PNG data
NSData *data = UIImagePNGRepresentation(small);
[self setThumbnailData:data];
// Cleanup image context resources
UIGraphicsEndImageContext();
}
I'm using some sample code I got from a tutorial to create basically a snapshot using AVCamRecorder. It doesn't save a picture, it just displays it in a little rect under the live camera view whenever I click a button. It seemed to be allocating more and more memory each time I clicked the button to update the image, so I put an if (image) {[image release]} and then continued with the rest of the code to capture the image. The problem I ran into there is that eventually I hit an EXC_BAD_ACCESS if I click the button fast enough repeatedly. I tried inserting an if (image) immediately before assigning it to my view, but I still get EXC_BAD_ACCESS. I tried adding an [NSThread sleepForTimeInterval:1] at the end, but that didn't help either. I still get the EXC_BAD_ACCESS after clicking the button several times. Is there a proper way to reuse this view? Thanks
if (image) {
[image release];
exifAttachments = nil;
}
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:stillImageConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
exifAttachments = CMGetAttachment(imageDataSamplebuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
// NSLog
} else {
// NSLog
}
NSData *imagedata = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
image = [[UIImage alloc] initWithData:imageData];
if (image) {
self.capturedPicView.image = image;
}
}];`
Is the image variable declared as __block? If not, you may get all sorts of weird things because you can't modify it within a block.
You probably don't need a separate image variable - just do:
self.capturedPicView.image = [[[UIImage alloc] initWithData:imageData] autorelease];
in your block.
P.S. And it looks like your original memory leak was due to not releasing the new image - you could have added autorelease to UIImage creation or just release it right after assigning (UIImageView.image retains it anyway):
image = [[UIImage alloc] initWithData:imageData];
if (image) {
self.capturedPicView.image = image;
[image release];
}
I am sure I am doing something stupid here. I build a category on top of UIButton which I want it to take all of the background images assigned to it (different states) and convert them to stretchable versions and reapply them back to the button.
- (void)enableBackgroundImageStrechingWithLeftCapWidth:(float)leftCapWidth withTopCapHeight:(float)topCapHeight;
{
UIImage *backgroundimageNormal = [self backgroundImageForState:UIControlStateNormal];
if (backgroundimageNormal != nil)
{
UIImage *stretchImage = [backgroundimageNormal stretchableImageWithLeftCapWidth:leftCapWidth topCapHeight:topCapHeight];
[self setBackgroundImage:stretchImage forState:UIControlStateNormal];
}
UIImage *backgroundimageSelected = [self backgroundImageForState:UIControlStateSelected];
if (backgroundimageSelected != nil)
{
UIImage *stretchImage = [backgroundimageSelected stretchableImageWithLeftCapWidth:leftCapWidth topCapHeight:topCapHeight];
[self setBackgroundImage:stretchImage forState:UIControlStateSelected];
}
UIImage *backgroundimageHighlighted = [self backgroundImageForState:UIControlStateHighlighted];
if (backgroundimageHighlighted != nil)
{
UIImage *stretchImage = [backgroundimageHighlighted stretchableImageWithLeftCapWidth:leftCapWidth topCapHeight:topCapHeight];
[self setBackgroundImage:stretchImage forState:UIControlStateHighlighted];
}
UIImage *backgroundimageDisabled = [self backgroundImageForState:UIControlStateDisabled];
if (backgroundimageDisabled != nil)
{
UIImage *stretchImage = [backgroundimageDisabled stretchableImageWithLeftCapWidth:leftCapWidth topCapHeight:topCapHeight];
[self setBackgroundImage:stretchImage forState:UIControlStateDisabled];
}
}
Seems to work except the button is now not clickable
It seems that the highlighted state causes the issue!
I have removed the highlighted block and it works fine?
If you do not have custom images for highlighted/disabled states, but rely on UIButton to apply the highlight/disabled effect, then [self backgroundImageForState: UIControlStateHighlighted] will NOT return nil. It will instead return a pointer to the normal state image.
Using your code, you are then effectively setting identical images for all states. They look like custom images to the framework, though. This disables the built-in highlight/disabled effects.
I have a view that generates an image based on a series of layers. I have images for the background, for the thumbnail, and finally for an overlay. Together, it makes one cohesive display.
It seems to work a dream, except for when it doesn't. For seemingly no reason, I get an EXC_BAD_ACCESS on the specified line below after it's generated somewhere between 8 and 20 images. I've run it through the memory leak tool and allocation tool, and it's not eating up tons of memory and it's not leaking. I'm totally stumped.
Here's the relevant code:
- (UIImage *)addLayer:(UIImage *)layer toImage:(UIImage *)background atPoint:(CGPoint)point {
CGSize size = CGSizeMake(240, 240);
UIGraphicsBeginImageContext(size);
[background drawAtPoint:CGPointMake(0, 0)]; // <--- error here
[layer drawAtPoint:point];
UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
// Build the layered image -- thingPage onto thingBackground,
// then the screenshot on that, then the thingTop on top of it all.
// thingBackground, thingPage and thingTop are all preloaded UIImages.
-(UIImage *)getImageForThing:(Thing *)t {
[self loadImageCacheIfNecessary];
if (!t.screenshot) {
return [UIImage imageNamed:#"NoPreview.png"];
} else {
UIImage *screenshot = t.screenshot;
UIImage *currentImage = [self addLayer:thingPage toImage:thingBackground atPoint:CGPointMake(0, 0)];
currentImage = [self addLayer:screenshot toImage:currentImage atPoint:CGPointMake(39, 59)];
currentImage = [self addLayer:thingTop toImage:currentImage atPoint:CGPointMake(0, 1)];
return currentImage;
}
}
I can't find anywhere that this is going wrong, and I've been tearing my hair out for a couple of hours on this. It's the final known bug in the system, so you can imagine how antsy I am to fix it! :-)
Thanks in advance for any help.
As to me, I always use -(void)drawInRect: instead of -(void)drawAtPoint:
CGRect rtDraw;
rtDraw.origin = CGPointZero;
rtDraw.size = size;
[background drawInRect:rtDraw];
[layer drawInRect:rtDraw];
And ....
The paint method with UIGraphicsBeginImageContext(size) and UIGraphicsEndImageContext() is not thread-safe.
Those functions will push or pop a context with stack struct, which is managed by system.
EXC_BAD_ACCESS is almost always due to accessing an object that has already been released. In your code this seems to be t.screenshot. Check creation (and retaining if it is an instance variable) of the object returned by Thing's screenshot property.
As it turns out, the error wasn't in the code I posted, it was in my caching of the thingBackground, thingPage and thingTop images. I wasn't retaining them. Here's the missing code, fixed:
-(void)loadImageCacheIfNecessary {
if (!folderBackground) {
thingBackground = [[UIImage imageNamed:#"ThingBack.png"] retain];
}
if (!folderPage) {
thingPage = [[UIImage imageNamed:#"ThingPage.png"] retain];
}
if (!folderTop) {
thingTop = [[UIImage imageNamed:#"ThingTop.png"] retain];
}
}
I will admit I'm still not comfortable with the whole retain/release/autorelease stuff in Objective C. Hopefully it'll sink in one day soon. :-)
I am having trouble with my UITableView in the NavigationController.
When I add data to the table I use another class to download images to display in that table, while all that works great but if in the middle of images being download I swtich back to the previous view in the navigationcontroller app crashed.
Here is my code to explain further
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Set appIcon and clear temporary data/image
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
UIImage *appIcon;
if (image.size.width != kAppIconHeight && image.size.height != kAppIconHeight)
{
CGSize itemSize = CGSizeMake(125, 85);
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
appIcon = UIGraphicsGetImageFromCurrentImageContext();
///UIGraphicsEndImageContext();
}
else
{
appIcon = image;
//self.appRecord.appIcon = image;
}
self.activeDownload = nil;
// Release the connection now that it's finished
self.imageConnection = nil;
// call our delegate and tell it that our icon is ready for display
if(delegate != nil)
[delegate appImageDidLoad:self.indexPathInTableView imaged:appIcon ];
[image release];
}
The appImageDidLoad is a method that exists in my UITableView view.
Is there a way I can check to see if the UITableView is valid in my imagedownload class so I know not to send the image.
Thanks in advance.
The crash is due to delegate getting release by the time the image was ready!
Try this in ViewWillDisappear
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress allValues];
[allDownloads performSelector:#selector(cancelDownload)];
This is the solution for this crash.
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress1 allValues];
[allDownloads makeObjectsPerformSelector:#selector(cancelDownload)];
}