Creating a method for wrapping a loaded image in a UIImageView - objective-c

I have the following un my applicationDidFinishLaunching method
UIImage *image2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"image2.jpg" ofType:nil]];
view2 = [[UIImageView alloc] initWithImage:image2];
view2.hidden = YES;
[containerView addSubview:view2];
I'm simply adding an image to a view. But because I need to add 30-40 images I need to wrap the above in a function (which returns a UIImageView) and then call it from a loop.
This is my first attempt at creating the function
-(UIImageView)wrapImage:(NSString *)imagePath
{
UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:imagePath
ofType:nil]];
UIImageView *view = [[UIImageView alloc] initWithImage:image];
view.hidden = YES;
return view;
}
And then to invoke it I have the following so far, for simplicity I am only wrapping 3 images
//Create an array and add elements to it
NSMutableArray *anArray = [[NSMutableArray alloc] init];
[anArray addObject:#"image1.jpg"];
[anArray addObject:#"image2.jpg"];
[anArray addObject:#"image3.jpg"];
//Use a for each loop to iterate through the array
for (NSString *s in anArray) {
UIImageView *wrappedImgInView=[self wrapImage:s];
[containerView addSubview:wrappedImgInView];
NSLog(s);
}
//Release the array
[anArray release];
I have 2 general questions
Is my approach correct?, ie following best practices, for what I am trying to do (load a multiple number of images(jpgs, pngs, etc.) and adding them to a container view)
For this to work properly with a large number of images, do I need to keep my array creation separate from my method invocation?
Any other suggestions welcome!

Just a note, in function declaration you should return pointer to UIImageView, not an UIImageView itself (i.e. add an asterisk).
Also, when returning the view from the function you should autorelease it. Otherwise it will leak memory. So initialization should look something like this:
UIImageView *view = [[[UIImageView alloc] initWithImage:image] autorelease];
Everything else seems to look good.

Related

UIPageViewController - view doesn't load

I want to load a UIView as the first page and some UIImageViews after that. The UIImageViews load fine, but the first UIView shows blank.
I designed the UIView in storyboard. It has some UIButtons and few other controls. What am I doing wrong?
I created the project from the default pageController template.
ModelController.m
- (id)init
{
self = [super init];
if (self) {
// Create the data model.
/*
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
_pageData = [[dateFormatter monthSymbols] copy];
*/
NSMutableArray *imageViews = [[NSMutableArray alloc] init];
FirstViewController *firstViewController = [[FirstViewController alloc] init];
firstViewController.view.frame = CGRectMake([UIScreen mainScreen].bounds.origin.x,[UIScreen mainScreen].bounds.origin.y,[[UIScreen mainScreen] bounds].size.height, [[UIScreen mainScreen] bounds].size.width);
[imageViews addObject:firstViewController];
for (int i=1; i<=19; i++) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:#"picture%i.jpg", i]]];
imageView.frame = CGRectMake([UIScreen mainScreen].bounds.origin.x,[UIScreen mainScreen].bounds.origin.y,[[UIScreen mainScreen] bounds].size.height, [[UIScreen mainScreen] bounds].size.width);
[imageViews addObject:imageView];
}
_pageData = imageViews;
}
return self;
}
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
// Return the data view controller for the given index.
if (([self.pageData count] == 0) || (index >= [self.pageData count])) {
return nil;
}
// Create a new view controller and pass suitable data.
DataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:#"DataViewController"];
dataViewController.dataObject = [self.pageData objectAtIndex:index];
if(index == 0)
{
//[dataViewController addChildViewController:[self.pageData objectAtIndex:index]];
FirstViewController *firstViewController = [self.pageData objectAtIndex:index];
[dataViewController.view addSubview:firstViewController.view];
}
else
{
UIImageView *imageView = [self.pageData objectAtIndex:index];
[dataViewController.view addSubview:imageView];
}
return dataViewController;
}
You haven't provided enough context to be certain, but it appears that what you need to load from the storyboard initially is an instance of FirstViewController, not an instance of UIView, so you'd need to do something like this instead of what you're currently doing:
FirstViewController *firstViewController = [self.storyboard instantiateInitialViewController];
// Note: If you need to adjust the frame of the controller's view, use the screen's
// application frame rather than its bounds.
controller.view.frame = [UIScreen mainScreen].applicationFrame;
Also, this seems like a bad idea, since you later fill the rest of the array with instances of UIImageView:
[imageViews addObject:firstViewController];
Why not add the controller's view to the array? Then you could eliminate the conditional logic in your viewControllerAtIndex:storyboard: method. Also, it's not clear why you're passing the storyboard in as an argument rather than using the built-in property, or for that matter, what class the code you posted belongs to, and what its relation is to the rest of your app. Maybe you could provide a little more info about that.
I got it to working by adding a xib file for FirstViewController. What ever I design in the xib shows up well. Previously I was designing it in the storyboard.

Cocoa Touch: Adding images in for loop

this is crashing on me when I put in an array of image urls, I think its because of me creating the UIImageView and then trying to do it again with the same name!
- (void)cycleImages:(NSArray *)images {
int fromLeft = 5;
for(int n = 0; n <= [images count]; n++){
UIImageView *myImageView = [[UIImageView alloc] initWithImage:[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[images objectAtIndex:n]]]]];
myImageView.frame = CGRectMake(fromLeft, 5, 48, 48); // from left, from top, height, width
fromLeft = fromLeft + 53;
//add image view to view
[self.view addSubview:myImageView];
NSLog(#"%#", [images objectAtIndex:n]);
}
}
Any ideas?
Thanks!
Dex
First, you should use fast enumeration instead of an index loop. Fast enumeration is simpler, clearer, and faster—whenever you don't specifically need an index for something, there is no reason not to use fast enumeration.
UIImageView *myImageView = [[UIImageView alloc] initWithImage:[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[images objectAtIndex:n]]]]];
This assumes that [images objectAtIndex:n] is a string. Is that what you put into the array?
Also, because you created that image view with alloc, you need to release it. If you're using ARC, that's done automatically, so you don't need to do anything; otherwise, you need to either send it release after adding it as a subview or send it autorelease.
this is crashing on me …
Please be more specific. What sort of “crash” do you get, and on what line?
If you get a crash log, please edit your question to include it.

iOS: Add multiple UIImageViews programmatically in runtime

So, I'm doing a Breakout-clone on the iPhone. All elements except the bricks to hit, are created and working as expected with the NIB-file.
However, if I want to create different levels and run collision detection on the bricks, it seems stupid to add them in Interface Builder. How do I add them to the view in code?
I got an image called "brick.png" that I want to use with an UIImageView. Also, I want to have arrays and / or lists with these so I can build cool levels with pattern in the bricks and all :)
How do I do this in code?
#Mark is right, I would just add where the image need to be displayed!
UIImageView *imgView = [[[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 100, 20)] autorelease];
NSString *imgFilepath = [[NSBundle mainBundle] pathForResource:#"brick" ofType:#"png"];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:imgFilepath];
[imgView setImage:img];
[img release];
[self.view addSubview:imgView];
I tested the code and for me only shows when told the coordinates
It's really pretty easy. Here's an example of how you would create and display a UIImageView programatically...
UIImageView *imgView = [[[UIImageView alloc] init] autorelease];
NSString *imgFilepath = [[NSBundle mainBundle] pathForResource:#"brick" ofType:#"png"];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:imgFilePath];
[imgView setImage:img];
[img release];
[self.view addSubview:imgView];
That's pretty much all there is to it.

problem with UiImageView.image = nil

.h file
#interface AppViewController : UIViewController {
...
UIImageView *imageViewArray[3][5];// not using any #property for this
...
}
...
#define FOR(j,q) for (int j = 0; j < q; j++)
.m file
ViewDidLoad{
FOR(i,5){
FOR(j,3)
{
image_names[0] = [UIImage imageNamed:[NSString stringWithFormat:#"%d.png",j]];
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_names[0]];
CGRect newFrame ;
newFrame = CGRectMake(60+(j*130), 110+(150*i),130,136);
imageViewArray[j][i].frame = newFrame;
[self.view addSubview:imageViewArray[j][i]];
}
}
}
//clear the previous images and call a method to add new
-(IBAction)Change{
FOR(i,5)
FOR(p,3)
imageViewArray[p][i].image = nil;
[self ImageSwitch];
}
-(void)ImageSwitch{
FOR(i,5){
FOR(j,3)
{
image_namesTmp[0] = [UIImage imageNamed:[NSString stringWithFormat:#"%d.png",j+3]];
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_namesTmp[0]];
CGRect newFrame ;
newFrame = CGRectMake(60+(j*130), 110+(150*i),130,136);
imageViewArray[j][i].frame = newFrame;
[self.view addSubview:imageViewArray[j][i]];
}
}
}
when i press the button for the first time it works fine(old images get replaced with new ones),but if i do it second time
imageViewArray[p][i].image = nil;
this line wont work and all the previous images still remain there and new images are overlapping with the present ones
In the ImageSwitch method you are always creating new instances of UIImageView.
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_namesTmp[0]];
At this point, there already is a reference to UIImageView stored in the variable. You are losing your reference to it and since you alloc/inited it, you will most probably also leak its memory. Moreover, you are adding new UIImageViews to the view hierarchy, but you are not removing the old ones.
Try something like this:
[imageViewArray[j][i] removeFromSuperview];
[imageViewArray[j][i] release];
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_namesTmp[0]];
Second time you set your images you don't need to recreate the UIImageView objects. Your ImageSwitch method is better declared as,
-(void)ImageSwitch{
FOR(i,5){
FOR(j,3)
{
imageViewArray[j][i].image = [UIImage imageNamed:[NSString stringWithFormat:#"%d.png",j+3]];
}
}
}
Just changing the images will suffice and that's what you need to do.
You need an NSMutableArray of ImageViews. In your header declare the NSMutableArray:
NSMutableArray *_imageViewArray;
Then in your viewDidLoad method initialize the array and then populate it with your image views.
_imageViewArray = [[NSMutableArray alloc]init];
for (int i =0; i < imageNames; i++) {
UIImageView *tempImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:[imageNames objectAtIndex:i];
[_imageViewArray addObject:tempImageView];
[tempImageView release];
}
So thats the image view array populated, you may do it a different way but thats up to you.
To switch the views over what I would do is like so...
[self.view removeAllSubviews]; // gets rid of all previous subviews.
[self.view addSubview:[_imageViewArray objectAtIndex:3]];// could be any number. generate a random number if you want.
Thats all you would need to do. If you wanted to know which image view was being displauyed the maybe declare an instance of UIImageView in your header called _currentImageView and then you can remove just that image view instead of all subviews.
Hope this helped.

I get EXC_BAD_ACCESS when MaxConcurrentOperationCount > 1

Hello i am using NSOperationQueue to download images in the background. I have created a custom NSOperation to download the images. I put the images in table cells. The problem is if I do [operationQueue setMaxConcurrentOperationCount: 10] and i scroll down several cells the program crashes with EXC_BAD_ACCESS. Every time it crashes at the same place in the table. There are 3 cells one after the other which are for the same company and have the same logo so basically it should download the images 3 times. Every other time it works fine.
- (void) main
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [[NSURL alloc] initWithString:self.imageURL];
debugLog(#"downloading image: %#", self.imageURL);
//NSError *error = nil;
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[url release];
UIImage *image = [[UIImage alloc] initWithData:data];
[data release];
if (image)
{
if (image.size.width != ICONWIDTH && image.size.height != ICONHEIGHT)
{
UIImage *resizedImage;
CGSize itemSize = CGSizeMake(ICONWIDTH, ICONHEIGHT);
//!!! UIGraphicsBeginImageContext NOT THREAD SAFE
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.theImage = resizedImage;
}
else
{
self.theImage = image;
}
[image release];
}
[delegate didFinishDownloadingImage: self];
[pool release];
}
This is how i handle downloading the images. If i comment out
[delegate didFinishDownloadingImage: self];
in the function above it doesn't crash but of course it is useless.
-(void) didFinishDownloadingImage:(ImageDownloadOperation *) imageDownloader
{
[self performSelectorOnMainThread: #selector(handleDidFinishDownloadingImage:) withObject: imageDownloader waitUntilDone: FALSE];
}
-(void) handleDidFinishDownloadingImage:(ImageDownloadOperation *)imageDownloadOperation
{
NSArray *visiblePaths = [self.myTableView indexPathsForVisibleRows];
CompanyImgDownloaderState *stateObject = (CompanyImgDownloaderState *)[imageDownloadOperation stateObject];
if ([visiblePaths containsObject: stateObject.indexPath])
{
//debugLog(#"didFinishDownloadingImage %# %#", imageDownloader.theImage);
UITableViewCell *cell = [self.myTableView cellForRowAtIndexPath: stateObject.indexPath];
UIImageView *imageView = (UIImageView *)[cell viewWithTag: 1];
if (imageDownloadOperation.theImage)
{
imageView.image = imageDownloadOperation.theImage;
stateObject.company.icon = imageDownloadOperation.theImage;
}
else
{
imageView.image = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
stateObject.company.icon = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
}
}
}
According to this mailing list post, UIGraphicsBeginImageContext isn't thread safe. The post hints that CGBitmapContextCreate and related functions are the safe way to do it.
http://osdir.com/ml/cocoa-dev/2009-10/msg00035.html
I think you're crashing because you're trying to access cells in the tableview that are not there.
Regardless of how long the logical table is, the visual tableview only holds enough cells to display the section of the logical table currently on screen. At the default row size, a table shows and therefore contains, only 9 to 10 cell objects. For example, if you have a logical table that is 100 rows long and your view is showing rows 11-20 the table only has 9 cell objects. If you show rows 89-98, it only has the exact same 9 cell objects. No matter which rows you display, you see the same 9 cell objects over and over again. The only thing that changes is the data they display.
If you try to access the cell for a logical row off screen, you won't get anything back. In your case, you try to access the 11th logical row but there is no 11th cell and there never will be.
I think you have some conceptual confusion because you're trying to store data in the tableview itself by setting the cell contents. This will not work because tableviews do not store any data beyond what is immediately displayed. When a tableview needs to display more rows as you scroll, it reuses the existing cell and its DataSource delegate changes the data that the existing cells display.
Instead of storing the images in the cells, you need to create a data model and store the images there. Then when the tableview scrolls, it will send tableview:cellForRowAtIndexPath: to its datasource delegate. The datasource delegate will then ask the data model for the data for the logical row, populate the reused cell and return it to the tableview.
Ok it seems it is solved thanks to codewarrior. This is the part that changed.
#synchronized(delegate)
{
UIImage *resizedImage;
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.theImage = resizedImage;
}