My app has a user select a photo from the picker. I apply the selected image to a view and then I save it to a file and reference that file in user defaults so when the UserProfile is created that avatar is loaded in.
When I close the app and then start it again, the app loads the image from the file. After loading the image from the file, my app is crashing when I apply it to an Image view because it is seen as an __NSCFArray. There is no method scale on __NSCFArray. Why is it being cast to this class?
-[__NSCFArray scale]: unrecognized selector sent to instance 0x145260
* Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '[__NSCFArray scale]:
unrecognized selector sent to instance 0x145260'
Here is my code where the UIImage is created from a file:
#implementation UserProfile
- (id) init {
self = [super init];
if(self) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
_username = (NSString *)[userDefaults objectForKey:USERNAME_KEY];
NSString *filename = (NSString *) [userDefaults objectForKey:#"AvatarFilename"];
NSLog(#"filename from user defaults: %#",filename);
if (filename) {
_avatar = [UIImage imageWithContentsOfFile:filename];
if (!_avatar) NSLog(#"LOGERROR: avatar was not created from file");
_customAvatar = TRUE;
} else {
_customAvatar = FALSE;
_avatar = [UIImage imageNamed:DEFAULT_AVATAR_FILENAME];
if (!_avatar) NSLog(#"LOGERROR: avatar was not created from default");
}
[self createThumbnail];
}
return self;
}
Note: in my createThumbnail code I call this [_avatar isKindOfClass:[UIImage class] and it says it is a UIImage. But then when I set the view, it thinks it is an __NSCFArray. I don't even understand how this is possible since the property is a UIImage *.
This is how the image is persisted
- (void) setAvatar:(UIImage *)image
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *filename = nil;
if (image) {
if (_avatar) [_avatar release];
_avatar = [image retain];
_customAvatar = TRUE;
filename = [NSString stringWithFormat:#"%#/%#",[MyUtilities applicationDocumentsDirectory],AVATAR_FILENAME];
if (![UIImagePNGRepresentation(image) writeToFile:filename atomically:YES])
NSLog(#"LOGERROR: Failure to write avatar file");
else NSLog(#"saved avatar to PNG file");
} else {
NSLog(#"setting default avatar");
_avatar = [UIImage imageNamed:DEFAULT_AVATAR_FILENAME];
_customAvatar = FALSE;
}
[userDefaults setObject:filename forKey:AVATAR_KEY];
// TODO If performance is crucial, consider creating a default thumbnail as well
[self createThumbnail];
if(![userDefaults synchronize])
{
NSLog(#"LOGERROR: Failure to synchronize userDefaults");
}
}
To my knowledge I am not receiving a memory warning.
You are assigning autoreleased objects to your ivars. Most likely they are being deallocated and then when you try to access one of those UIImage it happens to be an NSArray at the same memory address.
_username = (NSString *)[userDefaults objectForKey:USERNAME_KEY];
...
_avatar = [UIImage imageWithContentsOfFile:filename];
...
_avatar = [UIImage imageNamed:DEFAULT_AVATAR_FILENAME];
You need to retain them.
You are saving autoreleased object (return by imageNamed: and imageWithContentsOfFile:) without retaining in init. You can replace that method with next one:
- (id) init {
self = [super init];
if(self) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
_username = (NSString *)[userDefaults objectForKey:USERNAME_KEY];
NSString *filename = (NSString *) [userDefaults objectForKey:#"AvatarFilename"];
NSLog(#"filename from user defaults: %#",filename);
if (filename) {
_avatar = [[UIImage imageWithContentsOfFile:filename] retain];
if (!_avatar) NSLog(#"LOGERROR: avatar was not created from file");
_customAvatar = TRUE;
} else {
_customAvatar = FALSE;
_avatar = [[UIImage imageNamed:DEFAULT_AVATAR_FILENAME] retain];
if (!_avatar) NSLog(#"LOGERROR: avatar was not created from default");
}
[self createThumbnail];
}
return self;
}
You don't need to retain this object anymore.
Just release it in dealloc.
In setAvatar: you should also retain returned by imageNamed: value : _avatar = [[UIImage imageNamed:DEFAULT_AVATAR_FILENAME] retain];
Related
I'm creating images array for UIImageView so I can use [_myImageView startAnimating]; method. I figuree out that if I cache(preload) it like this way the animation is fluent.
But in Instruments I see, that game is deallocated but there is still too much memory allocated - I think there are some fragments of this UIGraphicsBeginContext stuff.
Am I using it right, is there some other way how to dealloc it?
Thanks in advance!
- (NSMutableArray *)generateCachedImageArrayWithFilename:(NSString *)filename extension:(NSString *)extension andImageCount:(int)count
{
_imagesArray = [[NSMutableArray alloc] init];
_fileExtension = extension;
_animationImageName = filename;
_imageCount = count;
for (int i = 0; i < count; i++)
{
NSString *tempImageNames = [NSString stringWithFormat:#"%#%i", filename, i];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:tempImageNames ofType:extension];
UIImage *frameImage = [self loadRetinaImageIfAvailable:imagePath];
UIGraphicsBeginImageContext(frameImage.size);
CGRect rect = CGRectMake(0, 0, frameImage.size.width, frameImage.size.height);
[frameImage drawInRect:rect];
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_imagesArray addObject:renderedImage];
if (_isDoublingFrames)
{
[_imagesArray addObject:renderedImage];
}
else if (_isTriplingFrames)
{
[_imagesArray addObject:renderedImage];
[_imagesArray addObject:renderedImage];
}
NSLog(#"filename = %#", filename);
}
return _imagesArray;
}
- (UIImage *)loadRetinaImageIfAvailable:(NSString *)path
{
NSString *retinaPath = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:#"%##2x.%#", [[path lastPathComponent] stringByDeletingPathExtension], [path pathExtension]]];
if ([UIScreen mainScreen].scale == 2.0 && [[NSFileManager defaultManager] fileExistsAtPath:retinaPath] == YES)
return [[UIImage alloc] initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:retinaPath]] CGImage] scale:2.0 orientation:UIImageOrientationUp];
else
return [UIImage imageWithContentsOfFile:path];
}
-(void) dealloc
{
[_imagesArray removeAllObjects];
_imagesArray = nil;
}
I had memory issues similar you wrote and what I would try out in your code
1) use #autoreleasepool
to wrap either the method or the content of the loop in #autoreleasepool { } to create a local pool which will be released after method is done. In your situation it seems to me wrapping the loop is better:
for (int i = 0; i < count; i++)
{
#autoreleasepool {
// the loop code
...
}}
2) kill [UIImage imageNamed]
Using imageNamed will prepare a cache (depending on the size of image) which will never be flushed out of the memory unless you reach a memory warning. Even if you set myImage.image = nil, the image is still in memory. XCode's Intruments tool Allocations view will show it nicely. What you can do is initialize UIImage like this, and iOS won't cache the image:
image = [UIImage imageWithContentsOfFile:fullpath];
You might use imageNamed in your code under loadRetinaImageIfAvailable.
Keep both two approach and let's check this out again!
I am trying to attach multiple photos in a MailComposerViewController and I am using ALAssetPickerViewController to pick multiple photos. I have an NSMutableArray, which contains reference of selected assets. I am implementing a for loop which is enumerating over array of selected assets to get NSData of UIImage and UIImage is initialized with CGImageRef.
Code is as written below:
#autoreleasepool
{
NSString *emailTitle = #"Test";
NSString *messageBody = #"IOS programming is so fun!";
NSArray *toRecipents = [NSArray arrayWithObjects:#"abc#gmail.com", nil];
MFMailComposeViewController *tempmcvc = nil;
tempmcvc = [[MFMailComposeViewController alloc] init];
tempmcvc.mailComposeDelegate = self;
[tempmcvc setSubject:emailTitle];
[tempmcvc setMessageBody:messageBody isHTML:YES];
[tempmcvc setToRecipients:toRecipents];
tempmcvc.modalPresentationStyle=UIModalPresentationFullScreen;
tempmcvc.navigationBar.barStyle = UIBarStyleBlackOpaque;
for (AlAsset *assets in SelectedAssetsarray)
{
#autoreleasepool
{
UIImage *attachImagTemp = nil;
NSData *myData = nil;
CGImageRef iref = [assets.defaultRepresentation fullScreenImage];
NSString *nameOfImgTemp;
attachImagTemp = [UIImage imageWithCGImage:iref];
nameOfImgTemp = assets2.defaultRepresentation.filename;
myData = UIImageJPEGRepresentation (attachImagTemp, 1.0);
[tempmcvc addAttachmentData:myData mimeType:#"image/jpeg" fileName:nameOfImgTemp];
myData = nil;
attachImagTemp = nil;
iref = nil;
nameOfImgTemp = nil;
ALAsset *_temp = assets2;
_temp = nil;
}
}
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self presentModalViewController:tempmcvc animated:YES]
});
Each asset I am attaching hardly is of 2 MB, but memory is constantly decreasing I am unable to release the memory properly; some memory is leaking please help to find the leak.
Try releasing attachment image with following code CGImageRelease(attachImagTemp); to release cache memory instead setting it as nil.
Also release tempmcvc object as suggested by Avi if you have not used ARC.
I'm having really weird situation. I create a singletone object of a class named "Profile like this:
static Profile *currentProfile;
+ (Profile *)currentProfile
{
#synchronized(self)
{
if (currentProfile == nil)
currentProfile = [[Profile alloc] init];
}
return currentProfile;
}
- (id)init
{
self = [super init];
if (self)
{
// Initialization code here.
isChecked = [[[NSUserDefaults standardUserDefaults] objectForKey:#"isChecked"] boolValue];
if (isChecked)
{
NSLog(#"isChecked block is called");
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myEncodedObjectKey"];
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[self retain];
for (int i = 0; i < self.avatar.count; i++)
[self.avatar replaceObjectAtIndex:i withObject:[UIImage imageWithData:[self.avatar objectAtIndex:i]]];
}
else
{
password = #"";
pfefferID = #"";
email = #"";
avatar = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],nil];
isBoy = YES;
friends = [[NSMutableDictionary alloc] init];
rating = 0;
}
}
return self;
}
In init method i check a certain condition stored in NSUserDefaults by using BOOL variable named "isChecked". isChecked is equal to YES and everything works fine. But... i create this Profile object in AppDelegate file like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
users = [[NSMutableDictionary alloc] init];
usersForLeaderboardFromServer = [[NSMutableDictionary alloc] init];
listOfFriendsFromServer = [[NSMutableDictionary alloc] init];
currentProfile = [Profile currentProfile];
sessionID = 0;
if (!currentProfile.isChecked)//why????
NSLog(#"not checked");
if (currentProfile.isChecked)
{
[self getUsersForLeaderboardFromServer];
MainMenuView *mainMenu = [[[MainMenuView alloc] init] autorelease];
[self.navigationController pushViewController:mainMenu animated:YES];
}
}
So the same isChecked variable which a moment (far less than a moment actually) ago was equal to YES gets equal to NO when being used in application didFinishLaunchingWithOptions method by accessing it via dot. What's going on? I'm able to handle it but i'm just curious about this situation. Do you know what's wrong with it?
You're reassigning self in init, so you're returning the new object rather than the one you set isChecked on. See this code:
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[self retain];
It's slightly awkward to do things like you've got - I would certainly not recommend replacing it in the way you have. For a start, the value you set to the static currentProfile is not being updated when you reassign self so that's still the old one. And also you're not releasing the old self when you reassign.
To fix it you could do something like this:
id newSelf = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
newSelf.isChecked = isChecked;
[self release];
self = [newSelf retain];
But I don't really advocate that personally. I suggest you load in the object from your archive and then proceed to update yourself with it rather than reassigning self.
I have a UITableView that is populated using a NSMutableArray . I am using xcode 4.2
the data in the NSMutable array stays in case I switch applications but it gets erased in these two cases :
1-the user switch view and come back .
2-the application is completley closed (i.e. the user double click on the Main button and remove it from the list of running application)
here is the code that I am using
-(NSString *)dataFilePath{
NSString *dataFilePath;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
dataFilePath = [documentDirectory stringByAppendingPathComponent:#"Test App-Info.plist"];
return dataFilePath;
}
-(void)saveData{
[NSKeyedArchiver archiveRootObject:[data copy] toFile:[self dataFilePath]];
}
- (void)loadData
{
data = [NSKeyedUnarchiver unarchiveObjectWithFile:self.dataFilePath];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
...
//saving the history
NSArray *archivedArray =[NSKeyedUnarchiver unarchiveObjectWithFile:[self dataFilePath]];
if (archivedArray == nil) {
data = [[NSMutableArray alloc] init];
}
else {
[self loadData];
[mainTableView reloadData];
}
}
Please let me know if I am missing something
Thanks
Edited :
the save data function is loaded in two locations :
1- the app that I am developing scans QR codes , so save data is called in the following function :
- (void) imagePickerController: (UIImagePickerController*) reader
didFinishPickingMediaWithInfo: (NSDictionary*) info
{
....
id<NSFastEnumeration> results =
[info objectForKey: ZBarReaderControllerResults];
ZBarSymbol *symbol = nil;
for(symbol in results){
// EXAMPLE: just grab the first barcode
break;
}
if(!symbol)
return;
// EXAMPLE: do something useful with the barcode data
theBarcodeString = symbol.data;
//adding the string to the list
[data addObject:theBarcodeString];
[self saveData];
[mainTableView reloadData];
[self endText];
stringLabel.text=theBarcodeString;
...
}
it is also called in when the data is edited :
-(IBAction)editTable{
UIBarButtonItem *leftItem;
[mainTableView setEditing:!mainTableView.editing animated:YES];
if (mainTableView.editing) {
leftItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(editTable)];
}
else {
leftItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:#selector(editTable)];
}
[self saveData];
[mainTableView reloadData];
}
You need to look into the following calls:
applicationDidEnterBackground
applicationWillEnterForeground
applicationDidBecomeActive
applicationWillTerminate
They are all methods of UIApplication. Your needs will dictate which of the above store and restore your data depending on when it should happen.
The user can choose whether he wants to use an existing image or take a new one for use as the background.
Everything works as far as the FirstViewController is concerned. Sadly the image is no longer set as the background, when the SecondViewController is displayed.
How do I enable the use of the image as background for the SecondViewController?
I used this piece of code (in the SecondViewController):
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
int bla = [prefs integerForKey:#"background_key"];
if (bla == 1) {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"1.mac.jpg"]];
} else if (bla == 2) {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"black.jpg"]];
} else if (bla == 3) {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"pinn.png"]];
} else if (bla == 4) {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"MyImage.png"]];
}
I tried to save the chosen image as "MyImage.png" but it looks as though it did not work. The background is black instead of the image selected.
I used this code (from the 1stViewController) to save the chosen picture as "MyImage.png":
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)anInfo {
//UIImage *selectedImage;
NSURL *mediaUrl;
mediaUrl = (NSURL *)[anInfo valueForKey:UIImagePickerControllerMediaURL];
if (mediaUrl == nil)
{
selectedImage = (UIImage *) [anInfo valueForKey:UIImagePickerControllerEditedImage];
if (selectedImage == nil)
{
selectedImage = (UIImage *) [anInfo valueForKey:UIImagePickerControllerOriginalImage];
NSLog(#"Original image picked.");
}
else
{
NSLog(#"Edited image picked.");
}
}
[picker dismissModalViewControllerAnimated:YES];
if (selectedImage != nil)
{
self.view.backgroundColor = [UIColor colorWithPatternImage:selectedImage];
}
// Get the image from the result
UIImage* ownImage = [anInfo valueForKey:UIImagePickerControllerOriginalImage];
// Get the data for the image as a PNG
NSData* imageData = UIImagePNGRepresentation(ownImage);
// Give a name to the file
NSString* imageName = #"MyImage.png";
// Now, we have to find the documents directory so we can save it
// Note that you might want to save it elsewhere, like the cache directory,
// or something similar.
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
// Now we get the full path to the file
NSString* fullPathToFile = [documentsDirectory stringByAppendingPathComponent:imageName];
// and then we write it out
[imageData writeToFile:fullPathToFile atomically:NO];
return;
}
There must be a mistake or logical mistake.
Help would be appreciated!
ImageNamed is generally used to load images that are included as part of the resources of an application.
I suspect you want:
UIImage* image = [UIImage imageWithContentsOfFile:fileNameAndPath];
Try loading the image with similar code to how you save it instead of using +imageNamed:: build the full path and load it from there.
+imageNamed: "looks for an image with the specified name in the application’s main bundle." (UIImage reference), but that's not where you are saving it to. I don't think you can write into the application's bundle, so you won't be able to use +imageNamed: here.