Button getting EXC_BAD_ACCESS by method with ivars - uiimageview

I'm neophyte in obj-c, so I cant understand some of this logic. I want to understand my code and app logic. My app is modification of easy example with animation of UIImageView: this's .h (in standart view-based template)
#import <UIKit/UIKit.h>
#interface ImageAnimatorViewController : UIViewController {
UIImageView *animImage;
}
#property (nonatomic, retain) IBOutlet UIImageView *animImage;
- (IBAction)startAnimation;
- (NSArray*)creatAnimation:(NSString*)fileName;
#end
this's .m (without standart dealloc and viewDidUnload)
#synthesize animImage;
- (IBAction)startAnimation{
[animImage startAnimating];
}
- (void)viewDidLoad{
[super viewDidLoad];
animImage.animationImages = [self creatAnimation:#"Images.jpg"];
animImage.animationDuration = 1.0f;
animImage.animationRepeatCount = 0;
}
- (NSArray*)creatAnimation:(NSString*)fileName{
UIImage *image = [UIImage imageNamed:fileName];
NSMutableArray *animationImages = [NSMutableArray array];
for (int i = 0; i<8; i++) {
CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage,
CGRectMake(i*600.0f, 0.0f, 600.0f, 262.0f));
UIImage *animationImage = [UIImage imageWithCGImage:imageRef];
[animationImages addObject:animationImage];
[animationImage release];
}
return animationImages;
}
Also i've xib with imageview and button, but when i pressed b there's crash and EXC_BAD_ACCESS in console. In IBAction I understand imageView hasn't animationImages, but why? and has strange reference count = 3,because in viewDidLoad has 2(and why 2?). I add self and self-> prefix in viewDidLoad, but it's no result. animationImages is (copy) property,so images saves after autorelease pool is drained with animationImages. I'm very surprised about this behavior of app.10x!

Do not release animationImage in createAnimation method. You also should clean memory for CGImageRef using CGImageRelease

Related

Simple UICollectionView to show images behaves odd: Some Images are displayed correct, some at wrong position, some missing at all

I want to show images in a grid on iPhone using a UICollectionView, showing 3 images a row. For a "dead simple test" (as I thought), I've added 15 JPG images to my project, so that they'll be in my bundle and I can load them simply via [UIImage imageNamed:...].
I think I've done everything correct (setting up & registering UICollectionViewCell subclass, use of UICollectionViewDataSource Protocol methods), however, the UICollectionView behaves very weird:
It shows only a few of the images in the following pattern:
First line shows image 1 & 3, second line is blank, next line like the first again (image 1 & 3 showing properly), fourth line blank, and so on...
If I push a button in my NavBar that triggers [self.collectionView reloadData], random cells appear or disappear. What drives me nuts is that it's not only an issue of images appear or not. Sometime, images also swap between the cells, i.e. they appear for a indexPath they are definitely not wired up!
Here is my code for the cell:
#interface AlbumCoverCell : UICollectionViewCell
#property (nonatomic, retain) IBOutlet UIImageView *imageView;
#end
#implementation AlbumCoverCell
#synthesize imageView = _imageView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_imageView = [[UIImageView alloc] initWithFrame:frame];
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)dealloc
{
[_imageView release];
[super dealloc];
}
- (void)prepareForReuse
{
[super prepareForReuse];
self.imageView.image = nil;
}
#end
Part of the code for my UICollectionViewController subclass, where 'imageNames' is an NSArray holding all jpg filenames:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[AlbumCoverCell class] forCellWithReuseIdentifier:kAlbumCellID];
}
#pragma mark - UICollectionViewDataSource Protocol methods
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imageNames count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
AlbumCoverCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kAlbumCellID forIndexPath:indexPath];
NSString *imageName = [self.imageNames objectAtIndex:indexPath.row];
NSLog(#"CV setting image for row %d from file in bundle with name '%#'", indexPath.row, imageName);
cell.imageView.image = [UIImage imageNamed:imageName];
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout Protocol methods
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
{
return CGSizeMake(100, 100);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
{
return UIEdgeInsetsMake(0, 0, 0, 0);
}
From the NSLog statement in cellForItemAtIndexPath: I can see that the method is called for all of the cells (not only the one's displayed) and that the mapping between indexPath.row and filename is correct.
Has anybody an idea what could cause this weird behavior?
In the meantime, I've found the solution. It was actually a very subtle error in the implementation of my UICollectionViewCell subclass AlbumCoverCell.
The problem is that I've set the frame of the cell instance as the frame of the UIImageView subview instead of passing the bounds property of the cell's contentView!
Here is the fix:
#implementation AlbumCoverCell
#synthesize imageView = _imageView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// WRONG:
// _imageView = [[UIImageView alloc] initWithFrame:frame];
// RIGHT:
_imageView = [[UIImageView alloc] initWithFrame:self.contentView.bounds];
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)prepareForReuse
{
[super prepareForReuse];
// reset image property of imageView for reuse
self.imageView.image = nil;
// update frame position of subviews
self.imageView.frame = self.contentView.bounds;
}
...
#end

UIScrollView Memory-Allocation and Release

I am working on a pretty simple and straightforward app that uses a couple of pages with scroll views. I have an array of images in ViewDidLoad and then a mutable array that shows current page plus and minus 1. There is a purge page method which is supposed to remove the extra images from the superview. When running this on an actual device with a large quantity of images, a memory warning comes up after scrolling through 50 or so images and then the app crashes. I ran this through instruments and see that the memory in increasing substantially with each swipe. When creating the array I used imageNamed as well as imageWithContentsOfFile and either way gives close to the same result. I realize there have been multiple scrollview questions here, but somehow I cannot seem to get past this one. Frustrating to say the least. I hope someone can look at this with a fresh set of eyes and shed some light on my problem. Thanks very much in advance.
EDIT: Forgot to mention ARC use. Yes, I use ARC.
Code: .h file
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) IBOutlet UIPageControl *pageControl;
#end
and the .m file
#import "FirstViewController.h"
#interface FirstViewController ()
#property (nonatomic, strong) NSArray *pageImages;
#property (nonatomic, strong) NSMutableArray *pageViews;
- (void)loadVisiblePages;
- (void)loadPage:(NSInteger)page;
- (void)purgePage:(NSInteger)page;
#end
#implementation FirstViewController
#synthesize scrollView = _scrollView;
#synthesize pageControl = _pageControl;
#synthesize pageImages = _pageImages;
#synthesize pageViews = _pageViews;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)loadVisiblePages {
// First, determine which page is currently visible
CGFloat pageWidth = self.scrollView.frame.size.width;
NSInteger page = (NSInteger)floor((self.scrollView.contentOffset.x * 2.0f + pageWidth) / (pageWidth * 2.0f));
// Update the page control
self.pageControl.currentPage = page;
// Work out which pages you want to load
NSInteger firstPage = page - 1;
NSInteger lastPage = page + 1;
// Purge anything before the first page
for (NSInteger i=0; i<firstPage; i++) {
[self purgePage:i];
}
// Load pages in our range
for (NSInteger i=firstPage; i<=lastPage; i++) {
[self loadPage:i];
}
// Purge anything after the last page
for (NSInteger i=lastPage+1; i<self.pageImages.count; i++) {
[self purgePage:i];
}
}
- (void)purgePage:(NSInteger)page {
if (page < 0 || page >= self.pageImages.count) {
// If it's outside the range of what you have to display, then do nothing
return;
}
// Remove a page from the scroll view and reset the container array
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView != [NSNull null]) {
[pageView removeFromSuperview];
[self.pageViews replaceObjectAtIndex:page withObject:[NSNull null]];
}
}
- (void)loadPage:(NSInteger)page {
if (page < 0 || page >= self.pageImages.count) {
// If it's outside the range of what you have to display, then do nothing
return;
}
// 1
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView == [NSNull null]) {
// 2
CGRect frame = self.scrollView.bounds;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0.0f;
// 3
UIImageView *newPageView = [[UIImageView alloc] initWithImage:[self.pageImages objectAtIndex:page]];
newPageView.contentMode = UIViewContentModeScaleAspectFit;
newPageView.frame = frame;
[self.scrollView addSubview:newPageView];
// 4
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// Load the pages that are now on screen
[self loadVisiblePages];
}
- (void)viewDidLoad {
[super viewDidLoad];
// 1
self.pageImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"BBD1.jpg"],
[UIImage imageNamed:#"BBD2.jpg"],
[UIImage imageNamed:#"BBD3.jpg"],
[UIImage imageNamed:#"BBD4.jpg"],
[UIImage imageNamed:#"BBD5.jpg"],
[UIImage imageNamed:#"BBD6.jpg"],
[UIImage imageNamed:#"BBD7.jpg"],
.....
[UIImage imageNamed:#"BBD292.jpg"],
[UIImage imageNamed:#"BBD293.jpg"],
[UIImage imageNamed:#"BBD294.jpg"],
[UIImage imageNamed:#"BBD295.jpg"],
[UIImage imageNamed:#"BBD296.jpg"],
[UIImage imageNamed:#"BBD297.jpg"],
[UIImage imageNamed:#"BBD298.jpg"],
[UIImage imageNamed:#"BBD299.jpg"],
[UIImage imageNamed:#"BBD300.jpg"],
nil];
NSInteger pageCount = self.pageImages.count;
// 2
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = pageCount;
// 3
self.pageViews = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < pageCount; ++i) {
[self.pageViews addObject:[NSNull null]];
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 4
CGSize pagesScrollViewSize = self.scrollView.frame.size;
self.scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * self.pageImages.count, pagesScrollViewSize.height);
// 5
[self loadVisiblePages];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
#end
I really appreciate you taking the time to look at this and hope there is someone out there who can point me in the right direction.
THANKS!
Loading the actual images into the array incurs a much larger overhead than storing just the names in the array and loading the image like
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[self.pageImages objectAtIndex:page]];
UIImageView *newPageView = [[UIImageView alloc] initWithImage:image];
[image release];
At this point, only the newPageView will hold a reference to the image in memory, and when that newPageView is removed from it's superview, the image in memory will be released.
You don't say if you are using ARC or not, but without ARC there are obvious problems in memory management. In loadPage: at comment "// 3" you create newPageView with a retain count of 1, then add it as a subview of self.scrollView and to the array self.pageViews. Thus it has an overall retain count of +3. In purgePage: you remove it from self.scrollView and self.pageViews but are still left with an overall retain count of +1. Thus the UIImageView for each page is forgotten but not deallocated.
You can fix this by using ARC in your project or modifying the line where you create newPageView to be instead:
UIImageView *newPageView = [[[UIImageView alloc] initWithImage:[self.pageImages objectAtIndex:page]] autorelease];

Core Image help: Setting CIImage from UIImage via UIImagePicker (then filtering it)

Searched around on SO for an answer. Found some interesting stuff but I am still stuck.
In a view controller I have a UIImageView which I set an image taken from UIImagePicker when the view loads.
(I also set a CIImage at the same time)
I have two sliders (one for brightness, one for contrast). Upon moving the slider, a filter is applied to the CIImage, then the CIImage is rendered into the the UIImageView's UIImage.
Fist off, the UIImage taken from UIImagePicker does show up correctly when first selected.
I also have my slider ranges set correctly and have verified the proper float values are being passed to the delegate functions (via NSLog).
However, when I try playing with the sliders, my UIImageView turns white! Perhaps you folks can help. I'll post some code snippets:
First, my #interface:
#interface PrepViewController : UIViewController <UIImagePickerControllerDelegate , UINavigationControllerDelegate>
#property (weak, nonatomic) IBOutlet UIImageView *editingImage;
#property (weak, nonatomic) CIImage *editingCIImage;
#property (weak, nonatomic) CIContext *editingCIContext;
#property (weak, nonatomic) CIFilter *editingCIFilter;
#property (nonatomic) BOOL didAskForImage;
#property (weak, nonatomic) IBOutlet UISlider *brightnessSlider;
#property (weak, nonatomic) IBOutlet UISlider *contrastSlider;
- (void) doImageFilter:(NSString *)filter withValue:(float)value;
#end
My viewDidLoad, pretty simple just sets didAskForImage to NO
- (void)viewDidLoad
{
[super viewDidLoad];
self.didAskForImage = NO;
}
viewDidAppear is where I check if I've already asked for the image, then ask for it
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if(!self.didAskForImage){
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = NO;
UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
sourceType = UIImagePickerControllerSourceTypeCamera;
}
picker.sourceType = sourceType;
[self presentModalViewController:picker animated:YES];
}
}
Here is the imagePickerController didFinishPickingImage delegate method
this is where I actually set the UIImageView's image property and initialize the CIImage, CIContext, and CIFilter objects.
I also set the didAskForImage boolean and the slider targets.
-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingImage : (UIImage *)image
editingInfo:(NSDictionary *)editingInfo
{
self.didAskForImage = YES;
self.editingImage.image = image;
self.editingCIImage = [[CIImage alloc] initWithImage:image];
self.editingCIContext = [CIContext contextWithOptions:nil];
self.editingCIContext = [CIContext contextWithOptions:nil];
self.editingCIFilter = [CIFilter filterWithName:#"CIColorControls" keysAndValues:kCIInputImageKey, self.editingCIImage, nil];
[self.contrastSlider addTarget:self action:#selector(contrastMove:) forControlEvents:UIControlEventValueChanged];
[self.brightnessSlider addTarget:self action:#selector(brightnessMove:) forControlEvents:UIControlEventValueChanged];
[picker dismissModalViewControllerAnimated:YES];
}
and the cancel delegate
-(void)imagePickerControllerDidCancel:(UIImagePickerController *) picker
{
[picker dismissModalViewControllerAnimated:YES];
picker = nil;
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
}
Here are the sliders' delegate methods. Again, the NSLogs display the expected values.
- (void) contrastMove:(id)sender{
NSLog(#"%f", [(UISlider *)sender value]);
[self doImageFilter:#"inputContrast" withValue:[(UISlider *)sender value]];
}
- (void) brightnessMove:(id)sender{
NSLog(#"%f", [(UISlider *)sender value]);
[self doImageFilter:#"inputBrightness" withValue:[(UISlider *)sender value]];
}
And finally, here is the doImageFilter method I created, which actually does the filter and reassigns the UIImageView (this is where it turns white)
- (void) doImageFilter:(NSString *)filter withValue:(float)value{
[self.editingCIFilter setValue:[NSNumber numberWithFloat:value] forKey:filter];
CIImage *output = [self.editingCIFilter outputImage];
CGImageRef cgi = [self.editingCIContext createCGImage:output fromRect:[output extent]];
self.editingCIImage = output;
self.editingImage.image = [UIImage imageWithCGImage:cgi];
}
So I have a hunch that the CIImage isn't even being initialized properly in the first place; but I don't know why. Been at this for a couple hours now.
Thanks for all your help!
Cheers
Hahaha, oh wow.
So I figured it out, my CIContext and CIFilter need to be set to strong instead of weak.
Awesome.

How do I put a background image on a text area (IN THE IPAD)

How do I put a template'd background image on a text area?
Not sure what you mean with "template'd"... but....
In interface builder, add the imageview behind the textview, uncheck the "opaque" box for the textview.
You can set the contents property of the text views layer.
#import <QuartzCore/QuartzCore.h>
...
- (void) viewDidLoad {
[super viewDidLoad];
textView.layer.contents = (id)[UIImage imageNamed:#"myImage.png"].CGImage
}
should work
If you want to add the image programmaticaly I managed to do it this way:
First in my textviewViewController.h I added an outlet for the UITextView.
#import <UIKit/UIKit.h>
#interface textviewViewController : UIViewController {
UITextView *textView;
}
#property (nonatomic, retain) IBOutlet UITextView *textView;
#end
Next, in my .xib I added my UITextView and connected my outlet.
Lastly in the viewDidLoad of textviewViewController.m I add the image as a subview to my textview.
- (void)viewDidLoad {
[super viewDidLoad];
// Change the pathForResource to reflect the name and type of your image file
NSString *path = [[NSBundle mainBundle] pathForResource:#"d" ofType:#"jpg"];
UIImage *img = [UIImage imageWithContentsOfFile:path];
UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
[self.textView insertSubview:imgView atIndex:0];
[imgView release];
}
I tried the insertSubview:atIndex: with both 0 and 1 with the same result, but I would stick with 0 idicating that it's behind the textview.

Archiving and Unarchiving results in Bad Access

I'm having trouble setting up a model object to save the visual state of user generated CALayers in a simple graphics application for the iphone.
I'm attempting to save the background color and frame of all the current layers on screen by passing those properties to model objects which implement the NSCoding protocol and then into an NSMutableArray which the app delegate owns. Then I archive the array with NSKeyedArchiver and store it in the NSUserDefaults.
Each CALayer's backgroundColor property is converted to a UIColor to be encoded by the model object for storage. I think that I'm unarchiving the array incorrectly or not restoring state from the unarchived array correctly. When I attempt to access the UIColor object that was store in the model object, I get an EXC_BAD_ACCESS.
I thought it was possibly a bug with encoding UIColor objects so tried pulling the values out of the CGColorRef with the CGColorGetComponents function and storing them in an array to encode and archive, but I had the same result of bad access after unarchiving, so I think I'm just doing it wrong.
This is my model object:
#interface AILayerData : NSObject <NSCoding> {
UIColor* color;
CGRect frame;
}
#property (retain) UIColor* color;
#property (assign) CGRect frame;
#end
#implementation AILayerData
#synthesize color;
#synthesize frame;
- (void)encodeWithCoder:(NSCoder *)coder;
{
[coder encodeObject:color forKey:#"color"];
[coder encodeCGRect:frame forKey:#"frame"];
}
- (id)initWithCoder:(NSCoder *)coder;
{
self = [[AILayerData alloc] init];
if (self != nil)
{
color = [coder decodeObjectForKey:#"color"];
frame = [coder decodeCGRectForKey:#"frame"];
}
return self;
}
#end
And this is my archiving implementation:
#implementation AppDelegate
- (void)applicationWillTerminate:(UIApplication *)application {
NSArray *layersArray = viewController.view.layer.sublayers;
dataArray = [NSMutableArray array];
for(AILayer *layer in layersArray)
{
AILayerData *layerData = [[AILayerData alloc] init];
layerData.frame = layer.frame;
UIColor *layerColor = [UIColor colorWithCGColor:layer.backgroundColor];
layerData.color = layerColor;
[dataArray addObject:layerData];
[layerData release];
}
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:layerDataArray] forKey:#"savedArray"];
}
#end
And here is where I restore state:
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
spaceView = [[AISpaceView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = spaceView;
[spaceView release];
spaceView.delegate = self;
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"savedArray"];
if (dataRepresentingSavedArray != nil) {
[self restoreStateWithData:dataRepresentingSavedArray];
}
}
- (void)restoreStateWithData:(NSData *)data
{
NSArray *savedLayers = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if (savedLayers != nil) {
NSArray *restoredLayers = [[NSArray alloc] initWithArray:savedLayers];
for(AILayerData *layerDataObject in restoredLayers) {
UIColor *layerColor = layerDataObject.color;
AILayer *newLayer = [[AILayer alloc] init];
newLayer.backgroundColor = layerColor.CGColor;
newLayer.frame = layerDataObject.frame;
newLayer.isSelected = NO;
[self.view.layer addSublayer:newLayer];
[newLayer release];
}
[restoredLayers release];
[spaceView.layer layoutSublayers];
}
}
#end
Any help with this is greatly appreciated. I'm pretty much a noob. I was encoding, archiving and unarching an NSArray of NSNumbers converted from the color's floats in pretty much the same way and getting bad access.
You certainly want to retain the color in initWithCoder:
color = [[coder decodeObjectForKey:#"color"] retain];
or, with the dot syntax as color was declared as a retain property:
self.color = [coder decodeObjectForKey:#"color"];
You are over-releasing layerColor: You don't own it (layerDataObject does), but you are releasing it.
It looks like NSCoder for iPhone doesn't respond to -encodeWithCGRect:
Source: http://17.254.2.129/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSCoder_Class/Reference/NSCoder.html

Resources