I am making a simple game that requires that I draw multiple UIImages in many different places. My problem here is that I need to add the UIImages to an NSMutableArray, and access them later. Using NSStrings to represent the images (such as for paths) will not work here, so I need to know how to change the images for every UIImage in the array.
My code is as follows (at least, for accessing the NSMutableArray). The NSMutableArray is declared in my .h file, and is initialized in a different method.
for (int a = 0; a <= [theArray count]; a ++) {
// I make a UIImage, which I will draw later
UIImage *theImage = [theArray objectAtIndex:a];
// then I do the drawing
}
This works fine. My problem is that I cannot figure out how to change a particular object in the NSMutableArray. How can I do this?
By the way, I am adding the UIImages to the NSMutableArray with the following code.
+ (void) createCarWithImage:(NSString*)theImageName {
UIImage *anImage= [UIImage imageNamed:theImageName];
[theArray addObject:anImage];
}
I think you want to use this method:
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject
e.g.:
[theArray replaceObjectAtIndex:4 withObject:someOtherImage];
Just modify each UIImage in the NSMutableArray as is, you only have a pointer to the actual data. That is, regardless of what pointer you use to change the data, the data is changed for every pointer.
Additionally, you can also iterate over your UIImages with:
for (UIImage *img in theArray)
{
//send messages to img
}
Answer to Comment
You can modify the UIImage at index 4 by:
UIImage *image = [theArray objectAtIndex:4];
//send messages to image
Related
I am having an issue with NSMutableArray wiping its contents.
Consider my code: (int i; is in my file's .h as is NSMutableArray* newFileControllerArray)
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [[NSMutableArray alloc]init];
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"%lu",(unsigned long)elementsInArray);
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"THERE ARE %lu ELEMENTS IN THE ARRAY",(unsigned long)elementsInArray);
}
The first method called (other than awakeFromNib:) is newFileMenubar: This will add one element to the array. I can confirm that this works because 1 is printed in the console. However, once OKbutton is called and I print out the number of elements in my array it says that no elements are in the array. Why is that?
Am I missing something very obvious here? Why does my array reset itself?
EDIT:
The comments have gotten long and unwieldy so here is the code w/NSLogs and outputs:
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [NSMutableArray array];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
i++;
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
[[newFileWindowControllerArray objectAtIndex:i]close];
}
When the program launches, this is the output: self=0x100141480, array=0x100140f30
This should be coming from awakeFromNib:
The next method called is newFileMenubar:
The output from this is
self=0x1001ac990, array=0x1005228a0 and immediately after self=0x100141480, array=0x100140f30
The last method called is OKButtonClicked:
The output from the last method (OKButtonClicked:) is self=0x1001ac990, array=0x1005228a0
As you can see from the code, the name of the array doesn't change, but my outputs beg to differ? What could cause this?
There are good clues in your log output. There are multiple instances of the view controller (see the different values for 'self'?). They each have their own array. See this code...
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
When you press the button associated with that action, your app builds another view controller and places it in the array. That view controller gets the awake from nib message and allocates another array, and so on.
To confirm this, change the code as follows:
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:#"Hello world"];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
In the other methods, comment out your expectations that the array has anything other than strings in it, and see what you get. e.g. ...
- (IBAction)OKButtonClicked:(id)sender {
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i]close];
// instead...
NSLog(#"danh thinks my array will be ok: %#", newFileWindowControllerArray);
}
You probably do not mean to create another view controller on every button press, but I'm not sure what function you do want. Maybe you want an array of views? (To create many view controllers under the control of another, you'll want to read up on container view controllers, here).
I'm trying to understand Automatic Reference Counting, as I come from a high-level programming language (Python) and I'm working on a project which use this feature of Objective-C. I often get problems with ARC deallocating objects which I need later, but now I got a concrete example for which I hope I'll get an explanation.
- (void) animateGun:(UIImageView *)gun withFilmStrip:(UIImage *)filmstrip{
NSMutableArray *frames = [[NSMutableArray alloc] init];
NSInteger framesno = filmstrip.size.width / gun_width;
for (int x=0; x<framesno; x++){
CGImageRef cFrame = CGImageCreateWithImageInRect(filmstrip.CGImage, CGRectMake(x * gun_width, 0, gun_width, gun_height));
[frames addObject:[UIImage imageWithCGImage:cFrame]];
CGImageRelease(cFrame);
}
gun.image = [frames objectAtIndex:0];
gun.animationImages = frames;
gun.animationDuration = .8;
gun.animationRepeatCount = 1;
[gun startAnimating];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(arc4random() % 300)/100 * NSEC_PER_SEC), dispatch_get_current_queue(),^{
[self animateGun:leftGun withFilmStrip:[self getFilmStripForAction:gunShoot andTeam:nil withWeapon:nil]];
});
}
The idea behind this snippet of code is simple: I have a (UIImageView*)gun which I animate with the images stored in (NSMutableArray *)frames, at random times. (UIImage *)filmstrip is just an image which contains all the frames which will be used on animation. The first iteration of animation works, but the problems appears on the second iteration, where I get -[UIImage _isResizable]: message sent to deallocated instance ... or -[UIImage _contentStretchInPixels]: message sent to deallocated instance ... or -[NSArrayI release]: message sent to deallocated instance .... This happens at
gun.animationImages = frames;
but I don't understand why. I'm not requesting a fix for my issue, but just to help me understand what's happening here. Thanks.
ARC is a mechanism that removes the need to manually retain/release objects. Here's a nice site that explains how this works: http://longweekendmobile.com/2011/09/07/objc-automatic-reference-counting-in-xcode-explained/
Try changing "leftGun" for "gun". I think that's probably the one that gets deallocated at some point, if you're using it through an ivar. Otherwise, leftGun simply isn't in the scope.
Here's what it should look like:
In your .h file:
#property (nonatomic, strong) IBOutlet UIImageView *leftGun;
In your .m file:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(arc4random() % 300)/100 * NSEC_PER_SEC), dispatch_get_current_queue(),^{
[self animateGun:gun withFilmStrip:[self getFilmStripForAction:gunShoot andTeam:nil withWeapon:nil]];
});
Also, not quite sure where "gunShoot" is coming from. Is that supposed to be an enum?
EDIT
Added an example of how the leftGun property should be defined. The reason behind using a property over an ivar is for memory management purposes. If you want to release or destroy an object that is a property, simply set it to nil and the property will take care of releasing the object if it has to.
You may prevent the deallocation of the frames array if you mark it as __block.
__block NSMutableArray *frames = [NSMutableArray array];
see “The __block Storage Type.”
I'm working with an image heavy iOS app and I found myself typing almost the same line again and again:
...
A016.image = [UIImage imageNamed:img21];
A017.image = [UIImage imageNamed:img21];
A018.image = [UIImage imageNamed:img21];
...
Now I ask you: is there a way where I could store the UIImageViews names in an Array or something?
Only to beautify my very ugly code.
/John
Yes, you can put the UIImageViews in an array and put it in a for.
UIImageView * imageView1;
UIImageView * imageView2;
UIImageView * imageView3;
NSArray * imageViewsArray = [NSArray arrayWithObjects:imageView1,imageView2,imageView3,nil];
for (UIImageView * currentImageView in imageViewsArray) {
currentImageView.image = [UIImage imageNamed:img21];
}
Of course you can simplify something like that. But it's not entirely clear what the context is exactly.
If you have all your image views in an array you could do something like this.
// Assume an NSArray called imageViews exists with all the UIImageView instances in it.
for (UIImageView *imageView in imageViews) {
imageView.image = [UIImage imageNamed:img21];
}
If your image views aren't in an array already and are actually called A016, A017 etc., then I'd recommend you change your code design. Something like this is never a good idea. It results in bad, difficult to maintain code. If this is some kind of table of images, try putting the image views in an array in the first place.
That being said, there are ways to put variables like that in an array. If they are declared as #properties or ivars you can could do something like this.
NSMutableArray *imageViews = [NSMutableArray arrayWithCapacity:20];
for (int i = 0; i < 20; i++) {
NSString *variableName = [NSString stringWithFormat:#"A0%d", i];
UIImageView *imageView = [self valueForKey:variableName];
[imageViews addObject:imageView];
}
After that you can save the array as an ivar and do the same thing as above.
A more hardcoded version would be to simply put the image views into an array yourself:
NSArray *imageViews = [NSArray arrayWithObjects:..., A016, A017, A018,..., nil];
But I really recommend not using indexed variable names like that.
I am working on a game in OBJ C that has a ball view and a stage view. The stage view has 4 subviews. All views are UIImageViews. I have a method for collision detection that is working. I would like to expand it to more than 4 subviews without simply creating more lines of code. Looking at the code below, is there a way to simplify this into loops instead. Thanks!
// convert each square to be relevant to ball's superview in order to collision detect
CGRect square_01Frame = [ball.superview convertRect:square_01.frame fromView:square_01.superview];
CGRect square_02Frame = [ball.superview convertRect:square_02.frame fromView:square_02.superview];
CGRect square_03Frame = [ball.superview convertRect:square_03.frame fromView:square_03.superview];
CGRect square_04Frame = [ball.superview convertRect:square_04.frame fromView:square_04.superview];
// convert CGRects to NSStrings for storage in square_frames array
NSString *square_01FrameString = NSStringFromCGRect(square_01Frame);
NSString *square_02FrameString = NSStringFromCGRect(square_02Frame);
NSString *square_03FrameString = NSStringFromCGRect(square_03Frame);
NSString *square_04FrameString = NSStringFromCGRect(square_04Frame);
// load array of NSStrings
[square_frames replaceObjectAtIndex:0 withObject:square_01FrameString];
[square_frames replaceObjectAtIndex:1 withObject:square_02FrameString];
[square_frames replaceObjectAtIndex:2 withObject:square_03FrameString];
[square_frames replaceObjectAtIndex:3 withObject:square_04FrameString];
// create a for loop
for (int i=0; i<4; i++) { // 4 squares
// create test frame
CGRect test_frame = CGRectFromString([square_frames objectAtIndex:i]);
if (CGRectIntersectsRect(test_frame,ball.frame)) { // collision detection
// do something
}
}
Well, I would do a number of things.
First, I would create a ball "model", just an NSObject subclass to represent the Ball. Probably, that would have a property "location" or something, which is the CGRect.
Then, your current view could have an array of ball objects on the screen, and just loop through them.
Overall, though, I don't think using UIView's rects is the best way to manage collision detection. I think you'd be better off defining that in some other way, and then simply updating the UI accordingly.
Generally, it's not a good idea to rely on your UI implementation for game design. It makes it hard to change (as you note in your question).
I'm getting some leaks (obvserved by Instruments) when trying to reuse an existing NSMutableArray (in order to save memory).
Basically I'm creating an NSMutableArray, filling it with objects (UIImages) and passing it onto another object which retains it. However, I now need to use an NSMutableArray again. I figured I would release all its objects, empty it, and everything would be fine, but Instruments reports a CALayer leaked object (??) from that very method which looks something as follows:
NSString *fileName;
NSMutableArray *arrayOfImages = [[NSMutableArray alloc] init];
// fill the array with images
for(int i = 0; i <= 7; i++) {
fileName = [NSString stringWithFormat:#"myImage_%d.png", i];
[arrayOfImages addObject:[UIImage imageNamed:fileName]];
}
// create a button with the array
aButton = [[CustomButtonClass buttonWithType:UIButtonTypeCustom]
initWithFrame:someFrame
imageArray:arrayOfImages];
// release its objects
for(int i = 0; i < [arrayOfImages count]; i++) {
[[arrayOfImages objectAtIndex:i] release];
}
// empty array
[arrayOfImages removeAllObjects];
// fill it with other images
for(int i = 0; i <= 7; i++) {
fileName = [NSString stringWithFormat:#"myOtherImage_%d.png", i];
[arrayOfImages addObject:[UIImage imageNamed:fileName]];
}
// create another button with other images (same array)
aSecondButton = [[CustomButtonClass buttonWithType:UIButtonTypeCustom]
initWithFrame:someFrame
imageArray:arrayOfImages];
[arrayOfImages release];
For the sake of clarity, my button init method looks as follows:
- (id)initWithFrame:(CGRect)frame
images:(NSArray *)imageArray
{
if(self = [super initWithFrame:frame]) {
myImageArray = [[NSArray arrayWithArray:imageArray] retain];
}
return self;
}
I know I could just create a new NSMutableArray and be over with this issue but it annoys me not to be able to just reuse the old array. What could be the problem?
I'm getting some leaks (obvserved by
Instruments) when trying to reuse an
existing NSMutableArray (in order to
save memory).
An array takes a really small amount of memory; 4 bytes per pointer stored (on a 32 bit system) + a tiny bit of overhead. Reusing an array to attempt to save memory is a waste of time in all but the most extraordinary circumstances.
// release its objects
for(int i = 0; i < [arrayOfImages count]; i++) {
[[arrayOfImages objectAtIndex:i] release];
}
// empty array
[arrayOfImages removeAllObjects];
You didn't retain the objects and, thus, you shouldn't be releasing them! That your app didn't crash after the above indicates that you are likely over-retaining the objects somewhere else.
I know I could just create a new
NSMutableArray and be over with this
issue but it annoys me not to be able
to just reuse the old array. What
could be the problem?
There isn't anything in that code that springs out as a memory leak. Just the opposite; you are over-releasing objects.
And the above indicates that you really need to revisit the memory management guidelines as re-using an array versus releasing the array and creating a new one really doesn't have anything to do with this problem.
You don't need this part:
// release its objects
for(int i = 0; i < [arrayOfImages count]; i++) {
[[arrayOfImages objectAtIndex:i] release];
}
This is against the ownership rule. You didn't retain the images at
[arrayOfImages addObject:[UIImage imageNamed:fileName]];
so it's not your responsibility to release them. It's NSMutableArray that retains them when -addObject is called, and so it's NSMutableArray's responsibility to release them when -removeObject or others of that ilk is called. The leak you found might be because the system got confused by this over-releasing...
I would also recommend to perform "Build and Analyze" in XCode.
The Leaks instrument tells you where the object that leaked was first allocated. The fact one of the images was leaked means that you used the image somewhere else, and did not release it there - Leaks cannot tell you where since it cannot show you code that does not exist.
Indeed as others have pointed out this is somewhat surprising since the code as-is is over-releasing objects and should have crashed. But the fact it did not crash is a good sign somewhere else you are using the images from the array and over-retaining them.