UIBezierPath lines with same origin - objective-c

I'm trying to draw lines originating from one point (the center of the UIView) like so:
- (void)drawRect:(CGRect)rect {
UIBezierPath *path = [self createPath];
[path stroke];
path = [self createPath];
CGAffineTransform rot = CGAffineTransformMakeRotation(2 * M_PI/16);
[path applyTransform:rot];
[path stroke];
path = [self createPath];
rot = CGAffineTransformMakeRotation( 2 * M_PI/8);
[path applyTransform:rot];
[path stroke];
}
- (UIBezierPath *) createPath {
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint start = CGPointMake(self.bounds.size.width/2.0f, self.bounds.size.height/2.0f);
CGPoint end = CGPointMake(start.x + start.x/2, start.y);
[path moveToPoint:start];
[path addLineToPoint:end];
return path;
}
The idea is to draw the same line and apply a rotation (around the center = the start of the line). The result is this:
https://dl.dropbox.com/u/103998739/bezierpath.png
The two rotated lines appear to be also offset in some way. The layer anchorpoint is default 0.5/0.5.
What am I doing wrong ?

In iOS, the default coordinate system origin is in the top-left corner of the layer. (The anchorpoint is relevant for the relation between the layer and its superlayer, but not for the coordinate system within the layer.)
To move the origin of the coordinate system to the center of the layer, you can apply a translation first:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, self.bounds.size.width/2.0f, self.bounds.size.height/2.0f);
UIBezierPath *path = [self createPath];
[path stroke];
path = [self createPath];
CGAffineTransform rot = CGAffineTransformMakeRotation(2 * M_PI/16);
[path applyTransform:rot];
[path stroke];
path = [self createPath];
rot = CGAffineTransformMakeRotation( 2 * M_PI/8);
[path applyTransform:rot];
[path stroke];
}
- (UIBezierPath *) createPath {
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint start = CGPointMake(0, 0);
CGPoint end = CGPointMake(self.bounds.size.width/4.0f, 0);
[path moveToPoint:start];
[path addLineToPoint:end];
return path;
}
Result:

Related

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.

appendBezierPath with style of second path

I have 2 bezierPath. On a first path I draw grid. I handle mouse event and draw new path on first path. I use appendBezierPath, that's all ok, but style of second path don't copy. Cocoa use first path style. How can I append new bezier path with new style.
As example, I draw black grid and I want draw red circle in second path.
UPD:
- (void)drawRect:(NSRect)dirtyRect {
NSRect bounds = [self bounds];
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
[backgroundColor set];
NSRectFill(bounds);
[lineColor set];
for(NSUInteger i = 30; i < NSMaxX(bounds); i += 60) {
[path moveToPoint: (NSPoint) {i, 0}];
[path lineToPoint: (NSPoint) {i, NSMaxY(bounds)}];
for(NSUInteger j = 30; j < NSMaxY(bounds); j += 60) {
[path moveToPoint: (NSPoint) {0, j}];
[path lineToPoint: (NSPoint) {NSMaxX(bounds), j}];
}
}
[NSGraphicsContext saveGraphicsState];
[path stroke];
[currentContext restoreGraphicsState];
}
- (void)setDeniedPoint:(NSPoint)point {
NSBezierPath *newPath = [NSBezierPath bezierPath];
[deniedPointColor set];
[newPath setLineWidth:3.0f];
[newPath moveToPoint:(NSPoint) {point.x-4, point.y-4}];
[newPath lineToPoint:(NSPoint) {point.x+4, point.y+4}];
[newPath moveToPoint:(NSPoint) {point.x-4, point.y+4}];
[newPath lineToPoint:(NSPoint) {point.x+4, point.y-4}];
[path appendBezierPath:newPath];
[self setNeedsDisplay:YES];
}
- (void)mouseDown:(NSEvent *)event {
NSPoint currentLocationCursor = [self convertPoint:[event locationInWindow] fromView:nil];
[self setDeniedPoint:currentLocationCursor];
}

Custom NSTextView border issues

I want to make NSTextView dot border, my drawRect: code is below
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
CGFloat lineDash[2];
lineDash[0] = 1.0;
lineDash[1] = 1.0;
NSBezierPath *path = [NSBezierPath bezierPathWithRect:self.bounds];
[path setLineDash:lineDash count:2 phase:0.0];
[path stroke];
}
I alse want to give some margin between text and border, my code is below
[textView setTextContainerInset:NSMakeSize(0, 10.0)];
[textView setString:#"This is a testThis is a testThis is a testThis is a test"];
But the result is that the top border is missing, who knows how to fix this?
You need to subclass NSScrollView instead of NSTextView. And then you will have a good performance. It can be done like this:
NSScrollView subclass:
-(void)tile {
id contentView = [self contentView];
[super tile];
[contentView setFrame:NSInsetRect([contentView frame], 1.0, 1.0)];
}
-(void)drawRect:(NSRect)dirtyRect {
CGFloat lineDash[2];
lineDash[0] = 1.5;
lineDash[1] = 1.5;
NSBezierPath *path = [NSBezierPath bezierPathWithRect:self.bounds];
[path setLineDash:lineDash count:2 phase:0.0];
[path setLineWidth:2];
[path stroke];
}
Result:

Draw line in Cocoa?

How i can draw a line in a specific window after a button click?
i'm using this:
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth: 4];
NSPoint startPoint = { 21, 21 };
NSPoint endPoint = { 128,128 };
[path moveToPoint: startPoint];
[path lineToPoint:endPoint];
[[NSColor redColor] set];
[path stroke];
but it work only if i put it in the:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
how i can solve this?
my goal is to create an application that can draw lines , according to the details(coordinate) received
thank you.
You should not be drawing outside of a view or layer's drawing method (e.g. drawRect:). What you want to do, in broad strokes, is have a view there that draws the line when a flag is set, and when you click the button, set the flag and tell the view to redraw.
When you click the mouse event. This code will create line, curve and draw.
#import <Cocoa/Cocoa.h>
#interface BezierView : NSView {
NSPoint points[4];
NSUInteger pointCount;
}
#end
#import "BezierView.h"
#implementation BezierView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)rect
{
NSBezierPath *control1 = [NSBezierPath bezierPath];
[control1 moveToPoint: points[0]];
[control1 lineToPoint: points[1]];
[[NSColor redColor] setStroke];
[control1 setLineWidth: 2];
[control1 stroke];
NSBezierPath *control2 = [NSBezierPath bezierPath];
[control2 moveToPoint: points[2]];
[control2 lineToPoint: points[3]];
[[NSColor greenColor] setStroke];
[control2 setLineWidth: 2];
[control2 stroke];
NSBezierPath *curve = [NSBezierPath bezierPath];
[curve moveToPoint: points[0]];
[curve curveToPoint: points[3]
controlPoint1: points[1]
controlPoint2: points[2]];
[[NSColor blackColor] setStroke];
CGFloat pattern[] = {4, 2, 1, 2};
[curve setLineDash: pattern
count: 4
phase: 1];
[[NSColor grayColor] setFill];
[curve fill];
[curve stroke];
}
- (void)mouseDown: (NSEvent*)theEvent
{
NSPoint click = [self convertPoint: [theEvent locationInWindow]
fromView: nil];
points[pointCount++ % 4] = click;
if (pointCount % 4 == 0)
{
[self setNeedsDisplay: YES];
}
}
#end

Multiple instances of a method?

The following code renders a zombie with interchangeable coordinates. The only issue is that I have to duplicate the code and create new coordinate variables myself for each zombie. Is there any way I can create copies of the variables so I only have to have one method for multiple zombies verses three methods and three separate variables for three zombies?
int zombieyCord;
int zombiexCord;
-(void)renderZombie
{
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth:4];
NSPoint center = {zombieyCord,zombiexCord};
[path moveToPoint: center];
[path appendBezierPathWithArcWithCenter:center
radius:5
startAngle:0
endAngle:360];
[[NSColor greenColor] set];
[path fill];
[[NSColor greenColor] set];
[path stroke];
}
Edit:
The spawning is great and works perfectly, but I want the zombies to move properly. Before, the code was that the method was called upon the player moving. This no longer works because I have to call an integer in the method.
+(void)entityZombie:(int)zombiexCord andY:(int)zombieyCord
{
/* Handle Zombie Movement*/
if (xcord != zombiexCord || ycord != zombieyCord)
{
if (xcord > zombiexCord){zombiexCord=zombiexCord+1;}
if (xcord < zombiexCord){zombiexCord=zombiexCord-1;}
if (ycord < zombieyCord){zombieyCord=zombieyCord-1;}
if (ycord > zombieyCord){zombieyCord=zombieyCord+1;}
/* Handle Zombie Render*/
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth:4];
NSPoint center = {zombieyCord,zombiexCord};
[path moveToPoint: center];
[path appendBezierPathWithArcWithCenter:center
radius:5
startAngle:0
endAngle:360];
[[NSColor greenColor] set];
[path fill];
[[NSColor greenColor] set];
[path stroke];
}
}
Player movement handling just involves rendering the player then [Entity entityZombie];
But as said above, no longer works.
-(void)renderZombieWithX:(int)xCoord andY:(int)yCoord {
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth:4];
NSPoint center = {xCoord,yCoord};
[path moveToPoint: center];
[path appendBezierPathWithArcWithCenter:center
radius:5
startAngle:0
endAngle:360];
[[NSColor greenColor] set];
[path fill];
[[NSColor greenColor] set];
[path stroke];
}
Call it like this:
[self renderZombieWithX:10 andY:20];
[self renderZombieWithX:13 andY:37];
//...
Further more there is a dedicated function for creating NSPoints:
NSPoint center = NSMakePoint(xCoord, yCoord);
which you might want to use in favor of:
NSPoint center = {xCoord,yCoord};
(see one of my answer's comments on why)
If performance is critical a cached approach like the following might bring performance gain:
static NSImage *sharedZombieStamp;
- (NSImage *)sharedZombie {
#synchronized(sharedZombieStamp) {
if (!sharedZombieStamp) {
CGFloat lineWidth = 4.0;
CGFloat radius = 5.0;
sharedZombieStamp = [[NSImage alloc] initWithSize:NSMakeSize((lineWidth + radius) * 2, (lineWidth + radius) * 2];
[zombieImage lockFocus];
NSBezierPath *path = [NSBezierPath bezierPath];
[path setLineWidth:4];
NSPoint center = NSMakePoint(lineWidth + radius, lineWidth + radius);
[path moveToPoint: center];
[path appendBezierPathWithArcWithCenter:center
radius:radius
startAngle:0
endAngle:360];
[[NSColor greenColor] set];
[path fill];
[[NSColor greenColor] set];
[path stroke];
[zombieImage unlockFocus];
}
}
return sharedZombieStamp;
}
-(void)renderZombieWithX:(int)xCoord andY:(int)yCoord {
NSImage *zombie = [self sharedZombie];
NSSize zombieSize = [zombie size];
[zombie drawAtPoint:NSMakePoint(xCoord - zombieSize.width / 2, yCoord - zombieSize.height / 2)
fromRect:NSMakeRect(0.0, 0.0, zombieSize.width, zombieSize.height)
operation:NSCompositeSourceOver
fraction:1.0];
}
This code is just out of my head though. Went thru no testing, whatsoever. Use with caution ;)