Objective-C Draw a Circle with a Ring shape - objective-c

I wrote this class that draws a animated progress with a circle (it draws a circular sector based on a float progress)
#implementation MXMProgressView
#synthesize progress;
- (id)initWithDefaultSize {
int circleOffset = 45.0f;
self = [super initWithFrame:CGRectMake(0.0f,
0.0f,
135.0f + circleOffset,
135.0f + circleOffset)];
self.backgroundColor = [UIColor clearColor];
return self;
}
- (void)drawRect:(CGRect)rect {
CGRect allRect = self.bounds;
CGRect circleRect = CGRectMake(allRect.origin.x + 2, allRect.origin.y + 2, allRect.size.width - 4,
allRect.size.height - 4);
CGContextRef context = UIGraphicsGetCurrentContext();
// background image
//UIImage *image = [UIImage imageNamed:#"loader_disc_hover.png"];
//[image drawInRect:circleRect];
// Orange: E27006
CGContextSetRGBFillColor(context,
((CGFloat)0xE2/(CGFloat)0xFF),
((CGFloat)0x70/(CGFloat)0xFF),
((CGFloat)0x06/(CGFloat)0xFF),
0.01f); // fill
//CGContextSetLineWidth(context, 2.0);
CGContextFillEllipseInRect(context, circleRect);
//CGContextStrokeEllipseInRect(context, circleRect);
// Draw progress
float x = (allRect.size.width / 2);
float y = (allRect.size.height / 2);
// Orange: E27006
CGContextSetRGBFillColor(context,
((CGFloat)0xE2/(CGFloat)0xFF),
((CGFloat)0x70/(CGFloat)0xFF),
((CGFloat)0x06/(CGFloat)0xFF),
1.0f); // progress
CGContextMoveToPoint(context, x, y);
CGContextAddArc(context, x, y, (allRect.size.width - 4) / 2, -M_PI_2, (self.progress * 2 * M_PI) - M_PI_2, 0);
CGContextClosePath(context);
CGContextFillPath(context);
}
#end
Now what I want to do I to draw a ring shape with the same progress animation, instead of filling the full circle, so a circular sector again not starting from the center of the circle.
I tried with CGContextAddEllipseInRect and the CGContextEOFillPath(context);
with no success.

I think you'll need to construct a more complex path, something like:
// Move to start point of outer arc (which might not be required)
CGContextMoveToPoint(context, x+outerRadius*cos(startAngle), y+outerRadius*sin(startAngle));
// Add outer arc to path (counterclockwise)
CGContextAddArc(context, x, y, outerRadius, startAngle, endAngle, 0);
// move *inward* to start point of inner arc
CGContextMoveToPoint(context, x+innerRadius*cos(endAngle), y+innerRadius*sin(endAngle));
// Add inner arc to path (clockwise)
CGContextAddArc(context, x, y, innerRadius, endAngle, StartAngle, 1);
// Close the path from end of inner arc to start of outer arc
CGContextClosePath(context);
Note: I haven't tried the above code myself

Cheap and nasty solution:
Draw a solid circle that is smaller than the original circle by the thickness of the ring you want to draw.
Draw this circle on top of the original circle, all that you will see animating is the ring.

Related

MKOverlayRenderer gets cut off when rendering MKOverlay but fixed by zooming out

I'm new to iOS development and I'm struggling with porting some code from iOS6 involving the use of MKOverlay.
When the overlay radius or coordinate change, the renderer should update the display accordingly in real time.
This part works, but if I drag the overlay too much, it reaches some boundary and the rendering gets cut off. I can't find any documentation or help on this behavior.
In the CircleOverlayRenderer class:
- (id)initWithOverlay:(id<MKOverlay>)overlay
{
self = [super initWithOverlay:overlay];
if (self) {
CircleZone *bOverlay = (CircleZone *)overlay;
[RACObserve(bOverlay, coordinate) subscribeNext:^(id x) {
[self setNeedsDisplay];
}];
[RACObserve(bOverlay, radius) subscribeNext:^(id x) {
[self setNeedsDisplay];
}];
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
{
CGRect rect = [self rectForMapRect:[self.overlay boundingMapRect]];
CGContextSaveGState(context);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextSetFillColorSpace(context, colorSpace);
CGColorSpaceRelease(colorSpace);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextSetFillColor(context, color);
CGContextSetAllowsAntialiasing(context, YES);
// outline
{
CGContextSetAlpha(context, 0.8);
CGContextFillEllipseInRect(context, rect);
}
// red
{
CGContextSetAlpha(context, 0.5);
CGRect ellipseRect = CGRectInset(rect, 0.01 * rect.size.width / 2, 0.01 * rect.size.height / 2);
CGContextFillEllipseInRect(context, ellipseRect);
}
CGContextRestoreGState(cox);
}
In the CircleOverlay class:
- (MKMapRect)boundingMapRect
{
MKMapPoint center = MKMapPointForCoordinate(self.coordinate);
double mapPointsPerMeter = MKMapPointsPerMeterAtLatitude(self.coordinate.latitude);
double mapPointsRadius = _radius * mapPointsPerMeter;
return MKMapRectMake(center.x - mapPointsRadius, center.y - mapPointsRadius,
mapPointsRadius * 2.0, mapPointsRadius * 2.0);
}
Here are some screen shots of the problem I'm seeing:
Problem when dragging overlay too much:
Problem when changing the radius:
The problem does go away if I keep zooming the map out. After the map tiles refresh, the overlay no longer gets cut off...
If anyone had a similar problem, please help me, it's driving me crazy!
Looking at the radius example, it makes me suspect the boundingMapRect, given how its cropping. Looking at the boundingMapRect implementation, the reliance upon MKMapPointsPerMeterAtLatitude (esp when you're looking at a large region) is worrying. That function is useful if you are, for example, trying to figure out where a coordinate 10 meters from some other coordinate, but when looking at really large spans, it doesn't always work out well.
I might, instead, suggest something that gets the MKCoordinateRegion of where the circle is, and then convert that to MKMapRect. A simplistic implementation might look like:
- (MKMapRect)boundingMapRect {
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.coordinate, _radius * 2, _radius * 2);
CLLocationCoordinate2D upperLeftCoordinate = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2);
CLLocationCoordinate2D lowerRightCoordinate = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2);
MKMapPoint upperLeft = MKMapPointForCoordinate(upperLeftCoordinate);
MKMapPoint lowerRight = MKMapPointForCoordinate(lowerRightCoordinate);
return MKMapRectMake(MIN(upperLeft.x, lowerRight.x),
MIN(upperLeft.y, lowerRight.y),
ABS(upperLeft.x - lowerRight.x),
ABS(upperLeft.y - lowerRight.y));
}
You'll have to tweak with this to make sure it gracefully handles crossing of the 180th meridian and when the circle encompasses the north pole, but it illustrates the basic idea: Get MKCoordinateRegion for the circle and then convert that to MKMapRect.

PaintCode: Circle Maintains A/R?

In Paintcode 2, I have a circle inside a frame inside a canvas.
The constraints on the circle are set like this:
To get the circle to size up and not become an ellipse, I have to
Know the expected aspect ratio
Code it in Objective-C myself
Is there any way around this type of code?
-(void)drawRect:(CGRect)rect {
if (rect.size.width > rect.size.height) {
rect.origin.x = (rect.size.width - rect.size.height) * .5f;
rect.size.width = rect.size.height;
} else {
rect.origin.y = (rect.size.height - rect.size.width) * .5f;
rect.size.height = rect.size.width;
}
NSLog(#"Frame=%#", NSStringFromCGRect(rect));
[CircleDraw drawCircleWithFrame:rect];
}
Create a canvas, 150x120
Create an ellipse, 10, 10, 100, 100
Create a new variable called frame, type rectangle: 10, 10, 150, 100
Create a new expression called smallestSide: min(frame.height, frame.width)
Make an expression called position (below)
Drag smallestSide to both height and width of the ellipse
Drag the position to the position of the ellipse
position (Expression)
makePoint(
frame.x+(frame.width-smallestSide)*0.5,
frame.y+(frame.height-smallestSide)*0.5
)
output
- (void)drawCanvas1WithFrame: (CGRect)frame
{
//// Variable Declarations
CGFloat smallestSide = MIN(frame.size.height, frame.size.width);
CGPoint position = CGPointMake(frame.origin.x + (frame.size.width - smallestSide) * 0.5, frame.origin.y + (frame.size.height - smallestSide) * 0.5);
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(position.x, position.y, smallestSide, smallestSide)];
[UIColor.grayColor setFill];
[ovalPath fill];
}
NOTE: I had the help of Matt Dunik at PaintCode to figure this out, but the solution is actually very straightforward.

CGContextAddEllipse - overlapping get's clipped - Quartz

I like to draw a glass with a few Elements
- Top Ellipse
- Bottom Ellipse
- and the Lines Inbetween
Next, it should be filled with a Gradient. The Elements work, but at the point, where the middle of the glass comes in touch with the top or bottom ellipse the area get's clipped.
- (void)drawRect:(CGRect)rect
{
CGPoint c = self.center;
// Drawing code
CGContextRef cx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(cx, 1.0);
[[UIColor whiteColor] setStroke];
// DrawTheShapeOfTheGlass
CGContextBeginPath(cx);
// Top and Bottom Ellipse
CGContextAddEllipseInRect(cx, CGRectMake(0, 0, 100, 20));
CGContextAddEllipseInRect(cx, CGRectMake(10, 90, 80, 20));
// Define the points for the Area inbetween
CGPoint points[] = { {0.0,10.0},{10.0,100.0},{90.0,100.0},{100.0,10.0} };
CGContextAddLines(cx, points, 4);
CGContextClosePath(cx);
// Clip, that's only the Clipped-Area wil be filled with the Gradient
CGContextClip(cx);
// CreateAndDraw the Gradient
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat colorSpace[] = {1.0f, 1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 1.0f };
CGFloat locations[] = { 0.0, 1.0 };
CGGradientRef myGradient = CGGradientCreateWithColorComponents(rgbColorSpace, colorSpace, locations, 2);
CGPoint s = CGPointMake(0, 0);
CGPoint e = CGPointMake(100, 100);
CGContextDrawLinearGradient(cx, myGradient, s, e, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGColorSpaceRelease(rgbColorSpace);
CGGradientRelease(myGradient);
}
Here how it looks like:
Is there any possibility to "fill" the whole ellipse? I played around with BlendModes but it didn't help.
Thanks
Try replacing the points[] initialization code with the following...
CGPoint points[] = {{0.0,10.0},{100.0,10.0},{90.0,100.0},{10.0,100.0}};
CoreGraphics uses the non-zero winding count rule to determine how to fill a path. Since ellipses are drawn clockwise and your trapezoid was drawn counter clockwise, the overlapping regions were not filled. Changing the drawing order of the trapezoid to clockwise will result in an object that is completely filled.

Relationship Between Skew and Angle - CGAffineTransform

I am working on an iPad application that allows the user to draw a line with their finger, then using the beginning and end points of that line I calculate the angle of the line in relation to the x/y plane of the iPad.
My problem is that I want to use the angle of the drawn line to set the skew of an image that I am drawing into the current context.
This is the relavent code from my drawRect method:
CGSize size = CGSizeMake(1024, 768);
UIGraphicsBeginImageContext(size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGAffineTransform skewIt = CGAffineTransformMake(1, 0, skewValue, 1, 0, 0);
CGContextConcatCTM(ctx, skewIt);
CGContextDrawImage(UIGraphicsGetCurrentContext(),
CGRectMake(0,0,size.width, size.height),
theImage.CGImage);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I calculate the angle of the line in my touchEnded method, the point values of the drawn line are stored in an array named skewArray:
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
if (stepThree){
CGContextClearRect(skewContext, CGRectMake(0, 0, 1024, 668));
CGPoint pt1 = CGPointMake([[skewArray objectAtIndex:1]point3].x, [[skewArray objectAtIndex:1]point3].y);
CGPoint pt2 = CGPointMake([[skewArray objectAtIndex:[skewArray count]-1]point3].x, [[skewArray objectAtIndex:[skewArray count]-1]point3].y);
CGPoint pt3 = CGPointMake([[skewArray objectAtIndex:[skewArray count]-1]point3].x, [[skewArray objectAtIndex:1]point3].y);
CGFloat dis1 = sqrt((pow((pt2.x - pt1.x), 2) + pow((pt2.y - pt1.y), 2)));
CGFloat dis2 = sqrt((pow((pt3.x - pt2.x), 2) + pow((pt3.y - pt2.y), 2)));
CGFloat dis3 = sqrt((pow((pt1.x - pt3.x), 2) + pow((pt1.y - pt3.y), 2)));
angle = acos(((-1*pow(dis2, 2))+pow(dis1, 2)+pow(dis3, 2))/(2*dis1*dis3)) * 180/3.14;
//Do something with the angle to produce the appropriate skew value
[self setNeedsDisplay];
}
}
Thank you in advance for your help!
Check out this Wikipedia article on shear mapping. It looks like you'd be better off getting the slope and using 1.0 / m as you skew value.

Plotting out Compass Bearing on iPhone screen

Ok so here is my question,
Let's say you have a bearing of 80 degrees. I would like to plot that out on the iPhone screen with a simple 10x10 square. Let's figure the top of the iPhone in portrait mode is North.
Any ideas on how I would accomplish this?
BTW - I don't want to use the following method:
CGAffineTransform where = CGAffineTransformMakeRotation(degreesToRadians(x_rounded));
[self.compassContainer2 setTransform:where];
I would like to plot out on the screen manually via setting the X -Y cords on the iPhone screen.
- (void)drawRect:(CGRect)rect
{
float compass_bearing = 80.0; // 0 = North, 90 = East, etc.
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 5.0, 5.0);
CGPathAddLineToPoint(path, NULL,
5.0 + 5.0 * cos((compass_bearing - 90.0) * M_PI / 180.0),
5.0 + 5.0 * sin((compass_bearing - 90.0) * M_PI / 180.0));
CGContextSetLineWidth(theContext, 2.0);
CGContextSetStrokeColorWithColor(theContext, [UIColor blackColor].CGColor);
CGContextAddPath(theContext, path);
CGContextStrokePath(theContext);
CGPathRelease(path);
}
So it sounds to me like what you want to accomplish should exist within the drawRect method of a custom view, and then this view would be added to your screen by whatever method you desired (i.e. storyboard or programmatically). Here is a possible implementation that you could use to draw a straight line from the center of the view based on some 'angle':
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Drawing code
CGFloat angle = 0.0 /180.0 * M_PI ;
//Set this to be the length from the center
CGFloat lineDist = 320.0;
CGContextSetLineWidth(context, 5.0);
//Set Color
[[UIColor redColor] setStroke];
//Draw the line
CGContextBeginPath(context);
//Move to center
CGContextMoveToPoint(context, self.frame.size.width/2, self.frame.size.height/2);
//Draw line based on unit circle
//Calculate point based on center starting point, and some movement from there based on the angle.
CGFloat xEndPoint = lineDist * sin(angle) + self.frame.size.width/2;
//Calculate point based on center starting point, and some movement from there based on the angle. (0 is the top of the view, so want to move up when your answer is negative)
CGFloat yEndPoint = -lineDist * cos(angle) + self.frame.size.height/2;
CGContextAddLineToPoint(context, xEndPoint, yEndPoint);
CGContextStrokePath(context);
}