UIBezier Path Refusing to Draw - objective-c

I'm trying to draw a arch using UIBezierPath. There are no errors or warnings and I cannot see any obvious bugs but it just will not draw. This is my code:
- (void)drawRect:(CGRect)rect {
CGFloat a = 10;
CGFloat width = CGRectGetWidth(self.bounds);
CGFloat height = CGRectGetHeight(self.bounds);
CGPoint center = CGPointMake(width/2, height/2);
CGFloat radius = MAX(width, height);
CGFloat archWidth = 18;
CGFloat archLengthPerA = 5;
CGFloat startAngle = 3 * M_PI /2;
CGFloat endAngle = archLengthPerA * a + startAngle;
UIBezierPath* path = [UIBezierPath bezierPath];
[path addArcWithCenter:center
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
path.lineWidth = archWidth;
[[UIColor colorWithRed:255 green:17 blue:0 alpha:1.0] setStroke];
[path stroke];
}
I put it in a separate view to test and it crashed on _myString so is that anything to do with it? I tried adding a breakpoint to see if it worked without it but then it crashed again. Any ideas why it's not working?

Your value of radius is too large, so the arc is drawn outside of the view.
Make it smaller. For instance:
CGFloat radius = MIN(width/2, height/2);

Also, your colour values are incorrect:
[[UIColor colorWithRed:255 green:17 blue:0 alpha:1.0] setStroke];
The colour components of that class method should be CGFloat with values between 0.0 and 1.0

Related

Rotate CAShapeLayer

I'm trying to rotate a CAShapeLayer in place
UIBezierPath *aPath = [UIBezierPath bezierPath];
CGFloat originx = (97.5+250);
CGFloat originy = (205+250);
[aPath moveToPoint:CGPointMake(originx,originy)];
[aPath addArcWithCenter:CGPointMake(originx+15, originy-30) radius:15 startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(360) clockwise:YES];
[aPath addLineToPoint:CGPointMake(originx+30,originy)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.anchorPoint = CGPointMake(originx, originy);
shapeLayer.path = [aPath CGPath];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 3.0;
shapeLayer.fillColor = [[UIColor blueColor] CGColor];
[aPath closePath];
shapeLayer.transform = CATransform3DMakeRotation(310 * M_PI/180, 0.0, 0.0, 1.0);
[self.view.layer addSublayer:shapeLayer];
I want to rotate the shape around an axis within the shape itself (the code above makes my little shape rotate around an axis several hundred pixels from the actual centre.
I felt this should be controlled with an anchorpoint like this one:
shapeLayer.anchorPoint = CGPointMake(originx, originy);
but it seems to do nothing within the code.
Any ideas?
The anchorPoint is relative to the bounds of the layer. You can't use the anchor point to place your layer. Instead you can place your shape relative to the zero point, rotate it, and the move it to the desired point. Rotation and translation are done by the transform.

append arrowhead to UIBezierPath

I need your help:
I am trying to create a graph using UIBezierPaths with variable widths and consisting of a bezier curve with two control points. Now I'd like to add arrow heads to the end (right hand side) of these paths. Is there a way to do this i.e. by appending a subpath with a smaller lineWidth which contains a triangle?
here is a sample picture of some paths to which I'd like to add arrowheads:
Thanks for your help!
Let's assume you drew one of arcs like so:
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point1];
[path addQuadCurveToPoint:point3 controlPoint:point2];
CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.lineWidth = 10;
shape.strokeColor = [UIColor blueColor].CGColor;
shape.fillColor = [UIColor clearColor].CGColor;
shape.frame = self.view.bounds;
[self.view.layer addSublayer:shape];
You can use atan2 to calculate the angle from the control point to the final point:
CGFloat angle = atan2f(point3.y - point2.y, point3.x - point2.x);
Note, it doesn't really matter if you use quad bezier or cubic, the idea is the same. Calculate the angle from the last control point to the end point.
You could then put an arrow head by calculating the corners of the triangle like so:
CGFloat distance = 15.0;
path = [UIBezierPath bezierPath];
[path moveToPoint:point3];
[path addLineToPoint:[self calculatePointFromPoint:point3 angle:angle + M_PI_2 distance:distance]]; // to the right
[path addLineToPoint:[self calculatePointFromPoint:point3 angle:angle distance:distance]]; // straight ahead
[path addLineToPoint:[self calculatePointFromPoint:point3 angle:angle - M_PI_2 distance:distance]]; // to the left
[path closePath];
shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.lineWidth = 2;
shape.strokeColor = [UIColor blueColor].CGColor;
shape.fillColor = [UIColor blueColor].CGColor;
shape.frame = self.view.bounds;
[self.view.layer addSublayer:shape];
Where we use sinf and cosf to calculate the corners like so:
- (CGPoint)calculatePointFromPoint:(CGPoint)point angle:(CGFloat)angle distance:(CGFloat)distance {
return CGPointMake(point.x + cosf(angle) * distance, point.y + sinf(angle) * distance);
}
That yields something looking like:
Clearly, just adjust the distance parameters to control the shape of the triangle.

Implementing a clock face in UIView

I'm trying to implement a clock face in UIView - without much luck. The problem is that the hands on the clock face end up in totally the wrong position (i.e. pointing to the wrong time). I'm pretty sure that the logic of my code is correct - but clearly there's something significantly wrong.
My test app is an iOS Single View Application. ViewController.h looks like this:
#import <UIKit/UIKit.h>
#interface Clock : UIView {
UIColor* strokeColour;
UIColor* fillColour;
float strokeWidth;
NSDate* clockTime;
CGPoint clockCentre;
float clockRadius;
}
- (id)initWithCentre:(CGPoint)centre
radius:(float)radius
borderWidth:(float)width
facecolour:(UIColor*)fill
handcolour:(UIColor*)handFill
time:(NSDate*)time;
#end
#interface ViewController : UIViewController
#end
and ViewController.m looks like this:
#import "ViewController.h"
#implementation Clock
- (id)initWithCentre:(CGPoint)centre
radius:(float)radius
borderWidth:(float)width
facecolour:(UIColor*)fill
handcolour:(UIColor*)handFill
time:(NSDate*)time {
CGRect frame = CGRectMake(centre.x-radius,
centre.y-radius,
radius*2,
radius*2);
if (self = [super initWithFrame:frame]) {
// Initialization code
strokeColour = fill;
fillColour = handFill;
strokeWidth = width;
clockRadius = radius;
clockTime = time;
clockCentre.x = frame.size.width/2;
clockCentre.y = frame.size.height/2;
}
return self;
}
- (void)drawRect:(CGRect)rect {
double red, green, blue, alpha;
CGRect circleRect;
circleRect.origin.x = rect.origin.x+strokeWidth;
circleRect.origin.y = rect.origin.y+strokeWidth;
circleRect.size.width = rect.size.width - (strokeWidth*2);
circleRect.size.height = rect.size.height - (strokeWidth*2);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, strokeWidth);
[fillColour getRed:&red green:&green blue:&blue alpha:&alpha];
CGContextSetRGBFillColor(contextRef, red, green, blue, alpha);
[strokeColour getRed:&red green:&green blue:&blue alpha:&alpha];
CGContextSetRGBStrokeColor(contextRef, red, green, blue, alpha);
CGContextFillEllipseInRect(contextRef, circleRect);
CGContextStrokeEllipseInRect(contextRef, circleRect);
CGContextSetLineWidth(contextRef, strokeWidth);
[strokeColour getRed:&red green:&green blue:&blue alpha:&alpha];
CGContextSetRGBStrokeColor(contextRef, red, green, blue, alpha);
float hourAngle, minuteAngle, secondAngle, angle;
double endX, endY;
NSCalendar *cal = [[NSCalendar alloc]initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [cal components:units fromDate:clockTime];
hourAngle = (components.hour /12.0) * M_PI * 2.0;
minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
secondAngle = (components.second / 60.0) * M_PI * 2.0;
//minute hand
angle = minuteAngle;
endX = cos(angle) * (clockRadius*0.85) + clockCentre.x;
endY = sin(angle) * (clockRadius*0.85) + clockCentre.y;
CGContextMoveToPoint(contextRef,clockCentre.x,clockCentre.y);
CGContextAddLineToPoint(contextRef,endX,endY);
CGContextStrokePath(contextRef);
//hour hand
angle = hourAngle;
endX = cos(angle) * (clockRadius*0.65) + clockCentre.x;
endY = sin(angle) * (clockRadius*0.65) + clockCentre.y;
CGContextSetLineWidth(contextRef, strokeWidth*1.5);
CGContextMoveToPoint(contextRef,clockCentre.x,clockCentre.y);
CGContextAddLineToPoint(contextRef,endX,endY);
CGContextStrokePath(contextRef);
}
#end
#interface ViewController ()
#end
#implementation ViewController
- (UIView*)drawClockFaceWithCentre:(CGPoint)centre
radius:(float)radius
time:(NSDate*)time
colour:(UIColor*)colour
backgroundColour:(UIColor*)bgcolour {
UIView* clock = [[Clock alloc]initWithCentre:centre
radius:radius
borderWidth:6.0
facecolour:bgcolour
handcolour:colour
time:time];
clock.backgroundColor = [UIColor clearColor];
return clock;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGPoint centre;
centre.x=200;
centre.y=200;
[self.view addSubview:[self drawClockFaceWithCentre:centre
radius:100
time:[NSDate date]
colour:[UIColor blackColor]
backgroundColour:[UIColor whiteColor]]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I'm sure it's something very simple that I've done wrong - a mistake in my maths. Can anyone see the bug (and any other suggestions for improvements that could be made would be welcome, bearing in mind (of course) that this is just a simple test harness).
As I wrote in the comment, consider dropping doing everything in drawRect and use layers. Also, instead of calculating trigonometry to draw correct path, consider just drawing hands at 12:00 and transform rotate them with correct angle. You could do it like this in init...
CAShapeLayer *faceLayer = [CAShapeLayer layer];
faceLayer.frame = self.bounds;
faceLayer.fillColor = [UIColor whiteColor].CGColor;
faceLayer.strokeColor = [UIColor blackColor].CGColor;
faceLayer.path = [UIBezierPath bezierPathWithOvalInRect:faceLayer.bounds].CGPath;
faceLayer.lineWidth = 3.0;
[self.layer addSublayer:faceLayer];
CGPoint middle = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [cal components:units fromDate:time];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:middle];
[path addLineToPoint:CGPointMake(middle.x, middle.y-radius*0.5)];
CGFloat hourAreaAngle = (2*M_PI)/12.0;
CAShapeLayer *hourLayer = [CAShapeLayer layer];
hourLayer.frame = self.bounds;
hourLayer.strokeColor = [UIColor redColor].CGColor;
hourLayer.lineWidth = 3.0;
hourLayer.path = path.CGPath;
[self.layer addSublayer:hourLayer];
hourLayer.transform = CATransform3DMakeRotation((components.hour/12.0*(M_PI*2.0))+(hourAreaAngle*(components.minute/60.0)), 0.0, 0.0, 1.0);
[path removeAllPoints];
[path moveToPoint:middle];
[path addLineToPoint:CGPointMake(middle.x, middle.y-radius*0.8)];
CAShapeLayer *minuteLayer = [CAShapeLayer layer];
minuteLayer.frame = self.bounds;
minuteLayer.strokeColor = [UIColor blueColor].CGColor;
minuteLayer.lineWidth = 2.0;
minuteLayer.path = path.CGPath;
[self.layer addSublayer:minuteLayer];
minuteLayer.transform = CATransform3DMakeRotation(components.minute/60.0*(M_PI*2.0), 0.0, 0.0, 1.0);
[path removeAllPoints];
[path moveToPoint:middle];
[path addLineToPoint:CGPointMake(middle.x, middle.y-radius)];
CAShapeLayer *secondsLayer = [CAShapeLayer layer];
secondsLayer.frame = self.bounds;
secondsLayer.strokeColor = [UIColor greenColor].CGColor;
secondsLayer.lineWidth = 1.0;
secondsLayer.path = path.CGPath;
[self.layer addSublayer:secondsLayer];
secondsLayer.transform = CATransform3DMakeRotation(components.second/60.0*(M_PI*2.0), 0.0, 0.0, 1.0);
Note I put my colours and line widths because I'm lazy and didn't want to check your code :P.
If you want to update hands later, just store hands layers as variables and transform rotate them again (just remember to first transform to identity!).

NSTextFieldCell rounded corners stroke not rounded

I want to have an NSTextField with rounded corners, for that I subclassed my NSTextFieldCell, and used drawInteriorWithFrame:(NSRect) inView:(NSView *)
My code looks like that :
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
//Color Declarations
NSColor* fillColor = [NSColor colorWithCalibratedRed: 1 green: 1 blue: 1 alpha: 1];
NSColor* strokeColor = [NSColor colorWithCalibratedRed: 0.679 green: 0.679 blue: 0.679 alpha: 1];
//Shadow Declarations
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: strokeColor];
[shadow setShadowOffset: NSMakeSize(0.1, 0.1)];
[shadow setShadowBlurRadius: 4];
//Rounded Rectangle Drawing
NSBezierPath* roundedRectanglePath = [NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius: 10 yRadius: 10];
[fillColor setFill];
[roundedRectanglePath fill];
//Rounded Rectangle Inner Shadow
NSRect roundedRectangleBorderRect = NSInsetRect([roundedRectanglePath bounds], -shadow.shadowBlurRadius, -shadow.shadowBlurRadius);
roundedRectangleBorderRect = NSOffsetRect(roundedRectangleBorderRect, -shadow.shadowOffset.width, -shadow.shadowOffset.height);
roundedRectangleBorderRect = NSInsetRect(NSUnionRect(roundedRectangleBorderRect, [roundedRectanglePath bounds]), -1, -1);
NSBezierPath* roundedRectangleNegativePath = [NSBezierPath bezierPathWithRect: roundedRectangleBorderRect];
[roundedRectangleNegativePath appendBezierPath: roundedRectanglePath];
[roundedRectangleNegativePath setWindingRule: NSEvenOddWindingRule];
[NSGraphicsContext saveGraphicsState];
{
NSShadow* shadowWithOffset = [shadow copy];
CGFloat xOffset = shadowWithOffset.shadowOffset.width + round(roundedRectangleBorderRect.size.width);
CGFloat yOffset = shadowWithOffset.shadowOffset.height;
shadowWithOffset.shadowOffset = NSMakeSize(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset));
[shadowWithOffset set];
[[NSColor grayColor] setFill];
[roundedRectanglePath addClip];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform translateXBy: -round(roundedRectangleBorderRect.size.width) yBy: 0];
[[transform transformBezierPath: roundedRectangleNegativePath] fill];
}
[NSGraphicsContext restoreGraphicsState];
[strokeColor setStroke];
[roundedRectanglePath setLineWidth: 2];
[roundedRectanglePath stroke];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
The result looks great apart from borders which are not rounded. Images are better than words : My NSTextField
All help is accepted ! :D Thank you in advance.
UPDATE
I did a copy paste with the code of the site you said, but I'm still having the same trouble...
NSTextField with on rounded corner
You may simply choose the Border of Text Field in its Attributes Inspector if you want a text field with its all corners rounded. Or to draw a text field with any one round corner, go through this
Also if you want draw a custom all round corner text field, just follow the steps in above link, but instead of drawing a bezier path with one round corner, simply draw a bezier path with all corners round using
[NSBezierPath bezierPathWithRoundedRect:textfieldrect xRadius:10 yRadius:10]

Stripped stroke on NSBezierPath

I am trying to draw a simple diagram. I have squares that are connected by lines. The lines are drawn with NSBezierPath's. I am using a random color for the lines so I can follow them. My problem is that the lines change color.
The output -
Code - I removed the lines that draw the squares so it only draws the lines:
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
CGFloat height = 70.0f;
CGFloat yOffset = 20.0f;
NSRect rect = NSMakeRect(50.0f, 50.0f, 200.0f, height);
NSEnumerator *reverseEnumerator = [steps reverseObjectEnumerator];
NSMutableDictionary *rectsByStep = [NSMutableDictionary dictionaryWithCapacity:10];
for( WFGJobStep *step in reverseEnumerator )
{
[[NSColor redColor] setFill];
[rectsByStep setObject:[NSValue valueWithRect:rect] forKey:[NSNumber numberWithInteger:step.step]];
rect.origin.y += height + yOffset;
}
reverseEnumerator = [steps reverseObjectEnumerator];
for( WFGJobStep *step in reverseEnumerator )
{
NSRect stepRect = [[rectsByStep objectForKey:[NSNumber numberWithInteger:step.step]] rectValue];
NSPoint startPoint = NSMakePoint( stepRect.origin.x + stepRect.size.width, stepRect.origin.y);
// draw lines
for( NSNumber *stepNumber in step.nextSteps )
{
//
//
// Line drawing code here
//
//
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth: 4];
NSRect targetStepRect = [[rectsByStep objectForKey:stepNumber] rectValue];
NSPoint endPoint = NSMakePoint( targetStepRect.origin.x + targetStepRect.size.width, targetStepRect.origin.y + targetStepRect.size.height);
[path moveToPoint:startPoint];
CGFloat controlX = ( startPoint.y - endPoint.y ) * .2 + stepRect.origin.x + stepRect.size.width + 20;
[path curveToPoint:endPoint controlPoint1:NSMakePoint(controlX, startPoint.y) controlPoint2:NSMakePoint(controlX, endPoint.y)];
NSRect square = NSMakeRect( endPoint.x, endPoint.y, 9, 9 );
[path appendBezierPathWithOvalInRect: square];
[[self randomColor] set];
[path stroke];
}
}
}
- (NSColor *)randomColor {
float c[4];
c[0] = (arc4random() / RAND_MAX) * 1;
c[1] = (arc4random() / RAND_MAX) * 1;
c[2] = (arc4random() / RAND_MAX) * 1;
c[3] = 1.0f;
return [NSColor colorWithDeviceRed:c[0] green:c[1] blue:c[2] alpha:c[3]];
}
My issue is that this is in a scoll view and this is my first NSScrollView. I was not aware that as the view scrolls it redraws its subviews. The issue was that I was using a random color for my stroke so that as I scrolled it would randomly change the colors of the lines.