How can I convert an MKCircle's radius (in meters) into a value I can use to draw a circle using core graphics in an MKOverlayPathView subclass?
In the following code the radius is hardcoded to 50 but I need it to reflect the radius of the MKCircle.
For the position I use MKMapPointForCoordinate() to convert the MKCircle's coordinate to an MKMapPoint, then convert the MKMapPoint to a point using MKOverlayPathView's pointForMapPoint:. But how can I convert the MKCircle's radius into a relative distance?
MKCircle *circle = [self circleForLocation:location];
CGPoint relativePoint = [self pointForMapPoint:MKMapPointForCoordinate(circle.coordinate)];
float radius = 50;
//Fill
[self applyFillPropertiesToContext:context atZoomScale:zoomScale];
CGContextAddArc(context, relativePoint.x, relativePoint.y, radius, 0, 2*3.1415926535898, 1);
CGContextDrawPath(context, kCGPathFill);
The height and width of the MKCircle's boundingMapRect should be equal to the circle's diameter:
CGRect boundingRect = [self rectForMapRect:circle.boundingMapRect];
CGFloat radius = boundingRect.size.width / 2;
Related
I have Requirement Were i have to Draw a Circle for Region in CLLocationManager. i have Accomplished the Requirement with this Code as,
CLLocationDegrees latitude = 37.33492222;
CLLocationDegrees longitude = -122.03304215;
CLLocationCoordinate2D centerPoint = CLLocationCoordinate2DMake(latitude, longitude);
CLLocationDistance radius = 100.0;
CLRegion *region = [[CLRegion alloc]initCircularRegionWithCenter:centerPoint radius:radius identifier:#"Apple"];
[locationManager startMonitoringForRegion:region];
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 1;
circle.opacity = 0.5;
// Add to parent layer
[self.view.layer addSublayer:circle;
I have Got Out Put as
Problem is When i given the radius value as 100, i got result as ScreenShot. if i will give value as 200 then obviously the Circle Will Increase.
My Question is, I want the Circle as in same Size When any Value is Given say 200, 300 or 400.
Your question talks about increasing the radius, but in this forum, "radius" will generally be construed as the radius of the circle measured in points/pixels.
Clearly, that's not what you're asking for, though. Now, you've said you don't want to use a MKMapView, but I think it's a useful language to use when talking about your goals. Bottom line, you don't want to change the radius of the circle presented on the screen. You want to change the "span" (MKCoordinateSpan) of the "region" (MKCoordinateRegion) of the map which will be presented in the aforementioned circle.
There are two ways of approaching this problem of how to draw a circle of fixed size that represents a projection of a map (but you don't, for some reason, want a map).
Manually:
Define a few useful properties:
#property (nonatomic) MKCoordinateRegion region;
#property (nonatomic) CGRect frame;
The region defines the range of latitude and longitudes that will be presented in your user interface. The frame will be the screen coordinates in which we'll be presenting the those latitude and longitude points. Note, to do a visual representation of lat/long coordinates in a user interface you need both of these two aspects, something that defines what lat/long can be represented and something else that says where to put it on the screen.
So, the first question is how you define the region. Here I'm defining the center coordinate and defining a region as being 500 meters from that:
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(...);
self.region = MKCoordinateRegionMakeWithDistance(coordinate, 500.0, 500.0);
You asked "how do I change the radius [i.e. the span] of what is shown?" You do that by changing the region variable which says that range of lat/long will be represented in the UI.
Anyway, you can now you can add the circle to your screen:
- (void)addCircle
{
CGPoint center = CGPointMake(self.view.layer.bounds.size.width / 2.0, self.view.layer.bounds.size.height / 2.0);
CGFloat radius = self.view.layer.bounds.size.width * 0.40;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:0.0
endAngle:M_PI * 2.0
clockwise:YES];
self.frame = CGRectMake(center.x - radius, center.y - radius, radius * 2.0, radius * 2.0);
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = [path CGPath];
layer.strokeColor = [[UIColor darkGrayColor] CGColor];
layer.fillColor = [[UIColor whiteColor] CGColor];
layer.lineWidth = 3.0;
[self.view.layer addSublayer:layer];
self.view.backgroundColor = [UIColor lightGrayColor];
}
Write a routine to convert from a CLLocationCoordinate2D to a location on the screen:
- (CGPoint)determineCGPointFromCoordinate:(CLLocationCoordinate2D)coordinate
{
CGFloat percentX = (coordinate.longitude - self.region.center.longitude + self.region.span.longitudeDelta / 2.0) / self.region.span.longitudeDelta;
CGFloat percentY = 1.0 - (coordinate.latitude - self.region.center.latitude + self.region.span.latitudeDelta / 2.0) / self.region.span.latitudeDelta;
return CGPointMake(self.frame.origin.x + percentX * self.frame.size.width,
self.frame.origin.y + percentY * self.frame.size.height);
}
You can then add your points on your screen:
CGPoint center = [self determineCGPointFromCoordinate:coordinate];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"spot.png"]];
imageView.center = center;
[self.view addSubview:imageView];
That yields us a view that looks like:
Even easier, in my opinion, is to reevaluate your decision to not to use a map view.
You could add a map view (see the Location Awareness Programming Guide)
Take your CAShapeLayer that you created, and rather than adding as a sublayer, you could instead clip the map to that circular CAShapeLayer:
[self.mapView.layer setMask:circleShapeLayer];
And, once you've done all of the adding of annotations to the view, you get something like this:
Personally, I like the map kit approach, as it gets you out of the business of manually calculating screen coordinates. But if you really don't want the map background there, you can do it manually.
I assume you want your circle to increase and decrease in size as the user zooms. That way, the circle will always be over the same area of land on the map.
In that case, use an MKCircle, created with +circleWithCenterCoordinate:radius:, conform to MKMapViewDelegate and set the stroke and fill in mapView:viewForOverlay:.
How to Pass latitude and Longitude values form CLLocation Manager to My CustomView. I have A ViewController With CLLocationManger where iam getting the Locations, I have Another UIView Class with drawRect Where i have Divided the View with Coordinates as
#define EARTH_RADIUS 6371
- (void)drawRect:(CGRect)rect
{
CGPoint latLong = {41.998035, -116.012215};
CGPoint newCoord = [self convertLatLongCoord:latLong];
NSLog(#"Cartesian Coordinate: (%f, %f)",newCoord.x, newCoord.y);
//Draw dot at coordinate
CGColorRef darkColor = [[UIColor colorWithRed:21.0/255.0
green:92.0/255.0
blue:136.0/255.0
alpha:1.0] CGColor];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, darkColor);
CGContextFillRect(context, CGRectMake(newCoord.x, newCoord.y, 100, 100));
}
-(CGPoint)convertLatLongCoord:(CGPoint)latLong
{
CGFloat x = EARTH_RADIUS * cos(latLong.x) * cos(latLong.y);
CGFloat y = EARTH_RADIUS * cos(latLong.x) * sin(latLong.y);
return CGPointMake(x, y);
}
I have Took the CGPoint latLong = {41.998035, -116.012215} static.
Can you say me How to Pass A ViewController Values to UIView Class
You would generally have a property in you UIView subclass for your coordinate (either using your CGPoint or perhaps a CLLocationCoordinate2D) and then call setNeedsDisplay so that the device knows that it needs to call your drawRect method.
Thus, have a property:
#property (nonatomic) CLLocationCoordinate2D coordinate;
And then update the implementation of your view as such:
- (void)setCoordinate:(CLLocationCoordinate2D)coordinate
{
_coordinate = coordinate;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGPoint newCoord = [self convertLatLongCoord:self.coordinate];
NSLog(#"Cartesian Coordinate: (%f, %f)",newCoord.x, newCoord.y);
//Draw dot at coordinate
CGColorRef darkColor = [[UIColor colorWithRed:21.0/255.0
green:92.0/255.0
blue:136.0/255.0
alpha:1.0] CGColor];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, darkColor);
CGContextSetStrokeColorWithColor(context, darkColor);
CGContextFillRect(context, CGRectMake(newCoord.x, newCoord.y, 100.0, 100.0));
}
// I assume you're using the algorithm from here: https://stackoverflow.com/a/1185413/1271826
- (CGPoint)convertLatLongCoord:(CLLocationCoordinate2D)coordinate
{
CGFloat x = EARTH_RADIUS * cos(coordinate.latitude * M_PI / 180.0) * cos(coordinate.longitude * M_PI / 180.0);
CGFloat y = EARTH_RADIUS * cos(coordinate.latitude * M_PI / 180.0) * sin(coordinate.longitude * M_PI / 180.0);
// TODO: The above calculates x and y values from -EARTH_RADIUS to EARTH_RADIUS.
// You presumably want to scale this appropriately for your view. The
// specifics will vary based upon your desired user interface.
return CGPointMake(x, y);
}
Then, when you want to update your view, you can do something like:
myCustomView.coordinate = CLLocationCoordinate2DMake(41.998035, -116.012215);
While I've tried to remedy some flaws in convertLatLongCoord, it's still not perfect, as it will yield values ranging from -EARTH_RADIUS to EARTH_RADIUS (which obviously won't work for a CGRect of a UIView, which is expecting values between zero and the width/height of the view).
I assume the underlying algorithm is one like discussed here: Converting from longitude\latitude to Cartesian coordinates. I've tried to fix the degrees to radians conversion needed by cos and sin, you still need to scale this and translate it to values appropriate for what portion of the map/radar you're intending to show.
Based on the comment that you want to "find your boat" it sounds like you want the heading from your current location to a specified location (your boat). That calculation is this
+ (float)getHeadingForDirectionFromCoordinate:(CLLocationCoordinate2D)fromLoc toCoordinate:(CLLocationCoordinate2D)toLoc
{
float fLat = degreesToRadians(fromLoc.latitude);
float fLng = degreesToRadians(fromLoc.longitude);
float tLat = degreesToRadians(toLoc.latitude);
float tLng = degreesToRadians(toLoc.longitude);
float degree = radiansToDegrees(atan2(sin(tLng-fLng)*cos(tLat), cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(tLng-fLng)));
if (degree >= 0) {
return degree;
} else {
return 360+degree;
}
}
I'm attempting to draw a translucent black rectangle with a triangle cut out of the center.
I have this working perfectly with the code below. However, I'd like to round the corners of the triangle.
I've tried a couple of different techniques for adding rounded corners such as setting CGContextSetLineCap for a stroke and by creating my path with quartz and using CGContextAddArcToPoint, but I've failed to get anything to work.
As I mentioned, the code below works for a triangle cutout. How can I round the corners of the triangle?
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint position = CGPointMake(rect.size.width/2, rect.size.height * .7);
CGSize size = CGSizeMake(rect.size.width*.8, rect.size.height*.5);
CGFloat firstPointX = (position.x - (size.width / 2));
CGFloat firstPointY = (position.y - (size.height));
CGPoint firstPoint = CGPointMake(firstPointX, firstPointY);
CGFloat secondPointX = position.x + (size.width / 2);
CGFloat secondPointY = position.y - (size.height);
CGPoint secondPoint = CGPointMake(secondPointX, secondPointY);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:position];
[path addLineToPoint:firstPoint];
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:position];
[path closePath];
CGContextAddRect(context, rect);
CGContextAddPath(context, path.CGPath);
CGContextEOClip(context);
UIColor *translucentColor = [UIColor colorWithWhite:0 alpha:0.5];
CGContextSetFillColorWithColor(context, translucentColor.CGColor);
CGContextFillRect(context, rect);
UIGraphicsEndImageContext();
EDIT: Here's an example of what I'm trying to accomplish.
I think you want to use CGContextAddArc. It draws part of a circle with a line leading up to it if necessary. Here's the code to draw a rounded box that fills a UIView. For a triangle you'd have 3 lines instead of 4. Two on the top and one in the middle on the bottom. This is from a roundBoxView class I have:
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect boxRect = self.bounds;
float bRadius = self.cornerRadius;
float shrink = self.strokeThickness/2;
CGContextBeginPath(context);
// instead of this gray you could later add options for other colors
CGContextSetGrayFillColor(context, 0.0f, self.backgroundOpacity);
CGContextMoveToPoint(context, CGRectGetMinX(boxRect)+shrink + bRadius, CGRectGetMinY(boxRect)+shrink);
CGContextAddArc(context, CGRectGetMaxX(boxRect)-shrink - bRadius, CGRectGetMinY(boxRect)+shrink + bRadius, bRadius, 3 * (float)M_PI / 2, 0, 0);
CGContextAddArc(context, CGRectGetMaxX(boxRect)-shrink - bRadius, CGRectGetMaxY(boxRect)-shrink - bRadius, bRadius, 0, (float)M_PI / 2, 0);
CGContextAddArc(context, CGRectGetMinX(boxRect)+shrink + bRadius, CGRectGetMaxY(boxRect)-shrink - bRadius, bRadius, (float)M_PI / 2, (float)M_PI, 0);
CGContextAddArc(context, CGRectGetMinX(boxRect)+shrink + bRadius, CGRectGetMinY(boxRect)+shrink + bRadius, bRadius, (float)M_PI, 3 * (float)M_PI / 2, 0);
CGContextClosePath(context);
CGContextFillPath(context);
Of course instead of the boxRect being the full bounds you could pass that to your method and draw it using whatever bounds you want. To make it a triangle you would only have the 3 lines instead of two and you might have to do some math to figure out the start and end angles. On a box those angles are always 90 degrees (here given is icky radians) but on a triangle you'll either have to calc the angles on the fly or have a preset aspect ratio on the triangle so you can use preset start and stop angles. In the example as pictured with 3 equal angles you'd do 120 degrees or M_PI / 1.5 sized steps.
I want to draw polygon of different sides (4-12). What is the logic for drawing a polygon. For e.g. if user selects 6 side, it should draw a hexagon, if user enters 8 sides it should draw a octagon. I have found the following code but i also want to resize the UIView in which i am drawing a polygon so that the shape inside of the view also grows along with the view. Any body can help me please. Following is the code i am using currently but it is not positioned at the center also when i resize the view that shape moves to another position in the view.
int radius = MINIMUM(widht, height)*0.4 ;
for (int i = 0; i < _numberOFsides; i++){
CGPoint point = CGPointMake(widht/2+radius *cosf(i*2*M_PI/_numberOFsides), widht/2+radius*sinf(i*2*M_PI/_numberOFsides));
if (i==0) {
[_shapePath moveToPoint:point];
}
else{
[_shapePath addLineToPoint:point];
[_shapePath stroke];
}
}
now to resize ur UIBazierPath you can add below code,
CGRect bazierRect = CGPathGetBoundingBox(bezierpath.CGPath)
CGFloat scaleX = view.frame.size.width / bazierRect.frame.size.width;
CGFloat scaleY = view.frame.size.height / bazierRect.frame.size.height;
CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY);
CGPathRef newPath = CGPathCreateCopyByTransformingPath(bezierpath.CGPath, &transform);
bezierPath.CGPath = newPath;
CFRelease(newPath);
If you want to make a regular polygon with any number of sides the following code will give you the vertices of each edge and it is easy to rescale in size and number of sides:
int n = 10; //number of edges
float j = 20; //length of each edge
float x = 130;
float y = 250;//the point 130,250 will be at the bottom of the figure
float angle = 2*M_PI;
for (int i = 0; i < n; i++) {
CGRect frame = CGRectMake(x, y, 2, 2);//put a dot on x,y
NSLog(#"%f | %f, %f", angle, x, y);
x = x + j*cosf(angle);
y = y + j*sinf(angle); //move to the next point
angle = angle - 2*M_PI/n; //update the angle
//display the dot
UIView *rect = [[UIView alloc] initWithFrame:frame];
rect.backgroundColor = [UIColor blueColor];
[self.view addSubview:rect];
}
Hope this helps. If you have any questions feel free to ask and have a great day!
~Deadly Porcupine
I wish to create an animation. The best way I can explain this is if you can imagine drawing a circle. It starts at the top or 12 o clock and draws clockwise all the way around until it becomes a complete circle over the space of 10 or so seconds.
The closet I have come to this is to draw a point rotating around a center point using Core Animation (using the sample code here). But I am at a loss on how to draw the circle? Any suggestions are very welcome.
Many Thanks :)
What you really should do is to animate the stroke of a CAShapeLayer where the path is a circle. This will be accelerated using Core Animation and is less messy then to draw part of a circle in -drawRect:.
The code below will create a circle shape layer in the center of the screen and animate the stroke of it clockwise so that it looks as if it is being drawn. You can of course use any shape you'd like. (You can read this article on Ole Begemanns blog to learn more about how to animate the stroke of a shape layer.)
Note: that the stoke properties are not the same as the border properties on the layer. To change the width of the stroke you should use "lineWidth" instead of "borderWitdh" etc.
// Set up the shape of the circle
int radius = 100;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor blackColor].CGColor;
circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 10.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
Here is another solution to the problem, based off #David's answer. This approach lets you set the direction of the circle's animation, and offers slightly more control. Edit: I've written a blog post on how to draw a circle with Swift, which I'll try to keep up to date with the betas. Check there if the code below doesn't work for you.
let radius = 100.0
// Create the circle layer
var circle = CAShapeLayer()
// Set the center of the circle to be the center of the view
let center = CGPointMake(CGRectGetMidX(self.frame) - radius, CGRectGetMidY(self.frame) - radius)
let fractionOfCircle = 3.0 / 4.0
let twoPi = 2.0 * Double(M_PI)
// The starting angle is given by the fraction of the circle that the point is at, divided by 2 * Pi and less
// We subtract M_PI_2 to rotate the circle 90 degrees to make it more intuitive (i.e. like a clock face with zero at the top, 1/4 at RHS, 1/2 at bottom, etc.)
let startAngle = Double(fractionOfCircle) / Double(twoPi) - Double(M_PI_2)
let endAngle = 0.0 - Double(M_PI_2)
let clockwise: Bool = true
// `clockwise` tells the circle whether to animate in a clockwise or anti clockwise direction
circle.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: clockwise).CGPath
// Configure the circle
circle.fillColor = UIColor.blackColor().CGColor
circle.strokeColor = UIColor.redColor().CGColor
circle.lineWidth = 5
// When it gets to the end of its animation, leave it at 0% stroke filled
circle.strokeEnd = 0.0
// Add the circle to the parent layer
self.layer.addSublayer(circle)
// Configure the animation
var drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
drawAnimation.repeatCount = 1.0
// Animate from the full stroke being drawn to none of the stroke being drawn
drawAnimation.fromValue = NSNumber(double: fractionOfCircle)
drawAnimation.toValue = NSNumber(float: 0.0)
drawAnimation.duration = 30.0
drawAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Add the animation to the circle
circle.addAnimation(drawAnimation, forKey: "drawCircleAnimation")
I got a solution to this problem, code below. I seem to have messed up in my explanation of the problem at hand so rewrote it as another question that got answered with the correct solution, see here
thanks everyone for their advice!
-(void)drawRect:(CGRect)rect
{
// Drawing code
CGRect allRect = self.bounds;
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw background
CGContextSetRGBStrokeColor(context, self.strokeValueRed, self.strokeValueGreen, self.strokeValueBlue, self.strokeValueAlpha); // white
CGContextSetLineWidth(context, 5);
// Draw progress
CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
CGFloat radius = (allRect.size.width - 4) / 2;
CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
CGContextStrokePath(context);
}
Ok I have a partial fix. I found the following code that draws segments of a circle. Its basically a UIView subclass that gets its drawRect fired every time its progress should be updated. The only question I have now is how would I change it so that instead of drawing sections or slices it would just draw the circle stroke?
-(void)drawRect:(CGRect)rect
{
// Drawing code
CGRect allRect = self.bounds;
CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw background
CGContextSetRGBStrokeColor(context, self.strokeValueRed, self.strokeValueGreen, self.strokeValueBlue, self.strokeValueAlpha); // white
CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 0.1f); // translucent white
CGContextSetLineWidth(context, self.lineWidth);
CGContextFillEllipseInRect(context, circleRect);
CGContextStrokeEllipseInRect(context, circleRect);
// Draw progress
CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
CGFloat radius = (allRect.size.width - 4) / 2;
CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextFillPath(context);
}