Drawing a MKCircle on a map view - objective-c

I need to draw a circle to display the distance around a point that I have plotted.
Where should I implement these two lines of code to make it work? I tried putting it in viewWillAppear: but the circle does not appear.
[self addCircle:_coordinate];
[self addCircleWithRadius:5.5 addCircleWithCoordinate:_coordinate];
- (void)addCircle: (CLLocationCoordinate2D)coordinate
{
// draw the radius circle for the marker
double radius = 2000.0;
MKCircle *circle = [MKCircle circleWithCenterCoordinate:coordinate radius:radius];
[circle setTitle:#"background"];
[_mapView addOverlay:circle];
MKCircle *circleLine = [MKCircle circleWithCenterCoordinate:coordinate radius:radius];
[circleLine setTitle:#"line"];
[_mapView addOverlay:circleLine];
}
- (void)addCircleWithRadius:(double)radius addCircleWithCoordinate: (CLLocationCoordinate2D) coordinate
{
MKCircle *circle = [MKCircle circleWithCenterCoordinate:coordinate radius:radius];
[circle setTitle:#"background"];
[_mapView addOverlay:circle];
MKCircle *circleLine = [MKCircle circleWithCenterCoordinate:coordinate radius:radius];
[circleLine setTitle:#"line"];
[_mapView addOverlay:circleLine];
}
- (void)sliderChanged:(UISlider*)sender
{
[_mapView removeOverlays:[_mapView overlays]];
double radius = (sender.value * 100);
CLLocationCoordinate2D coordinate = self.coordinate;
[self addCircleWithRadius:radius addCircleWithCoordinate:coordinate];
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay{
MKCircle *circle = overlay;
MKCircleView *circleView = [[[MKCircleView alloc] initWithCircle:overlay] autorelease];
if ([circle.title isEqualToString:#"background"])
{
//circleView.fillColor = UIColorFromRGB(0x598DD3);
circleView.alpha = 0.25;
}
else
{
//circleView.strokeColor = UIColorFromRGB(0x5C8AC7);
circleView.lineWidth = 2.0;
}
return circleView;
}

While you could try viewDidAppear instead of viewWillAppear, I think it should already work in that regard. I think that you have something else wrong, and you should step through with a debugger to find it. Check the usual suspects:
Set the fill color. Make it opaque and obvious.
_mapView might be nil or zombied during runtime. (or not mapped in your xib)
The coordinates or radius may be different than you expect. Check the actual coordinate values in your debugger.
Everything may be correct except that the coordinates are not located in your map's zoomed area.

Related

Why does this code not properly center scene when doing UIPanGestureRecognizer?

I am a beginner developing a program for the AppStore using Xcode's sprite kit. I made a 'test' program so I can try out new things before adding it to my game. Right now I am fiddling around with swiping a scene - I have an SKNode (called "background") where I am adding several children as SKSpriteNodes. One sprite node is visible on the initial scene (the center, position 160,240), and two more that are not visible: to the left of the scene (position -160,240), and to the right of the scene (position 480,240).
I would like my game to be able to swipe left or right, and when it swipes left or right, the view will auto-center itself (with animation) to one of the three SKSpriteNodes. My code using the UIPanGestureRecognizer to move the background node works properly, and my code for auto-centering the view works MOSTLY (background position set to 0,0 or -320,0 or +320,0), but sometimes it has a strange offset and doesn't completely center itself (for example, the background position will be 7,0 or -34,0 when I pan right or left). What am I doing wrong?
P.S: I am using code from RayWenderlich's "iOS Games" for the SKTMoveEffect. I also want to note that if I make the function f(t)=t, there is no problem (at least in my several tests), but f(t)=t^2 or anything else seems to have an issue; if it helps to see the code for this I can post it too
#implementation LTMyScene
{
SKNode *background;
SKSpriteNode *spaceship1, *spaceship2;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
background=[SKNode node];
[self addChild:background];
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
myLabel.text = #"Hello, World!";
myLabel.fontSize = 30;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[background addChild:myLabel];
spaceship1=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship1.position=CGPointMake(-self.size.width/2, self.size.height/2);
spaceship1.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship1];
spaceship2=[SKSpriteNode spriteNodeWithImageNamed:#"Spaceship.png"];
spaceship2.position=CGPointMake(self.size.width*3/2, self.size.height/2);
spaceship2.anchorPoint=CGPointMake(0.5, 0.5);
[background addChild:spaceship2];
}
return self;
}
- (void)didMoveToView:(SKView *)view
{
UIPanGestureRecognizer *swipe = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragPlayer:)];
[[self view] addGestureRecognizer:swipe];
}
-(void)dragPlayer: (UIPanGestureRecognizer *)gesture {
[[[self view] layer] removeAllAnimations];
CGPoint trans = [gesture translationInView:self.view];
SKAction *moveAction = [SKAction moveByX:trans.x y:0 duration:0];
[background runAction:moveAction];
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
if([gesture state] == UIGestureRecognizerStateEnded) {
CGFloat finalX=0;
if (abs(background.position.x)<self.size.width/2) {
finalX=0;
} else if (abs(background.position.x)<self.size.width*3/2) {
finalX=self.size.width*background.position.x/abs(background.position.x);
}
NSLog(#"%f",finalX);
SKTMoveEffect *upEffect =
[SKTMoveEffect effectWithNode:background duration:0.5
startPosition:background.position
endPosition:CGPointMake(finalX, 0)];
upEffect.timingFunction = ^(float t) {
// return powf(2.0f, -3.0f * t) * fabsf(cosf(t * M_PI * 1.0f)) //has bounce
// return (-1.0f*t*t+1) //no bounce ... for parabola this is only solution with (1,0) and (0,1) as intercepts and vertex at (1,0)
return (t*t)
;};
SKAction *upAction = [SKAction actionWithEffect:upEffect];
[background runAction:upAction];
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
NSLog(#"%f,%f",background.position.x,background.position.y);
}
#end
You have to remember that you are moving the larger background node with other nodes as its children. Keeping that in mind, the code you need to center on a specific node is:
_worldNode.position = CGPointMake(-(myNode.position.x-(self.size.width/2)), -(myNode.position.y-(self.size.height/2)));
The above assumes that your main background "canvas" is called _worldNode and your target node is a child of _worldNode

Receive global TouchEvents with NSEvent

I just try to receive Touch Events globally in the Window and not only in a view.
In my code, you can see below, i will get the absolute position of the touch in the magic trackpad. As long as the cursor is inside the view (the red NSRect) it works fine, but how can i receive touches outside of this view.
I searched for solutions in many communities and the apple devcenter but found nothing.
I think the problem is this: NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil]; Isn't there a method in NSEvent that gets every touch?
Hope anybody can help me.
Here my Implementation:
#implementation MyView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
[self setAcceptsTouchEvents:YES];
myColor = [NSColor colorWithDeviceRed:1.0 green:0 blue:0 alpha:0.5];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
NSRect bounds = [self bounds];
[myColor set];
[NSBezierPath fillRect:bounds];
}
- (void)touchesBeganWithEvent:(NSEvent *)ev {
NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil];
for (NSTouch *touch in touches) {
NSPoint fraction = touch.normalizedPosition;
NSSize whole = touch.deviceSize;
NSPoint wholeInches = {whole.width / 72.0, whole.height / 72.0};
NSPoint pos = wholeInches;
pos.x *= fraction.x;
pos.y *= fraction.y;
NSLog(#"%s: Finger is touching %g inches right and %g inches up "
#"from lower left corner of trackpad.", __func__, pos.x, pos.y);
}
}
Calling touchesMatchingPhase:inView: with nil for the last parameter (the way you are doing) will get all touches. The problem is that touchesBeganWithEvent: will simply not fire for a control that isn't in the touch area.
You can make the view the first responder, which will send all events to it first. See becomeFirstResponder and Responder object

MKMapView Overlay (image animation)

I'm trying to animate a radar image on an MKMapView. I have separate images for different times, and I have successfully set an image as an overlay on top of the MKMapView. My problem is when I try to show these different images in a sequence one after each other. I tried a method of adding and removing overlays, but the system does not handle it well at all, with flickering of overlays, and some overlays not being removed, and overall, its not worth it.
Could anyone help me find a way to show multiple images (like an animated gif) on top of a MapView in a way that is smooth and fast?
Here's my code to overlay the image:
- (id)initWithImageData:(NSData*)imageData withLowerLeftCoordinate:(CLLocationCoordinate2D)lowerLeftCoordinate withUpperRightCoordinate:(CLLocationCoordinate2D)upperRightCoordinate {
self.radarData = imageData;
MKMapPoint lowerLeft = MKMapPointForCoordinate(lowerLeftCoordinate);
MKMapPoint upperRight = MKMapPointForCoordinate(upperRightCoordinate);
mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, lowerLeft.y - upperRight.y);
return self;
}
- (CLLocationCoordinate2D)coordinate
{
return MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMidX(mapRect), MKMapRectGetMidY(mapRect)));
}
- (MKMapRect)boundingMapRect
{
return mapRect;
}
and here's how I'm setting it up on the MapView:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)ctx
{
MapOverlay *mapOverlay = (MapOverlay *)self.overlay;
image = [[UIImage alloc] initWithData:mapOverlay.radarData];
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
#try {
UIGraphicsBeginImageContext(image.size);
UIGraphicsPushContext(ctx);
[image drawInRect:theRect blendMode:kCGBlendModeNormal alpha:0.5];
UIGraphicsPopContext();
UIGraphicsEndImageContext();
}
#catch (NSException *exception) {
NSLog(#"Caught an exception while drawing radar on map - %#",[exception description]);
}
#finally {
}
}
and here's where I'm adding the overlay:
- (void)mapRadar {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.mapOverlay = [[MapOverlay alloc] initWithImageData:appDelegate.image withLowerLeftCoordinate:CLLocationCoordinate2DMake(appDelegate.south, appDelegate.west) withUpperRightCoordinate:CLLocationCoordinate2DMake(appDelegate.north, appDelegate.east)];
[self.mapView addOverlay:self.mapOverlay];
[self.mapView setNeedsDisplay];
self.mapView.showsUserLocation = YES;
MKMapPoint lowerLeft2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.south2, appDelegate.west2) );
MKMapPoint upperRight2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(appDelegate.north2, appDelegate.east2));
MKMapRect localMapRect = MKMapRectMake(lowerLeft2.x, upperRight2.y, upperRight2.x - lowerLeft2.x, lowerLeft2.y - upperRight2.y);
[self.mapView setVisibleMapRect:localMapRect animated:YES];
}
#pragma Mark - MKOverlayDelgateMethods
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay{
if([overlay isKindOfClass:[MapOverlay class]]) {
MapOverlay *mapOverlay = overlay;
self.mapOverlayView = [[MapOverlayView alloc] initWithOverlay:mapOverlay];
}
return self.mapOverlayView;
}
Does anyone have an idea or a solution where I can show a sequence of images on an MKMapView smoothly? At this point, I'm desperate for solutions and can't find it anywhere else, so anything would help me, thanks!
Using Lefteris' suggestion, you can just use UIImageView with animationImages. The animation should be smooth provided your overlay images are properly sized for the screen.
To pass touches through to the map view below, you can create a new UIView subclass with a (weak) reference to the map view. Override the touch event methods (touchesBegan:withEvent:, touchesMoved:withEvent:, etc) to forward to the map view, like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.mapView touchesBegan:touches withEvent:event];
}
This way the user can still interact with the view below.
You may run into problems when the user tries to zoom, as it will be difficult if not impossible to scale all your animation frames in real-time. You should look into using CATiledLayer instead.

Move NSView around until it hits a border

In a Cocoa-based App i'm having a canvas for drawing, inherited from NSView, as well as a rectangle, also inherited from NSView. Dragging the rectangle around inside of the canvas is no problem:
-(void)mouseDragged:(NSEvent *)theEvent {
NSPoint myOrigin = self.frame.origin;
[self setFrameOrigin:NSMakePoint(myOrigin.x + [theEvent deltaX],
myOrigin.y - [theEvent deltaY])];
}
Works like a charm. The issue i'm having now: How can i prevent the rectangle from being moved outside the canvas?
So, first of all i would like to fix this just for the left border, adapting the other edges afterwards. My first idea is: "check whether the x-origin of the rectangle is negative". But: once it is negative the rectangle can't be moved anymore around (naturally). I solved this with moving the rectangle to zero x-offset in the else-branch. This works but it's ... ugly.
So i'm little puzzled with this one, any hints? Definitely the solution is really near and easy. That easy, that i cannot figure it out (as always with easy solutions ;).
Regards
Macs
I'd suggest not using the deltaX and deltaY; try using the event's location in the superview. You'll need a reference to the subview.
// In the superview
- (void)mouseDragged:(NSEvent *)event {
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
// Could also add the width of the moving rectangle to this check
// to keep any part of it from going outside the superview
mousePoint.x = MAX(0, MIN(mousePoint.x, self.bounds.size.width));
mousePoint.y = MAX(0, MIN(mousePoint.y, self.bounds.size.height));
// position is a custom ivar that indicates the center of the object;
// you could also use frame.origin, but it looks nicer if objects are
// dragged from their centers
myMovingRectangle.position = mousePoint;
[self setNeedsDisplay:YES];
}
You'd do essentially the same bounds checking in mouseUp:.
UPDATE: You should also have a look at the View Programming Guide, which walks you through creating a draggable view: Creating a Custom View.
Sample code that should be helpful, though not strictly relevant to your original question:
In DotView.m:
- (void)drawRect:(NSRect)dirtyRect {
// Ignoring dirtyRect for simplicity
[[NSColor colorWithDeviceRed:0.85 green:0.8 blue:0.8 alpha:1] set];
NSRectFill([self bounds]);
// Dot is the custom shape class that can draw itself; see below
// dots is an NSMutableArray containing the shapes
for (Dot *dot in dots) {
[dot draw];
}
}
- (void)mouseDown:(NSEvent *)event {
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
currMovingDot = [self clickedDotForPoint:mousePoint];
// Move the dot to the point to indicate that the user has
// successfully "grabbed" it
if( currMovingDot ) currMovingDot.position = mousePoint;
[self setNeedsDisplay:YES];
}
// -mouseDragged: already defined earlier in post
- (void)mouseUp:(NSEvent *)event {
if( !currMovingDot ) return;
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
spot.x = MAX(0, MIN(mousePoint.x, self.bounds.size.width));
spot.y = MAX(0, MIN(mousePoint.y, self.bounds.size.height));
currMovingDot.position = mousePoint;
currMovingDot = nil;
[self setNeedsDisplay:YES];
}
- (Dot *)clickedDotForPoint:(NSPoint)point {
// DOT_NUCLEUS_RADIUS is the size of the
// dot's internal "handle"
for( Dot *dot in dots ){
if( (abs(dot.position.x - point.x) <= DOT_NUCLEUS_RADIUS) &&
(abs(dot.position.y - point.y) <= DOT_NUCLEUS_RADIUS)) {
return dot;
}
}
return nil;
}
Dot.h
#define DOT_NUCLEUS_RADIUS (5)
#interface Dot : NSObject {
NSPoint position;
}
#property (assign) NSPoint position;
- (void)draw;
#end
Dot.m
#import "Dot.h"
#implementation Dot
#synthesize position;
- (void)draw {
//!!!: Demo only: assume that focus is locked on a view.
NSColor *clr = [NSColor colorWithDeviceRed:0.3
green:0.2
blue:0.8
alpha:1];
// Draw a nice border
NSBezierPath *outerCirc;
outerCirc = [NSBezierPath bezierPathWithOvalInRect:
NSMakeRect(position.x - 23, position.y - 23, 46, 46)];
[clr set];
[outerCirc stroke];
[[clr colorWithAlphaComponent:0.7] set];
[outerCirc fill];
[clr set];
// Draw the "handle"
NSRect nucleusRect = NSMakeRect(position.x - DOT_NUCLEUS_RADIUS,
position.y - DOT_NUCLEUS_RADIUS,
DOT_NUCLEUS_RADIUS * 2,
DOT_NUCLEUS_RADIUS * 2);
[[NSBezierPath bezierPathWithOvalInRect:nucleusRect] fill];
}
#end
As you can see, the Dot class is very lightweight, and uses bezier paths to draw. The superview can handle the user interaction.

CGAffineTransformMakeScale Makes UIView Jump to Original Size before scale

I have a UIView that I set up to respond to pinch gestures and change its size, except, once you enlarge it and then try and pinch it again, it jumps to its original size (which just so happens to be 100x200). Here is the code:
#implementation ChartAxisView
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// do gesture recognizers
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(onPinch:)];
[self addGestureRecognizer:pinch];
[pinch release];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0].CGColor;
CGContextSetFillColorWithColor(context, redColor);
CGContextFillRect(context, self.bounds);
}
- (void)onPinch: (UIPinchGestureRecognizer*) gesture {
self.transform = CGAffineTransformMakeScale(gesture.scale, gesture.scale);
}
- (void)dealloc {
[super dealloc];
}
#end
Any thoughts?
So there are two types of Scale (or, transform in general) functions: CGAffineTransformMakeScale and CGAffineTransformScale
The first one, CGAffineTransformMakeScale which you are using, always transforms with respect to the image's original size. And that is why you see the jump to its original size before the scaling happens.
The second one, CGAffineTransformScale, transforms from the image's current position. This is what you need. For this, it requires an additional 'transform' arg. The 'transform' arg in your case represents the enlarged image.
Read this very informative blog post about transformations.
- (void)onPinch: (UIPinchGestureRecognizer*) gesture {
if ([gesture state] == UIGestureRecognizerStateBegan)
{
curTransform = self.transform;
}
self.transform = CGAffineTransformScale(curTransform,gesture.scale, gesture.scale);
}
use CGAffineTransformScale instead of CGAffineTransformMakeScale
you will need a member -> CGAffineTransform curTransform;
;)
you can set transform with following code:
ivClone setTransform:CGAffineTransformMakeScale(scale, scale)];
and you can get current transform with following coee:
newV.transform.a //x scale
newV.transform.d // y scale