Cocos2d Chipmunk: Touch Shapes? - iphone

Today i got a cocos2d question!
I got a few shapes and the space-manager set up in my .m file by using nodes:
- (CCNode*) createBlockAt:(cpVect)pt
width:(int)w
height:(int)h
mass:(int)mass
{
cpShape *shape = [smgr addRectAt:pt mass:mass width:w height:h rotation:0];
cpShapeNode *node = [cpShapeNode nodeWithShape:shape];
node.color = ccc3(56+rand()%200, 56+rand()%200, 56+rand()%200);
[self addChild:node];
return node;
}
- (CCNode*) createCircleAt:(cpVect)pt
mass:(int)mass
radius:(int)radius
{
cpShape *shape = [smgr addCircleAt:pt mass:mass radius:radius];
cpShapeNode *node1 = [cpShapeNode nodeWithShape:shape];
CCSprite *sprt = [CCSprite spriteWithFile:#"fire.png"];
node1.color = ccc3(56+rand()%200, 56+rand()%200, 56+rand()%200);
[self addChild:node1];
return node1;
}
then i actually make the shapes, set up a background, alloc and start the space-manager in my init method:
- (id) init
{
[super init];
CCSprite *background = [CCSprite spriteWithFile:#"BGP.png"];
background.position = ccp(240,160);
[self addChild:background];
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:NO];
//allocate our space manager
smgr = [[SpaceManagerCocos2d alloc] init];
smgr.constantDt = 1/55.0;
[smgr addWindowContainmentWithFriction:1.0 elasticity:1.0 inset:cpvzero];
[self createBlockAt:cpv(160,50) width:50 height:100 mass:100];
[self createBlockAt:cpv(320,50) width:50 height:100 mass:100];
[self createBlockAt:cpv(240,110) width:210 height:20 mass:100];
[self createCircleAt:cpv(240,140) mass:25 radius:20];
[smgr start];
return self;
}
Now, i want a shape to be removed if it's touched. What is the best way to do that???
I hope that somebody out there can help me :)
Edit:
Plz somebody, start a bounty! Or i never get this answered :(
-DD

How about like this?
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint point = [self convertTouchToNodeSpace:touch];
cpShape *shape = [smgr getShapeAt:point];
if (shape) {
[self removeChild:shape->data cleanup:YES];
[smgr removeAndFreeShape:shape];
}
return YES;
}

Related

How to add a magnifier to custom control?

How to add a magnifier to custom control? Control is a child of UIView. (It's a UIWebView - but native magnification functionality doesn't work at some pages.)
UPDATED:
Maybe it's possible to force a draw of magnifier on UIWebView?
1. Add the following files to your project:
MagnifierView.h:
//
// MagnifierView.h
// SimplerMaskTest
//
#import <UIKit/UIKit.h>
#interface MagnifierView : UIView {
UIView *viewToMagnify;
CGPoint touchPoint;
}
#property (nonatomic, retain) UIView *viewToMagnify;
#property (assign) CGPoint touchPoint;
#end
MagnifierView.m:
//
// MagnifierView.m
// SimplerMaskTest
//
#import "MagnifierView.h"
#import <QuartzCore/QuartzCore.h>
#implementation MagnifierView
#synthesize viewToMagnify;
#dynamic touchPoint;
- (id)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame radius:118];
}
- (id)initWithFrame:(CGRect)frame radius:(int)r {
int radius = r;
if ((self = [super initWithFrame:CGRectMake(0, 0, radius, radius)])) {
//Make the layer circular.
self.layer.cornerRadius = radius / 2;
self.layer.masksToBounds = YES;
}
return self;
}
- (void)setTouchPoint:(CGPoint)pt {
touchPoint = pt;
// whenever touchPoint is set, update the position of the magnifier (to just above what's being magnified)
self.center = CGPointMake(pt.x, pt.y-66);
}
- (CGPoint)getTouchPoint {
return touchPoint;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect bounds = self.bounds;
CGImageRef mask = [UIImage imageNamed: #"loupe-mask#2x.png"].CGImage;
UIImage *glass = [UIImage imageNamed: #"loupe-hi#2x.png"];
CGContextSaveGState(context);
CGContextClipToMask(context, bounds, mask);
CGContextFillRect(context, bounds);
CGContextScaleCTM(context, 1.2, 1.2);
//draw your subject view here
CGContextTranslateCTM(context,1*(self.frame.size.width*0.5),1*(self.frame.size.height*0.5));
//CGContextScaleCTM(context, 1.5, 1.5);
CGContextTranslateCTM(context,-1*(touchPoint.x),-1*(touchPoint.y));
[self.viewToMagnify.layer renderInContext:context];
CGContextRestoreGState(context);
[glass drawInRect: bounds];
}
- (void)dealloc {
[viewToMagnify release];
[super dealloc];
}
#end
TouchReader.h:
//
// TouchReader.h
// SimplerMaskTest
//
#import <UIKit/UIKit.h>
#import "MagnifierView.h"
#interface TouchReader : UIView {
NSTimer *touchTimer;
MagnifierView *loop;
}
#property (nonatomic, retain) NSTimer *touchTimer;
- (void)addLoop;
- (void)handleAction:(id)timerObj;
#end
TouchReader.m:
//
// TouchReader.m
// SimplerMaskTest
//
#import "TouchReader.h"
#import "MagnifierView.h"
#implementation TouchReader
#synthesize touchTimer;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.touchTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(addLoop) userInfo:nil repeats:NO];
// just create one loop and re-use it.
if (loop == nil) {
loop = [[MagnifierView alloc] init];
loop.viewToMagnify = self;
}
UITouch *touch = [touches anyObject];
loop.touchPoint = [touch locationInView:self];
[loop setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleAction:touches];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.touchTimer invalidate];
self.touchTimer = nil;
[loop removeFromSuperview];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.touchTimer invalidate];
self.touchTimer = nil;
[loop removeFromSuperview];
}
- (void)addLoop {
// add the loop to the superview. if we add it to the view it magnifies, it'll magnify itself!
[self.superview addSubview:loop];
// here, we could do some nice animation instead of just adding the subview...
}
- (void)handleAction:(id)timerObj {
NSSet *touches = timerObj;
UITouch *touch = [touches anyObject];
loop.touchPoint = [touch locationInView:self];
[loop setNeedsDisplay];
}
- (void)dealloc {
[loop release];
loop = nil;
[super dealloc];
}
#end
Based on: http://coffeeshopped.com/2010/03/a-simpler-magnifying-glass-loupe-view-for-the-iphone
2. Add the following images:
Used images on the code:
loupe-hi#2x.png:
loupe-mask#2x.png:
Original but centered images with a shadow (not used at this moment):
loupe-shadow-hi#2x.png:
loupe-shadow-mask#2x.png:
3. Replace the main UIView on your xib-file by TouchReader
The magnifier will work automaticaly except controls that captures touch events themselfs (for example, UIWebView). And the code above doesn't support the images with a shadow. Please add new answer to the qustion if you successfully fix this issue.
UPDATED:
Change the following code to add UIWebView support. UIView should remain UIView.
#interface TouchReader : UILongPressGestureRecognizer
And add a gesture to webView:
TouchReader* gestureMagnifier = [[[TouchReader alloc] initWithTarget:self action:#selector(handleMagnifier:)] autorelease];
gestureMagnifier.webView = editSource;
gestureMagnifier.delegate = self;
gestureMagnifier.minimumPressDuration = 0.5;
[webView addGestureRecognizer:gestureMagnifier];
TouchReader.h:
//- (void)handleAction:(id)timerObj;
-(void) handleGestureAction:(CGPoint)location;
TouchReader.m:
-(void)awakeFromNib
{
UILongPressGestureRecognizer * longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self addGestureRecognizer:longPressGesture];
}
-(void)handleGesture:(UILongPressGestureRecognizer *)longPressGesture
{
CGPoint location = [longPressGesture locationInView:self];
switch (longPressGesture.state) {
case UIGestureRecognizerStateBegan:
self.touchTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(addLoop)
userInfo:nil
repeats:NO];
// just create one loop and re-use it.
if(loop == nil){
loop = [[MagnifierView alloc] init];
loop.viewToMagnify = self;
}
loop.touchPoint = location;
[loop setNeedsDisplay];
break;
case UIGestureRecognizerStateChanged:
[self handleGestureAction:location];
break;
case UIGestureRecognizerStateEnded:
[self.touchTimer invalidate];
self.touchTimer = nil;
[loop removeFromSuperview];
loop=nil;
break;
default:
break;
}
}
- (void)addLoop {
// add the loop to the superview. if we add it to the view it magnifies, it'll magnify itself!
[self.superview addSubview:loop];
}
-(void) handleGestureAction:(CGPoint)location
{
loop.touchPoint = location;
[loop setNeedsDisplay];
}
You don't need the touches... methods any more.

How to smoothen the Parallax Scrolling with Swipe Gesture

I have add the Gestures Classes available from the example to support the Gesture rcognition.
I have 4 Sprites in the Top Layer to be moved at the Swipe Left/Right and have 3 Sprites to Swipe at the Bottom Layer.
I have the following code
-(id) init
{
// Some Stuff
[Gestures sharedGestures].swipeTolerance = 40;
[Gestures sharedGestures].pointResetLimit = 10;
[Gestures sharedGestures].delegate = self;
[Gestures sharedGestures].useX = NO;
[Gestures sharedGestures].useCircle = NO;
[Gestures sharedGestures].useSquare = NO;
for (int i=1; i<6; i++)
{
NSString *str=[NSString stringWithFormat:#"Howtoplay_0%d.png",i];
topLayer[i]=[CCSprite spriteWithFile:str];
[topLayer[i] setPosition:ccp(640/2,480/2)];
[self addChild:topLayer[i] z:1];
[topLayer[i] setVisible:NO];
}
[topLayer[1] setVisible:YES];
[topLayer[1] setPosition:ccp(320/2,480/2)];
for (int i=1; i<4; i++)
{
NSString *str=[NSString stringWithFormat:#"HowtoplayB_0%d.png",i];
backLayer[i]=[CCSprite spriteWithFile:str];
[backLayer[i] setPosition:ccp((320*i)/2,480/2)];
[self addChild:backLayer[i] z:-1];
}
[backLayer[1] setPosition:ccp(320/2,480/2)];
}
-(void) registerWithTouchDispatcher{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:1 swallowsTouches:YES];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
[[Gestures sharedGestures] reset];
return TRUE;
}
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint point = [touch locationInView: [touch view]];
CGPoint converted = [[CCDirector sharedDirector] convertToGL:point];
[[Gestures sharedGestures] addPoint:converted];
}
-(void) swipeLeftComplete
{
NSLog(#"Swipe Left Complete Called");
[self MoveLeft];
}
-(void) swipeRightComplete
{
[self MoveRight];
}
-(void)MoveLeft
{
[topLayer[currentTop+1] setPosition:ccp(640/2,480/2)];
[topLayer[currentTop+1] setVisible:YES];
[topLayer[currentTop] runAction:[CCMoveTo actionWithDuration:2 position:ccp(-320/2,480/2)]];
[topLayer[currentTop+1] runAction:[CCMoveTo actionWithDuration:2 position:ccp(320/2,480/2)]];
[topLayer[currentTop-1] setVisible:NO];
currentTop+=1;
}
-(void) MoveRight
{
[topLayer[currentTop-1] setVisible:YES];
[topLayer[currentTop] setVisible:YES];
[topLayer[currentTop] runAction:[CCMoveTo actionWithDuration:2 position:ccp(-320/2,480/2)]];
[topLayer[currentTop-1] runAction:[CCMoveTo actionWithDuration:2 position:ccp(320/2,480/2)]];
currentTop--;
}
I am unable to Swipe Right/Left Smoothly it gives the overlapping of the topLayer. Please Help me to smoothen the layer movement.
Any kind of Help will be Appreciated
Thanks...
Your issue sounds similar to problems I, and others, have faced with tiling sprites and borders between them. Try adding this to the top of your init statement:
[[CCDirector sharedDirector] setProjection:CCDirectorProjection2D];

objective c pass block as parameter with OOP

i make a class called "Tile" which is a square, and the passed block will get called when touched.
-(id) initWithRect: (CGRect) r color: (ccColor4B) c block: (void (^) (void)) blk {
if ((self = [super init])) {
rect = r;
color = c;
block = blk;
tile = [CCLayerColor layerWithColor:color width:rect.size.width height:rect.size.height];
tile.position = ccp(rect.origin.x, rect.origin.y);
[self addChild: tile];
self.isTouchEnabled = YES;
}
return self;
}
//rect is the square, i use CCLayerColor to represent the square.
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocation = [Helper locationFromTouch: touch];
if (CGRectContainsPoint(rect, touchLocation)) {
block();
[tile setColor:ccGRAY];
return YES;
}
else {
return NO;
}
}
//when touched, just call the block.
then i make a couple of Tiles as follows:
Tile* aTile = [Tile tileWithMidPos:ccp(512, 500) width:300 height:200 color:ccc4(250, 250, 250, 250) block:^{
[Helper playEffectButtonClicked];
}];
but all the tiles actually execute the block that is passed by the last tile.
what is the problem here? (every tile is an object, so they should call their own block)
Blocks are allocated on the stack.
In this case the Tile class should make a copy of the blk argument:
-(id) initWithRect: (CGRect) r color: (ccColor4B) c block: (void (^) (void)) blk {
if ((self = [super init])) {
rect = r;
color = c;
block = [blk copy];
tile = [CCLayerColor layerWithColor:color width:rect.size.width height:rect.size.height];
tile.position = ccp(rect.origin.x, rect.origin.y);
[self addChild: tile];
self.isTouchEnabled = YES;
}
return self;
}
- (void)dealloc {
[block release];
[super dealloc];
}
If you are using ARC, you won't need to worry about memory management (copy and release) if they are passed to methods with block parameters. If you pass stack allocated blocks to objects with id parameters, you must still copy:
[myArray addObject:[^{ // some block } copy]];
Mike Ash has an article well worth reading regarding blocks and ARC.

Why is it that my CGRects are not properly registering when touched?

Here is a rather large chunk of code. The comments should be pretty helpful. What I'm trying to do is iterate through an NSArray of CGRects (wrapped in NSValues) and check if the place we're currently touching is inside one of those CGRects.
When I run this code however (notice the NSLog statement in the for loop), all it ever does is say that the current loop value is 0. Furthermore, it does this when I touch places that are nowhere near where the CGRects should be. My comments explain how I created the CGRects. Thanks!
#import "GameUI.h"
#implementation GameUI // This is a scene with just 1 layer on it.
+(id) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
GameUI *layer = [GameUI node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// initialize your instance here
-(id) init
{
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
// enable touches
self.isTouchEnabled = YES;
// unlock touching ingredients to start with
touchLocked = NO;
// attach nothing
attachedSprite = -1;
winSize = [CCDirector sharedDirector].winSize;
// Build the UI
// Counter
CCSprite *counter = [CCSprite spriteWithFile:#"counter.png"
rect:CGRectMake(0, 0, 480, 320)];
counter.position = ccp(winSize.width/2, winSize.height/2);
[self addChild:counter];
// Let's hardcode some hitbox bounds.
// I got these values by using Photoshop's info panel and simply using the rectangular marquee tool.
// That gave me the origin point and then the two width+height values.
breadTouchbox = CGRectMake(241, 12, 71, 61);
onionTouchbox = CGRectMake(166, 11, 63, 53);
hamTouchbox = CGRectMake(115, 60, 56, 58);
cheeseTouchbox = CGRectMake(108, 128, 74, 74);
tomatoTouchbox = CGRectMake(47, 162, 52, 47);
lettuceTouchbox = CGRectMake(4, 78, 70, 77);
ketchupTouchbox = CGRectMake(2, 166, 38, 69);
mayoTouchbox = CGRectMake(6, 242, 31, 75);
NSValue *breadTouchboxValue = [NSValue valueWithCGRect:breadTouchbox];
NSValue *onionTouchboxValue = [NSValue valueWithCGRect:onionTouchbox];
NSValue *hamTouchboxValue = [NSValue valueWithCGRect:hamTouchbox];
NSValue *cheeseTouchboxValue = [NSValue valueWithCGRect:cheeseTouchbox];
NSValue *tomatoTouchboxValue = [NSValue valueWithCGRect:tomatoTouchbox];
NSValue *lettuceTouchboxValue = [NSValue valueWithCGRect:lettuceTouchbox];
NSValue *ketchupTouchboxValue = [NSValue valueWithCGRect:ketchupTouchbox];
NSValue *mayoTouchboxValue = [NSValue valueWithCGRect:mayoTouchbox];
// Put them in the array
touchboxArray = [[NSArray alloc] initWithObjects:breadTouchboxValue, onionTouchboxValue, hamTouchboxValue, cheeseTouchboxValue, tomatoTouchboxValue, lettuceTouchboxValue, ketchupTouchboxValue, mayoTouchboxValue, nil];
// Create the sprites we're going to drag around. Spawn them off screen somewhere.
breadSprite = [CCSprite spriteWithFile:#"breadslice.png" rect:CGRectMake(0,0,-200,-200)];
onionSprite = [CCSprite spriteWithFile:#"onionslices.png" rect:CGRectMake(0,0,-200,-200)];
hamSprite = [CCSprite spriteWithFile:#"hamslice.png" rect:CGRectMake(0,0,-200,-200)];
cheeseSprite = [CCSprite spriteWithFile:#"cheeseslices.png" rect:CGRectMake(0,0,-200,-200)];
tomatoSprite = [CCSprite spriteWithFile:#"tomatoslices.png" rect:CGRectMake(0,0,-200,-200)];
lettuceSprite = [CCSprite spriteWithFile:#"lettuceslice.png" rect:CGRectMake(0,0,-200,-200)];
ketchupSprite = [CCSprite spriteWithFile:#"ketchupsplurge.png" rect:CGRectMake(0,0,-200,-200)];
mayoSprite = [CCSprite spriteWithFile:#"mayosplurge.png" rect:CGRectMake(0,0,-200,-200)];
// Make sure you add them to this layer.
[self addChild:breadSprite];
[self addChild:onionSprite];
[self addChild:hamSprite];
[self addChild:cheeseSprite];
[self addChild:tomatoSprite];
[self addChild:lettuceSprite];
[self addChild:ketchupSprite];
[self addChild:mayoSprite];
// Put them in the array
spriteArray = [[NSArray alloc] initWithObjects:breadSprite, onionSprite, hamSprite, cheeseSprite, tomatoSprite, lettuceSprite, ketchupSprite, mayoSprite, nil];
}
return self;
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Set the currently touched location to LOCATION.
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
// NSLog(#"%#", NSStringFromCGPoint(location));
// Check for a touch in any of the touchboxes. If yes, break out of the loop so you preserve arrayCount's value.
int arrayCount = 0;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < [touchboxArray count]; i++) {
CGRect rect = [[touchboxArray objectAtIndex:i] CGRectValue];
if (CGRectContainsPoint(rect, location)) {
NSLog(#"The value of arrayCount is %i", arrayCount);
break;
}
}
[pool release];
// Now use that value to find the appropriate sprite and set it up for finger attachment.
// Remember to lock further touches [DO WE EVEN NEED TOUCHLOCKED? CAN'T SWIPE THROUGH SHIT ANYMORE]
//attachedSprite = arrayCount;
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// Set the currently touched location to LOCATION.
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
// Make a holder sprite.
CCSprite *currentIngredient;
// Check and see if there is a sprite to be attached. If there is, attach it to the touch location.
if (attachedSprite != -1) {
currentIngredient = [spriteArray objectAtIndex:attachedSprite];
currentIngredient.position = location;
}
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// Remove any attachment.
attachedSprite = -1;
}
-(void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
// Remove any attachment.
attachedSprite = -1;
}
#end
Answer provided in the comment by #deanWombourne
The row
NSLog(#"The value of arrayCount is %i", arrayCount);
should be
NSLog(#"The value of arrayCount is %i", i);

How to enable touchEvents (scroll and pan) in MKMapview below a custom UIView?

alt text http://www.gisnotes.com/wordpress/wp-content/uploads/2009/09/poly.png
In a nutshell, I am trying to figure out how to scale the geometry (point, line, and polygon) implemented in a custom view (geometryView) on top of MKMapView (mapView).
What I did was..
Create DrawMapViewController. Add the UIBarButtonItems (MAP, PT, LN, PG) on the bottom toolbar.
When you click on map button, you are able to pan/zoom on the Map. This enables the mapView by setting the geometryView.hidden = YES;
When any of the three geometry buttons is clicked, the geometryView is displayed by geometryView.hidden = NO, thus, enabling touchEvents and drawing the geometry from GeometryView.drawRect's method.
Layer ordering is as follows: mapView is at the bottom of geometryView.
-geometryView
-mapView
What is my problem?
Ideally, during "map" mode and when the user is panning and zooming, I am hoping if its possible to display the drawing from geometryView. But when the user hits "map", then geometryView.hidden = YES, thus the drawing disappears. If I make geometryView visible, then the user interacts with geometryView not mapView, so there's no zooming and panning.
Is it possible to handle touchEvents (pan/zoom) of MKMapView below a custom view while the custom View is displayed? Any other ideas/approaches is very much appreciated.
Thanks,
Rupert
GeometryView Listing:
#synthesize mapview, pinFactory;
- (id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
NSLog(#"DrawRect called");
CGContextRef context = UIGraphicsGetCurrentContext();
// Drawing lines with a white stroke color
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
if(pinFactory.geometryState == 2){ //Draw Line
if( [pinFactory actualPinCount] > 1){
Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
CGPoint pt1 = pin1.touchLocation;
CGContextMoveToPoint(context, pt1.x, pt1.y);
for (int i = 1; i < ([pinFactory actualPinCount]); i++)
{
Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:i];
CGPoint pt2 = pin2.touchLocation;
CGContextAddLineToPoint(context, pt2.x, pt2.y);
}
CGContextStrokePath(context);
}
}
else if(pinFactory.geometryState == 3){ //Draw Polygon
//if there are two points, draw a line first.
//if there are three points, fill the polygon
if( [pinFactory actualPinCount] == 2){
Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
CGPoint pt1 = pin1.touchLocation;
CGContextMoveToPoint(context, pt1.x, pt1.y);
Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:1];
CGPoint pt2 = pin2.touchLocation;
CGContextAddLineToPoint(context, pt2.x, pt2.y);
CGContextStrokePath(context);
}
else if([pinFactory actualPinCount] > 2){
//fill with a blue color
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
CGPoint pt1 = pin1.touchLocation;
CGContextMoveToPoint(context, pt1.x, pt1.y);
for (int i = 1; i < ([pinFactory actualPinCount]); i++)
{
Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:i];
CGPoint pt2 = pin2.touchLocation;
CGContextAddLineToPoint(context, pt2.x, pt2.y);
}
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self setNeedsDisplay];
UITouch* aTouch = [touches anyObject];
location = [aTouch locationInView:self];
NSLog(#"touchesBegan: x:%f, y:%f", location.x, location.y );
CLLocationCoordinate2D coordinate = [mapview convertPoint:location toCoordinateFromView:self];
switch (pinFactory.geometryState) {
case 1:{
if( [pinFactory actualPinCount] == 1){
//[UIView beginAnimations:#"stalk" context:nil];
//[UIView setAnimationDuration:1];
//[UIView setAnimationBeginsFromCurrentState:YES];
Pin *pin = (Pin *)[pinFactory getObjectAtIndex:0];
[mapview removeAnnotation:pin];
[pinFactory removeObject:pin];
Pin *newPin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:newPin];
[mapview addAnnotation:newPin];
[newPin release];
//[UIView commitAnimations];
}
else{
//Lets add a new pin to the geometry
Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:pin];
[mapview addAnnotation:pin];
[pin release];
}
break;
}
case 2:{
//Lets add a new pin
Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:pin];
[mapview addAnnotation:pin];
[pin release];
[self setNeedsDisplay];
break;
}
case 3:{
//Lets add a new pin
Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:pin];
[mapview addAnnotation:pin];
[pin release];
[self setNeedsDisplay];
break;
}
default:
break;
}
}
- (void)dealloc {
[super dealloc];
}
#end
DrawMapViewController Listing:
#import "DrawMapViewController.h"
#import "Pin.h"
#implementation DrawMapViewController
#synthesize mapview, mapBarButton, pointBarButton, lineBarButton, polygonBarButton, geometryView;
/* State represents state of the map
* 0 = map
* 1 = point
* 2 = line
* 3 = polygon
*/
// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
self.title = #"Map";
}
return self;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
mapview.mapType = MKMapTypeSatellite;
NSMutableArray *pinArray = [[NSMutableArray alloc] initWithObjects:nil];
pinFactory = [[PinFactory alloc] initWithArray:pinArray];
pinFactory.map = mapview;
[pinArray release];
geometryView = [[GeometryView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 372.0f)];
geometryView.pinFactory = pinFactory;
geometryView.mapview = mapview;
geometryView.backgroundColor = [UIColor clearColor];
[self.view addSubview:geometryView];
[self changeButtonAndViewState:0];
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (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)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[mapBarButton release];
[pointBarButton release];
[lineBarButton release];
[polygonBarButton release];
[super dealloc];
}
- (IBAction)mapBarButtonPressed{
NSLog(#"mapBarButtonPressed");
[self changeButtonAndViewState:0];
}
- (IBAction)pointBarButtonPressed{
NSLog(#"pointBarButtonPressed");
[self changeButtonAndViewState:1];
if( [pinFactory actualPinCount] > 0){
[self resetGeometry];
}
}
- (IBAction)lineBarButtonPressed{
NSLog(#"lineBarButtonPressed");
if( [pinFactory actualPinCount] > 0){
[self resetGeometry];
}
[self changeButtonAndViewState:2];
}
- (IBAction)polygonBarButtonPressed{
NSLog(#"polygonBarButtonPressed");
if( [pinFactory actualPinCount] > 0){
[self resetGeometry];
}
[self changeButtonAndViewState:3];
}
- (void)resetGeometry{
NSLog(#"resetting geometry.. deleting all pins");
[mapview removeAnnotations:[pinFactory pinArray]];
NSMutableArray *array = [pinFactory pinArray];
[array removeAllObjects];
[geometryView setNeedsDisplay];
}
- (void)changeButtonAndViewState:(int)s{
[pinFactory setGeometryState:s];
mapBarButton.style = UIBarButtonItemStyleBordered;
pointBarButton.style = UIBarButtonItemStyleBordered;
lineBarButton.style = UIBarButtonItemStyleBordered;
polygonBarButton.style = UIBarButtonItemStyleBordered;
pointBarButton.enabled = YES;
lineBarButton.enabled = YES;
polygonBarButton.enabled = YES;
switch ([pinFactory geometryState]) {
case 0:{
mapBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = YES;
break;
}
case 1:{
pointBarButton.enabled = NO;
pointBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = NO;
break;
}
case 2:{
lineBarButton.enabled = NO;
lineBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = NO;
break;
}
case 3:{
polygonBarButton.enabled = NO;
polygonBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = NO;
break;
}
default:
break;
}
}
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animate{
NSLog(#"regionDidChangeAnimated");
}
#end
Hey I see nobody has answered you, and I just figured out how to do this. Unfortunately you cannot intercept events and forward them to the mapView so something like
#interface MapOverlay
MKMapView* map;
#end
#implementation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Do my stuff
foo();
bar();
// Forward to the map
[map touchesBegan....];
}
#end
This is what you want to do but it WILL NOT WORK. For some reason you cannot intercept the map's events, nor can you do the reverse and subclass MKMapView and overload it's touch methods. The only way I have found to do this is as follows.
Create your MapOverlay view, set it to have a transparent background and disable touches to it. Overlay this on top of your MKMapView. Then do the following
Subclass UIWindow with a custom class that will forward all touches to your overlay, either handling the logic as "if the overlay is not hidden, then forward", or in the overlay itself keep state. Anyway it looks like this
#implementation CustomWindow
- (void)sendEvent:(UIEvent*)event
{
[super sendEvent:event];
// Forward the event to the overlay
}
When you are forwarding the event to the overlay, you first check if the touches are inside of your mapView region, then figure out what type of touches they are, then call the correct touch method on your overlay.
Good luck!
I know this answer probably comes too late, but I'll take a stab anyway for the benefit of those (such as myself) who've also come across this problem as well.
The MKMapView class's touch events are all handled by a UIScrollView internally. You can capture the events of this scroll view by making the MKMapView a subview of a custom UIView, and providing your custom touches methods in the custom view.
The trick is to keep track of the UIScrollView used by the MKMapView. To do this, I overrode the "hitTest" method, which returns "self", which I believe means this custom view should handle the touch events.
Also, the hitTest method gets the UIScrollView of the MKMapView. In my custom UIView I called it "echo_to" because the events are then echoed to the UIScrollView to make the map work as it does normally.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// Get the UIView (in this case, a UIScrollView) that
// would ordinarily handle the events
echo_to = [super hitTest:point withEvent:event];
// But let it be known we'll handle them instead.
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touches Began");
// add your custom annotation behavior here
[echo_to touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touches moved");
// add your custom annotation behavior here
[echo_to touchesMoved:touches withEvent:event];
}
Best of luck.
Depending on the sophistication of your geometryView, you may be able to use an annotation view to draw it.
The downside to this approach is that the view won't scale with the map (although you can use the mapView:regionDidChangeanimated: delegate method to redraw after a zoom), but it will pan with the map.
The hitTest trick works great as long as you don't need users to be able to tap on annotations, or pinch-and-zoom the map. Neither of these are able to be forwarded.