Access NSMutableArray created in viewDidLoad on button tap - iphone

In my app I have an animation that plays when the user taps a button. In the IBAction method for the button tap, I am having it create a NSMutable Array, load the images into the array, then cycle through the images.
This results in a fair amount of lag between the button tap and the animation playing, but every tap after that is fine since the array is already created with the images.
I have tried placing the array creation and image loading in the vieDidLoad method, but for some reason the IBAction method (where the call to cycle through the images is) cannot access the array. How would I make the array available to it?
- (IBAction)tap {
NSMutableArray *anim = [[NSMutableArray alloc]initWithObjects:[UIImage imageNamed:#"0001.png"], ...(x30)... nil];
type.animationImages = anim;
type.animationDuration = 1.0;
type.animationRepeatCount = 1;
[type startAnimating];
}

Define NSMutableArray *anim into your UIViewController .h file. It will make it available to global to access anywhere into your .m file.
- (void) viewDidLoad
{
anim = [[NSMutableArray alloc]initWithObjects:[UIImage imageNamed:#"0001.png"], ...(x30)... nil];
}
- (IBAction)tap
{
type.animationImages = anim;
type.animationDuration = 1.0;
type.animationRepeatCount = 1;
[type startAnimating];
}

Define NSMutableArray *anim in .h file ie class member variable
in viewDidLoad method defin like this:
anim = [[NSMutableArray alloc]initWithObjects:[UIImage imageNamed:#"0001.png"], ...(x30)... nil];
Use like this in button event:
- (IBAction)tap
{
type.animationImages = anim;
type.animationDuration = 1.0;
type.animationRepeatCount = 1;
[type startAnimating];
}

Try this simple code may be it help full for you.
Array allocate one time when you tap first time.
if(!anim)
{
NSMutableArray *anim = [[NSMutableArray alloc]initWithObjects:[UIImage imageNamed:#"0001.png"], ...(x30)... nil];
NSLog(#"array allocate");
}else
{
NSLog(#"array already allocated");
}

Related

Cocos2d NSMutable Array mutated while enumerated

I've been working on a game, and at a point I didn't have issues removing the enemy game objects (right now they are subclassed from CCSprite and I know that's not the best way)
But I'm not sure what I changed to make it crash when the program attempts to removeChild from _targets after they have been added to targetsToDelete.
I tried moving things around, I just don't know how I am adding or editing the array while its being created... Any help or advice would be great!
And actually if you had any pointers on how best to create game enemies, do you subclass NSObject or CCNode? I heard to divide them into component classes but I had no clue what they meant.
//Projectile Target collision
-(void)update:(ccTime)dt {
for (spygot *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
//Collision Detection Player
CGRect playerRect2 = CGRectMake(
_controlledSprite.position.x - (_controlledSprite.contentSize.width/2),
_controlledSprite.position.y - (_controlledSprite.contentSize.height/2),
_controlledSprite.contentSize.width,
_controlledSprite.contentSize.height);
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (Projectile *projectile in _projectiles)
{
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
BOOL monsterHit = FALSE;
if (CGRectIntersectsRect(projectileRect, targetRect))
{
NSLog(#"hit");
target.mhp = target.mhp - 1;
monsterHit = TRUE;
if (target.mhp <= 0)
{
[targetsToDelete addObject:target];
}
}
for (spygot *target in targetsToDelete)
{
[self removeChild:target cleanup:YES];
[_targets removeObject:target];
}
if (monsterHit)
{
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (Projectile *projectile in projectilesToDelete)
{
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
It looks like the code that you've pasted is all from within a for loop iterating over _targets. How does the variable target get initialized?
Usually when I get this sort of error it's because I have the code in a block or am in some other way on a nebulous thread. How sure are you that this bit of code is not running more than once at the same time?
You could try wrapping it in the following:
dispatch_async(dispatch_get_main_queue(), ^{
// do everything here.
});
As for advice about using CCSprite for your game enemy objects, my advice is fix it when it becomes a problem. Are you seeing issues with it right now? Premature optimization is almost as bad as doing it wrong in the first place. You'll know better at the end of the project how you should have done it earlier. ;)
I guess you know that you cannot remove elements from the array while you iterate over it. That is why you have targetsToDelete array.
But it looks to me that you do remove targets to soon.
Try this:
finish iterating the main loop and finish collecting targets to the targetsToDelete array and only after main loop is done remove the targets.

NSBlockOperation or NSOperation with ALAsset Block to display photo-library images using ALAsset URL

I am asking this question regarding my questions Display photolibrary images in an effectual way iPhone and Highly efficient UITableView "cellForRowIndexPath" method to bind the PhotoLibrary images.
So I would like to request that answers are not duplicated to this one without reading the below details :)
Let's come to the issue,
I have researched detailed about my above mentioned issue, and I have found the document about operation queues from here.
So I have created one sample application to display seven photo-library images using operation queues through ALAsset blocks.
Here are the sample application details.
Step 1:
In the NSOperationalQueueViewController viewDidLoad method, I have retrieved all the photo-gallery ALAsset URLs in to an array named urlArray.
Step 2:
After all the URLs are added to the urlArray, the if(group != nil) condition will be false in assetGroupEnumerator, so I have created a NSOperationQueue, and then created seven UIImageView's through a for loop and created my NSOperation subclass object with the corresponding image-view and URL for each one and added them in to the NSOperationQueue.
See my NSOperation subclass here.
See my implementation (VierwController) class here.
Let's come to the issue.
It not displaying all the seven images consistently. Some of the images are missing. The missing order is changing multiple times (one time it doesn't display the sixth and seventh, and another time it doesn't display only the second and third). The console log displays Could not find photo pic number. However, the URLs are logged properly.
You can see the log details here.
Are there any mistakes in my classes?
Also, when I go through the above mentioned operational queue documentation, I have read about NSBlockOperation. Do I need to implement NSBlockOperation instead of NSOperation while dealing with ALAsset blocks?
The NSBlockOperation description says
A class you use as-is to execute one or more block objects
concurrently. Because it can execute more than one block, a block
operation object operates using a group semantic; only when all of the
associated blocks have finished executing is the operation itself
considered finished.
How can I implement the NSBlockOperation with ALAsset block regarding my sample application?
I have gone through Stack Overflow question Learning NSBlockOperation. However, I didn't get any idea to implement the NSBlockOperation with ALAsset block!!
This is the tutorial about "How to access all images from iPhonePhoto Library using ALAsset Library and show them on UIScrollView like iPhoneSimulator" .
First of all add AssetsLibrary.framework to your project.
Then in your viewController.h file import #import <AssetsLibrary/AssetsLibrary.h> header file.
This is your viewController.h file
#import <UIKit/UIKit.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "AppDelegate.h"
#interface ViewController : UIViewController <UIScrollViewDelegate>
{
ALAssetsLibrary *assetsLibrary;
NSMutableArray *groups;
ALAssetsGroup *assetsGroup;
// I will show all images on `UIScrollView`
UIScrollView *myScrollView;
UIActivityIndicatorView *activityIndicator;
NSMutableArray *assetsArray;
// Will handle thumbnail of images
NSMutableArray *imageThumbnailArray;
// Will handle original images
NSMutableArray *imageOriginalArray;
UIButton *buttonImage;
}
-(void)displayImages;
-(void)loadScrollView;
#end
And this is your viewController.m file -
viewWillAppear:
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation ViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
assetsArray = [[NSMutableArray alloc]init];
imageThumbnailArray = [[NSMutableArray alloc]init];
imageOriginalArray = [[NSMutableArray alloc]init];
myScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0.0, 0.0, 320.0, 416.0)];
myScrollView.delegate = self;
myScrollView.contentSize = CGSizeMake(320.0, 416.0);
myScrollView.backgroundColor = [UIColor whiteColor];
activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = myScrollView.center;
[myScrollView addSubview:activityIndicator];
[self.view addSubview:myScrollView];
[activityIndicator startAnimating];
}
viewDidAppear:
-(void)viewDidAppear:(BOOL)animated
{
if (!assetsLibrary) {
assetsLibrary = [[ALAssetsLibrary alloc] init];
}
if (!groups) {
groups = [[NSMutableArray alloc] init];
}
else {
[groups removeAllObjects];
}
ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) {
//NSLog(#"group %#",group);
if (group) {
[groups addObject:group];
//NSLog(#"groups %#",groups);
} else {
//Call display Images method here.
[self displayImages];
}
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error) {
NSString *errorMessage = nil;
switch ([error code]) {
case ALAssetsLibraryAccessUserDeniedError:
case ALAssetsLibraryAccessGloballyDeniedError:
errorMessage = #"The user has declined access to it.";
break;
default:
errorMessage = #"Reason unknown.";
break;
}
};
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:listGroupBlock failureBlock:failureBlock];
}
And this is displayImages: method body
-(void)displayImages
{
// NSLog(#"groups %d",[groups count]);
for (int i = 0 ; i< [groups count]; i++) {
assetsGroup = [groups objectAtIndex:i];
if (!assetsArray) {
assetsArray = [[NSMutableArray alloc] init];
}
else {
[assetsArray removeAllObjects];
}
ALAssetsGroupEnumerationResultsBlock assetsEnumerationBlock = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[assetsArray addObject:result];
}
};
ALAssetsFilter *onlyPhotosFilter = [ALAssetsFilter allPhotos];
[assetsGroup setAssetsFilter:onlyPhotosFilter];
[assetsGroup enumerateAssetsUsingBlock:assetsEnumerationBlock];
}
//Seprate the thumbnail and original images
for(int i=0;i<[assetsArray count]; i++)
{
ALAsset *asset = [assetsArray objectAtIndex:i];
CGImageRef thumbnailImageRef = [asset thumbnail];
UIImage *thumbnail = [UIImage imageWithCGImage:thumbnailImageRef];
[imageThumbnailArray addObject:thumbnail];
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef originalImage = [representation fullResolutionImage];
UIImage *original = [UIImage imageWithCGImage:originalImage];
[imageOriginalArray addObject:original];
}
[self loadScrollView];
}
Now you have two array one is imageThumbnailArray and another is imageOriginalArray.
Use imageThumbnailArray for showing on UIScrollView for which your scrolling will not be slow.... And use imageOriginalArray for an enlarged preview of image.
'loadScrollView:' method, This is how to images on UIScrollView like iPhoneSimulator
#pragma mark - LoadImages on UIScrollView
-(void)loadScrollView
{
float horizontal = 8.0;
float vertical = 8.0;
for(int i=0; i<[imageThumbnailArray count]; i++)
{
if((i%4) == 0 && i!=0)
{
horizontal = 8.0;
vertical = vertical + 70.0 + 8.0;
}
buttonImage = [UIButton buttonWithType:UIButtonTypeCustom];
[buttonImage setFrame:CGRectMake(horizontal, vertical, 70.0, 70.0)];
[buttonImage setTag:i];
[ buttonImage setImage:[imageThumbnailArray objectAtIndex:i] forState:UIControlStateNormal];
[buttonImage addTarget:self action:#selector(buttonImagePressed:) forControlEvents:UIControlEventTouchUpInside];
[myScrollView addSubview:buttonImage];
horizontal = horizontal + 70.0 + 8.0;
}
[myScrollView setContentSize:CGSizeMake(320.0, vertical + 78.0)];
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
}
And here you can find which image button has been clicked -
#pragma mark - Button Pressed method
-(void)buttonImagePressed:(id)sender
{
NSLog(#"you have pressed : %d button",[sender tag]);
}
Hope this tutorial will help you and many users who search for the same.. Thank you!
You have a line in your DisplayImages NSOperation subclass where you update the UI (DisplayImages.m line 54):
self.imageView.image = topicImage;
This operation queue is running on a background thread, and we know that you should only update the state of the UI on the main thread. Since updating the view of an image view is definitely updating the UI, this can be simply fixed by wrapping the call with:
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = topicImage;
});
This puts an asynchronous call on the main queue to update the UIImageView with the image. It's asynchronous so your other tasks can be scheduled in the background, and it's safe as it is running on the main queue - which is the main thread.

NSMutableArray and batchNode problems

I'm making a little game, here is some example code of whats going on:
-(id) init
{
self.arrowProjectileArray = [[[NSMutableArray alloc] init] autorelease];
self.batchNode = [CCSpriteBatchNode batchNodeWithTexture:[[CCTextureCache sharedTextureCache] addImage:#"arrow.png"]];
[self addChild:_batchNode z:2];
for (CCSprite *projectile in _arrowProjectileArray) {
[_batchNode removeChild:projectile cleanup:YES];
}
[_arrowProjectileArray removeAllObjects];
self.nextProjectile = nil;
}
}
-(void) callEveryFrame:(ccTime)dt{
for (int i = 0; i < [_arrowProjectileArray count];i++) {
CCSprite *cursprite = [_arrowProjectileArray objectAtIndex:i];
if (cursprite.tag == 1) {
float x = theSpot.x+10;
float y = theSpot.y+10;
cursprite.position = ccp(x, y);
}
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_batchNode addChild:_nextProjectile z:1 tag:1];
[_arrowProjectileArray addObject: _nextProjectile];
[self spriteMoveFinished];
}
-(void) dealloc
{
self.arrowProjectileArray = nil;
self.nextProjectile = nil;
[super dealloc];
}
The only code that I included was code that is relevant to the arrow's projection.
The arrow shoots fine, the problem is every time I shoot the stupid thing, I think it shoots a new arrow, but puts multiple arrows onto of that 1 arrow and makes it look like a fat ugly arrow pixel thing. What am I doing wrong? I'm not too familiar with NSMutableArray, but I'm currently stuck.
In init method, you create a new NSMutableArray instance and assign it to self.arrowProjectileArray, then you traverse the arrowProjectileArray in the following lines using a for loop. If addChild: method does not add anything to arrowProjectileArray, then your code has a logic mistake, because what you do by traversing arrowProjectileArray is traversing an empty array, which means you do nothing in that code.
You should double-check what you intend to do and what your code is doing actually.
I solved my own problem by doing a little bit of research, I also got rid of the batch node.

Animating a button to look like a spinner on click

So basically I have a button. When it is clicked the first time I want it to display a spinner animation (kind of like the game twister, the spinner thing). I want this animation on the button itself. Preferably as the background. I have the other part of the button working correctly where when it is pressed again it stops pointing in a direction at random. Any ideas on how to get this working?
-(IBAction)spinnerButton:(id)sender{
NSString *display;
if (pressCount%2 == 0) {
NSString *spinning;
spinning = #"Press again to stop spinner.";
display = [[NSString alloc] initWithFormat:#"%#",spinning];
sender.animationImages = [NSArray arrayWithArray: imageArray];
sender.animationDuration = 1.0;
sender.animationRepeatCount = 0;
sender.startAnimating;
}
The imageArray is put together here.
- (void)viewDidLoad
{
[super viewDidLoad];
imageArray = [[NSMutableArray alloc] initWithCapacity:8];
for (int i = 0; i < 8; i++) {
[imageArray addObject:[UIImage imageNamed:[NSString stringWithFormat:#"piece%d.png", i]]];
}
}
My thought is that it needs to look something like this
[sender setBackgroundImage:[UIImage imageNamed:#"piece1.png"] forState:UIControlStateNormal];
but I'm not exactly sure how to place the array in it and make set the duration and all that stuffs. Thanks any help is welcomed and appreciated.
I got it to work fine. I placed an Image View over the top of the button. It still allowed me to click the button and it allowed me to tie the images and animation in correctly.

Hiding or moving SegmentContoller

Hello I've tried for 3 weeks to solve this issue and it stumps me. What i am trying to do is create a 3 part segment from an array, display it in a view in a certain position, then remove it from view when the "OFF" flag is set. Every thing works except the removal of the segment. It will even commuticate with (pickOne) and display the segment letters in a label. What i can't get to work is either of the two: setHidden:YES, or removeAllSegments. Any help would be appreciated. Here is my code.
- (void) showSegment {
int x = 192;
int y = 212;
int w = 125;
int h = 25;
SegUnit1 = #"A";
SegUnit2 = #"B";
SegUnit3 = #"C";
threeSegs = [NSArray arrayWithObjects: SegUnit1, SegUnit2, SegUnit3, nil];
segSize = [NSArray arrayWithArray:threeSegs];
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
if ([segmentState_height isEqualToString:#"ON"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
} else if ([segmentState_height isEqualToString:#"OFF"]) {
NSLog(#"segmentState_height = %#",segmentState_height);
[heightSC setHidden:YES]; // NSLog showing "OFF" but segment will not hide.
[heightSC removeAllSegments]; // NSLog showing "OFF" and segment is suppose to dismantle and does not.
}
}
I know now that i have to "not" create and remove in the same function, and was given a tip on correcting this but I don't know how to use the tip.
here is what was suggested.
Well, your method is a little confused, since you are trying to both create and hide at the same time. So you might consider splitting that up into separate methods.
In general, it will be along these lines:
Code:
if ([self theControlProperty] == nil)
{
UISeg... *theControl = [[UISeg alloc] ....];
[self setTheControlProperty:theControl];
...
}
if (shouldHideTheControl)
{
[[self theControlProperty] setHidden:YES];
}
Any help would be appreciated.
The problem you have is that you're creating a new UISegmentedControl instance every time that method is called. The first time through, you create an instance and add it as a subview to your view. This apparently works fine, as it should. Then the method returns, and you no longer have any easy way to refer to that instance that you created. When you re-enter -showSegment, you create a different instance, and then hide and/or destroy it. This different instance has no effect whatsoever on the instance that you gave to the view.
What you need to do is make heightSC an instance variable. Add it to the interface declaration in the header file, then initialize it only once, and hide or modify it as needed subsequently. The key point is that you need to have a reference to the instance of the UISegmentedControl which is being drawn on the screen, a reference that lives outside the method itself that you can use the second, third, fourth, etc time you call that method.
Try using the remove segments in your button choice method pickOne. This takes it outside the showSegment method and matches the users desired action to make the change and clear off the buttons.
- (void) pickOne:(id)sender {
UISegmentedControl* userChose = sender;
if( [userChose selectedSegmentIndex] == 0 ){
your first button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 1 ){
your second button operation;
[heightSC removeAllSegments];
}
if( [userChose selectedSegmentIndex] == 2 ){
your third button operation;
[heightSC removeAllSegments];
}
}
I tried this and got the results I was looking for. Thanks goes to Mythogen and BrianSlick I just need to check and make sure there are no leaks. Now that will be a task.
Does anyone know if I need the second [heightSC release]; ?
// .h
# interface ------ {
UISegmentedControl *segmentPicked;
}
|
#property (nonatomic, retain) UISegmentedControl *segmentPicked;
// .m
|
#synthesize segmentPicked;
|
if ([self segmentPicked] == nil) {
UISegmentedControl *heightSC = [[UISegmentedControl alloc] initWithItems:segSize];
[self setSegmentPicked:heightSC];
[heightSC release];
heightSC.frame = CGRectMake(x, y, w, h);
heightSC.segmentedControlStyle = UISegmentedControlStyleBar;
heightSC.selectedSegmentIndex = -1;
[heightSC addTarget:self
action:#selector(pickOne:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:heightSC];
[heightSC release];
}
if ([segmentState_height isEqualToString:#"OFF"])
{
[[self segmentPicked] setHidden:YES];
} else {
[[self segmentPicked] setHidden:NO];
}
[yourSegment removeFromSuperview];
?