What is wrong with this attempt to create and populate an NSMutableArray? - iphone

In my .h file, I have:
#property (nonatomic) NSMutableArray *cards;
In my .m file, I have, in the initializer:
- (id) init
{
self = [super init];
self.cards = [NSMutableArray alloc];
return self;
}
In a loop that populates a number of items visible and on-screen:
[self.cards addObject:noteView];
And in a touch event handler, I have:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"In touchesBegan.");
UITouch *touch = [touches anyObject];
UIView *selectedView = nil;
CGPoint touchLocation = [touch locationInView:self.view];
for (UIView *card in _cards)
{
CGRect cardRect = [card frame];
NSLog(#"%f", cardRect.origin.x);
NSLog(#"%f", cardRect.origin.y);
NSLog(#"%f", cardRect.size.height);
NSLog(#"%f", cardRect.size.width);
if (CGRectContainsPoint(cardRect, touchLocation)) {
NSLog(#"Match found.");
selectedView = card;
CGRect selectedFrame = selectedView.frame;
selectedFrame.origin.y = -selectedFrame.size.height;
selectedFrame.size = selectedView.frame.size;
float heightRatio = (float) floor([[UIScreen mainScreen] bounds].size.height + .4) / (float) selectedFrame.size.height;
float widthRatio = (float) floor([[UIScreen mainScreen] bounds].size.width + .4) / (float) selectedFrame.size.width;
float ratio = MIN(heightRatio, widthRatio);
selectedFrame.size.height *= ratio;
selectedFrame.size.width *= ratio;
selectedFrame.origin.x = -selectedFrame.origin.x * ratio;
}
}
}
The output for every touch I've made has been that the unconditional NSLog statement is output, but none of the "Log this float" statements execute. It seems that I have not correctly initialized or not correctly populated NSMutableArray.
I seem to get the same behavior whether I refer to _cards or self.cards.
Thanks for any help,
--EDIT--
I seem to be sticking on something else than originally thought of. I now have self.cards = [[NSMutableArray alloc] init], but identical behavior: I go through a loop and populate a bunch of cards, but when I tap one of them, the touch handler outputs "In touchesBegan." but none of the floats. Given an updated init, why would touchesBegan be acting as if it hadn't seen any cards after a number of cards are visible onscreen? (The other output should give a number of lines of floats whether or not the touch was on target for a particular card.)

You need to init the array!
self.cards = [[NSMutableArray alloc] init];
Or
self.cards = [NSMutableArray new];
They are both equivalents
With alloc the only thing you are doing is reserve space in the memory for that variable.
From Apple Documentation:
alloc
Returns a new instance of the receiving class.
init
Implemented by subclasses to initialize a new object (the receiver) immediately after memory for it has been allocated.

Are you using a storyboard for this? Try to initialize your cards property in viewDidLoad.
As a quick check, try to initialize that property right before the loop that is adding objects to it.

Related

NsMutableArray object value only same value

I want to make NSMutableArray to store the touch time gap, but came across a problem:
The NSMutableArray's count is only 1.
What can I do?
NSInteger count = 0;
float firstTempTime = 0;
float nTempTime = 0;
float nTouchTimegap = 0;
#pragma mark –
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
messageLabel.text = #"Touches Began";
[self updateLabelsFromTouches:touches];
NSArray *array = [touches allObjects];
for (UITouch *touch in array){
count = count +1;
NSLog(#"began touch count: %d", count);
nTempTime = [touch timestamp];
NSLog(#"n TempTime stamp : %lf", nTempTime);
if (count == 1) {
firstTempTime = [touch timestamp];
}
else {
nTouchTimegap = nTempTime - firstTempTime;
firstTempTime = nTempTime;
}
NSLog(#"nTouchTimegap : %lf", nTouchTimegap);
nTouchTimegapArray = [NSMutableArray arrayWithCapacity:count];
[nTouchTimegapArray addObject:[NSNumber numberWithFloat: nTouchTimegap]];
for(id obj in nTouchTimegapArray){
NSLog(#"nTouchTimegapArray : %i", [nTouchTimegapArray count]);
NSLog(#"nTouchTimegapArray : %#", obj);
}
}
touchesBegan: will be called once you touch the screen, if you touch the screen with a second finger it will be called again.
You should take a look at touchesEnded: and touchesCancelled:
I guess I have to spell it out:
nTouchTimegapArray = [NSMutableArray arrayWithCapacity:count];
[nTouchTimegapArray addObject:[NSNumber numberWithFloat: nTouchTimegap]];
That sequence effectively erases the array on each iteration. There's no possible way to ever have more than one entry in the array.
Move the first line out of the loop, and, if you want to accumulate touches from all invocations of the method, move it to the init routine or viewDidLoad or some such.

MKPolygon ontouch detailview

I've created a MKMapView with MKPolygons based on coordinates. There are multiple polygons on the map (look here for an example of what I am re-creating as an app).
What I am trying to do is when the user touches the polygon, it opens a popover view with information about the location. This information is currently stored inside a plist file with the coordinates.
What I currently have so far is that I am able to get touch event and print to the log that the polygon was touched.
The question that I have is:
Can MKPolygonView be used like an MKAnnotationView where once the user taps the pin more information pops up about that current location?
I want to do the same for the polygon view. When touched, the user would see more information about the location that is stored in the plist. If it is possible what would be the best way to get it to work?
My current code is below.
#import "outagemapViewController.h"
#import "MyAnnotation.h"
#import "WildcardGestureRecognizer.h"
#define METERS_PER_MILE 46309.344
#interface outagemapViewController ()
#end
#implementation outagemapViewController
- (void)viewDidLoad {
outages = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"outages"ofType:#"plist"]];
for (NSDictionary *coloredAreas in outages) {
coordinateData = coloredAreas[#"coords"];
test = coloredAreas[#"outages"];
NSLog(#"test %#", test);
coordsLen = [coordinateData count];
NSLog(#"coords %d", coordsLen);
CLLocationCoordinate2D coords[coordsLen];
for (i=0; i < coordsLen; i++) {
NSString *lat = coordinateData[i];
NSArray *latt = [lat componentsSeparatedByString:#","];
double latitude = [[latt objectAtIndex:0] doubleValue];
double longitude = [[latt objectAtIndex:1] doubleValue];
coords[i] = CLLocationCoordinate2DMake(latitude, longitude);
}
MKPolygon* poly2 = [MKPolygon polygonWithCoordinates:coords count:coordsLen];
poly2.title=#"test";
[self.mapView addOverlay:poly2];
}
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonView* aView = [[MKPolygonView alloc] initWithPolygon:(MKPolygon*)overlay];
int numbers = [test intValue];
if(numbers >= 10){
aView.fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.6];
aView.strokeColor = [[UIColor greenColor] colorWithAlphaComponent:1.0];
aView.lineWidth = 3;
}else if(numbers < 10){
aView.fillColor = [[UIColor yellowColor] colorWithAlphaComponent:0.6];
aView.strokeColor = [[UIColor yellowColor] colorWithAlphaComponent:1.0];
aView.lineWidth = 3;
}
return aView;
}
return nil;
}
}
-(void)viewWillAppear:(BOOL)animated{
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 35.20418;
zoomLocation.longitude = -89.86862;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);
[_mapView setRegion:viewRegion animated:YES];
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.mapView];
CLLocationCoordinate2D coord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in self.mapView.overlays)
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygon *poly = (MKPolygon*) overlay;
id view = [self.mapView viewForOverlay:poly];
if ([view isKindOfClass:[MKPolygonView class]])
{
MKPolygonView *polyView = (MKPolygonView*) view;
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon) {
// debug(#"hit!");
NSLog(#"hit");
} else {
NSLog(#"miss");
}
}
}
}
};
[self.mapView addGestureRecognizer:tapInterceptor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Unfortunately, for overlays, there's no built-in touch-detection and callout view like there is for annotations.
You'll have to do the touch-detection manually like you're already doing (and it looks like it should work).
(Even more unfortunate here is that adding a gesture recognizer directly to the overlay view doesn't work -- you have to add it to the whole map and then check whether the touch point is in any overlay.)
For an overlay callout view, once you've detected a touch on an overlay, you can create a custom UIView and do addSubview. I suggest adding it to the map instead of the overlay view and you might be able to use the CGPoint point you are already calculating to determine the frame of the custom callout view.
You might also want to keep a ivar/property reference to the overlay callout view so it can be easily removed and re-added if the user taps on another overlay while the callout for another overlay is already displayed.
Another option which is probably easier is to create a custom UIViewController and present or push it. The specifics of showing it depend on whether you're using a navigation controller and/or storyboard.
If your app is also built for iPad, you could also show the "callout" using a UIPopoverController.
See How do I display a UIPopoverView as a annotation to the map view? (iPad) for a code example (it's with an annotation but you should be able to adapt it for the overlay).
Once you've identified which overlay was tapped, you need to display its associated data which is in your original data source (the outages array). Right now, overlays are created and added but have no reference back to the original data object (outage dictionary in outages array).
(Subclassing MKPolygon to add a custom property has issues and workarounds and creating a completely custom MKOverlay class introduces a lot of other additional work.)
For your current data source structure, a simple, quick (and somewhat crude) option is to set the overlay's title property to the index in the outages array of the outage object associated with the overlay. Since the title property is an NSString and the array index is an integer, we'll convert it to a string:
NSUInteger outageIndex = [outages indexOfObject:coloredAreas];
poly2.title = [NSString stringWithFormat:#"%d", outageIndex];
[self.mapView addOverlay:poly2];
In viewForOverlay, it looks like you're using test (which comes from an outage object) to determine the polygon's color. The value of the externally declared/set test variable will not necessarily be in sync with the overlay the delegate method is currently being called for (the map could call viewForOverlay multiple times for the same overlay and not necessarily in the order you add them). You have to retrieve the outage object based on some property of the overlay parameter. Since we are setting the overlay's title property to the outage's index:
//int numbers = [test intValue]; <-- remove this line
int outageIndex = [overlay.title intValue];
NSDictionary *outageDict = [outages objectAtIndex:outageIndex];
id outageNumbersObject = outageDict[#"outages"];
//replace id above with actual type
//can't tell from code in question whether it's NSString or NSNumber
int numbers = [outageNumbersObject intValue];
//use "numbers" to set polygon color...
Finally, when an overlay is tapped, you use the same method as in viewForOverlay to get the outage object:
if (mapCoordinateIsInPolygon) {
int outageIndex = [overlay.title intValue];
NSDictionary *outageDict = [outages objectAtIndex:outageIndex];
NSLog(#"hit, outageDict = %#", outageDict);
//show view with info from outageDict...
}

iPhone, Cocos2D - moving sprite left/right while touching screen

I'm new to Objective C and app development so please go easy on me!
I'm trying to make a basic game and need to move a sprite left or right continuously while the user's finger is on the screen - left side to go left, right to go right...
I'm trying to use update to repeat movements of a few pixels every 1/60th second. So far, this is what I have (and sorry about the formatting):
#import "GameplayLayer.h"
#implementation GameplayLayer
-(id)init {
self = [super init];
if (self != nil) {
CGSize screenSize = [CCDirector sharedDirector].winSize;
// enable touches
self.isTouchEnabled = YES;
blobSprite = [CCSprite spriteWithFile:#"blob.png"];
[blobSprite setPosition: CGPointMake(screenSize.width/2, screenSize.height*0.17f)];
ball = [CCSprite spriteWithFile:#"ball.png"];
[ball setPosition:CGPointMake(10, screenSize.height*0.75f)];
[self addChild:blobSprite];
[self addChild:ball];
[self schedule:#selector(update) interval:1.0f/60.0f];
}
return self;
}
-(void) update:(ccTime)dt{
if (_tapDownLeft == YES){
blobSprite.position.x==blobSprite.position.x-5;
}
if (_tapDownRight == YES){
blobSprite.position.x==blobSprite.position.x+5;
}
}
-(void) ccTouchesBegan:(UITouch*)touch withEvent: (UIEvent *)event{
CGPoint touchLocation = [touch locationInView:[touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
if (touchLocation.x > 400) {
if ((blobSprite.position.x+10)<460){
_tapDownRight = YES;
}
}
if (touchLocation.x < 200) {
if ((blobSprite.position.x-10>20)){
_tapDownLeft = YES;
}
}
else {
_tapDownLeft = NO;
_tapDownRight = NO;
}
}
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
_tapDownLeft = NO;
_tapDownRight = NO;
}
-(void) registerWithTouchDispatcher{
[[CCTouchDispatcher sharedDispatcher]addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
#end
Am I on the right lines with this? At the moment it's giving me 'expression result unused' in update. Could anyone tell me what I'm missing? Any help would be greatly appreciated.
Thanks,
Patrick
i see a few things here:
not certain your selector will call update : #selector(update:)
I would not rely on dt being either exactly 1/60th of a second, nor being constant. I would favor defining a speed constant (in points per second) and compute the deltaX in points based on the desired speed and dt, at each update cycle.
I dont see a 'registerWithTouchDispatcher' call (i try to place them in onEnter and onExit) methods.
Somewhere in there, make certain you remove your children (either in dealloc, or better in a local cleanup method (dont forget to invoke [super cleanup]).
Remove the argument in the update function

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.

Why do CALayer's move slower than UIView's?

I have a UIView subclass that moved around 'event's when the user touches them, using the following overridden method:
// In a custom UIView...
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint point = [[touches anyObject] locationInView:self];
UIView *eventView = [self.ringOfEvents hitTest:point withEvent:event];
for (EventView *e in self.events) {
if (e == eventView) {
event.position = point;
}
}
}
Why is it that when I make EventView a CALayer instead of UIView, the movement slows down to a creep? I can post that code too, but it is so similar that I don't think it is necessary.
I would think that abstracting to a lower level would speed up event handling, but I must be missing something.
By the way, either if *eventView is a subclass of UIView or CALayer, the position property is as follows:
- (void)setPosition:(CGPoint)pos {
self.layer.position = pos;
}
- (CGPoint)position {
return self.layer.position;
}
Not sure why I get a huge decrease in latency when using UIView as apposed to CALayer..
Most CALayer properties are changed with animation by default, so decrease in latency is probably caused by that.
You may want to disable animations when layer position is changed. Possible solutions are discussed for example in here and here
This is due to implicit animations.
I've implemented a category method which removes implicit animation for givven keys and can be used like this
[view.layer setNullAsActionForKeys:#[#"bounds", #"position"]];
Implementation
#implementation CALayer (Extensions)
- (void)setNullAsActionForKeys:(NSArray *)keys
{
NSMutableDictionary *dict = [self.actions mutableCopy];
if(dict == nil)
{
dict = [NSMutableDictionary dictionaryWithCapacity:[keys count]];
}
for(NSString *key in keys)
{
[dict setObject:[NSNull null] forKey:key];
}
self.actions = dict;
}
#end