how to draw UIBezierPaths - ios

Here's what I want to do:
I have a UIBezierPath and I want to pass it to some method for it to be drawn. Or simply draw it from the method in which it is created.
I'm not sure how to indicate which view it should be drawn in. Do all methods for drawing have to start with
- (void)drawRect:(CGRect)rect { ...} ?
can I do
- (void)drawRect:(CGRect)rect withBezierPath:(UIBezierPath*) bezierPath { ... } ??
How do I call this function, or method, from another method?

drawRect: is something that is invoked automatically when you message setNeedsDisplay or setNeedsDisplayInRect: on a view. You never call drawRect: directly.
However you are right in saying that all drawing operations are done within the drawRect: method. Typical implementation would be,
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
/* Do your drawing on `context` */
}
Since you are using UIBezierPaths, you will need to maintain an array of bezier paths that you will need to draw and then call setNeedsDisplay when something changes.
- (void)drawRect:(CGRect)rect {
for ( UIBezierPath * path in bezierPaths ) {
/* set stroke color and fill color for the path */
[path fill];
[path stroke];
}
}
where bezierPaths is an array of UIBezierPaths.

First, save your path in an ivar
#interface SomeView {
UIBezierPath * bezierPath;
}
#property(nonatomic,retain) UIBezierPath * bezierPath;
...
#end
....
- (void)someMethod {
self.bezierPath = yourBezierPath;
[self setNeedsDisplayInRect:rectToRedraw];
}
in -drawRect:
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(currentContext, 3.0);
CGContextSetLineCap(currentContext, kCGLineCapRound);
CGContextSetLineJoin(currentContext, kCGLineJoinRound);
CGContextBeginPath(currentContext);
CGContextAddPath(currentContext, bezierPath.CGPath);
CGContextDrawPath(currentContext, kCGPathStroke);
}

When you need to custom your view, you can overwrite -drawRect: on the subclass:
- (void)drawRect:(CGRect)rect
{
// config your context
[bezierPath stroke];
}
Edit: directly using -stroke make code more compact.

Drawing only happens inside a method called -drawRect: (which is automatically called when a view is marked as needing display via setNeedsDisplay). So a drawRect:withBezierPath: method will never get invoked automatically. The only way it will execute is if you call it yourself.
Once you have a UIBezierPath, however, it's very easy to draw it:
- (void)drawRect:(CGRect)rect {
UIBezierPath *path = ...; // get your bezier path, perhaps from an ivar?
[path stroke];
}
There's no need to futz around with Core Graphics if all you want to do is draw a path.

you can do something like following. just define a UIColor *setStroke; in .h file and you need to set this strokeColor object before your you call [myPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
- (void)drawRect:(CGRect)rect
{
[strokeColor setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
for(UIBezierPath *_path in pathArray)
[myPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
myPath=[[UIBezierPath alloc]init];
myPath.lineWidth = currentSliderValue;
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[pathArray addObject:myPath];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}

Related

Drawing to my own UIView with drawRect

Not so much to say here. Just that, no STROKE is being added to my UIView.
#import "DrawingLayerView.h"
#implementation DrawingLayerView
UIBezierPath *newPath;
- (void)startTouch:(CGPoint)point;
{
[newPath moveToPoint:point];
}
- (void)drawRect:(CGRect)rect {
[[UIColor redColor] setStroke];
[newPath stroke];
}
- (void)drawShape:(CGPoint)point
{
[newPath addLineToPoint: point]; // (4)
[self setNeedsDisplay];
}
- (void)endTouch:(CGPoint)point {
[newPath removeAllPoints];
}
#end
DrawingLayerView.h is a custom class for my UIView.
The drawRect function is for sure called, but no strokes are being made.
If more information is necessary. Just tell me and I'll get it !
At least one thing required for the code to work is the initialization of newPath. On start touch: self.newPath = [UIBezierPath bezierPath]; (make it a property to use self.newPath, otherwise you're implicitly declaring it static).
You might want to show how/where you are calling drawShape. There's nothing here that would call it. Also, assuming you are doing that, you don't appear to ever instantiate a UIBezierPath, e.g.:
- (void)startTouch:(CGPoint)point // note, no semicolon
{
newPath = [UIBezierPath bezierPath]; // note, create a `UIBezierPath` before you try to `moveToPoint`, or later, `addLineToPoint`
[newPath moveToPoint:point];
}

Redraw a rect using UIBezierPath

I draw an initial simple rect using UIBezierPath and fill it with color. How can I change only its color by touching on it?
- (void) drawRect:(CGRect)rect
{
// Drawing code
[self drawRectWithFrame:_myRect fillColor:_firstColor];
}
- (void) drawRectWithFrame:(CGRect)frame fillColor:(UIColor *)color
{
[color setFill];
UIBezierPath *path = [UIBezierPath bezierPathWithRect:frame];
[path fill];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//get the rect color from touches (pixel color)
UIColor *color = [self getRectColorFromTouches:touches];
//chnage the rect color
if (color == _firstColor) {
//doesn't work
//[self drawRectWithFrame:_myRect fillColor:_secondColor]; //??
//How can I do that?
}
else {
//??
}
}
Some remarks :
Instead of overriding UIView touch event methods, you should use a UIGestureRecognizer. In your case, a UItapGestureRecognizer might be enough. (this way, it will be easier to handle conflict between different touch actions, that's the reason why gestureRecognizers were created!)
When you receive tap, change a local property of your view (for example, a BOOL isRectangleDrawn might be changed each time the view receives the tap)
Finally - and that's what missing in your code, that otherwise should be correct - don't forget to call [self setNeedsDisplay] to be sure your view's - (void) drawRect:(CGRect)rect method gets called

UIBezier path not responding to stroke

I am using the following code to draw a UIBezierPath on a UIImageView in a UIView.
But it is not showing a green path.
- (void)drawRect:(CGRect)rect
{
[[UIColor blackColor] setStroke];
[aPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
self->aPath = [[UIBezierPath alloc]init];
CAShapeLayer* greenPath = [CAShapeLayer layer];
greenPath.path = aPath.CGPath;
[greenPath setFillColor:[UIColor greenColor].CGColor];
[greenPath setStrokeColor:[UIColor blueColor].CGColor];
greenPath.frame=CGRectMake(0, 0,100,30);
//add shape layer to view's layer
[[imgView layer] addSublayer:greenPath];
aPath.lineCapStyle=kCGLineCapRound;
aPath.miterLimit=0;
aPath.lineWidth=10;
[aPath moveToPoint:[mytouch locationInView:imgView]];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[aPath addLineToPoint:[mytouch locationInView:imgView]];
[imgView setNeedsDisplay];
}
I need to show a green line wherever the UIBezierPath is drawn.
There's a lot of weird stuff going on in your code, but it runs, the problem is here
[imgView setNeedsDisplay];
As I see in your code, you want it to work on drawRect, but drawRect will only get called on setNeedsDisplay on the current view, not on imgView, switch it for
[self setNeedsDisplay];
And it will work. You'd probably need to subclass UIImageView to handle drawRect inside of it. Also, I'm guessing that you want to actually modify the image, hence the attempt to draw within in, try reading on working with CoreGraphics in an image context.
About the other issues
The main problem as I see it, is that you're confusing CALayers and CoreGraphics, as it stands right now, the CALayer you're adding to the imageView is completely unused. Just with the drawRect and setting the BezierPath as you move around should work.
Also, be careful when using ->, understand what you're getting yourself into, I usually suggest sticking to #property, and using self.myVar and _myVar.

UIBezierPath draw line representation

I wrote a simple drawing code using UIBezierPath and the -touches... methods of UIView. The drawing works great, but I made some bad experiences.
When I'm painting very slow, it works great. But the more fast I draw, the more edgy the line gets. So how do I like "smooth" them to become less edgy (more points)?
When I use setLineWidth with high line widths, the line become very ugly.
Here is an image to show what ugly actually means:
Why does it draw fat line in that way?!
EDIT: here some code
- (void)drawInRect:(CGRect)rect
{
for(UIBezierPath *path in pathArray) {
[[UIColor blackColor] setStroke];
[path setLineWidth:50.0];
[path stroke];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
startPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
[path addLineToPoint:[[touches anyObject] locationInView:self]];
if(isDrawing) {
[pathArray removeLastObject];
}
else {
isDrawing = YES;
}
[pathArray addObject:path];
[path close];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
isDrawing = NO;
}
I hope that somebody can help me with these issues. THANKS a lot, greets, Julian
Mmmmh, I'm not going to address performance issues with your implementation in this thread, but take a look at setNeedsDisplayInRect: in UIView once you get the basics working.
I think you're basically trying to take out the last created path from your array and replace it with a new one for as long as you're moving.
You should try to put a CGMutablePathRef in your array instead (take a look here for that CGMutablePathRef to NSMutableArray).
The basic flow would be something like:
touchesBegan:
Create the CGMutablePathRef
Call moveToPoint
Store in the array
touchesMoved:
Get the lastObject from your array (i.e. the CGMutablePathRef)
Call addLineToPoint
[self setNeedsDisplay]
Then in drawInRect, iterate through your paths and paint them.
Again, this will be slow at first, then you need to optimize. CGLayerRef can help. setNeedsDisplayInRect most definitely will also.

How to draw and interact with UIBezierPath

I would like to implement and design a map for a building floor in my application. Before starting, I would like to have some advices.
I'm planning to use UIBezierPath to draw the shapes. Each UIBezierPath will represent a shop on my map.
Here is an illustration (map_with_UIBezierPath)
My code structure is the following: I have a UIViewController And a UiView. In the UIViewController "viewDidLoad" method, I instantiate the UIView and in the UIView "drawRect" method, I draw the shapes like following (UIBezierPathExtension inherit from UIBezierPath):
- (void)drawRect:(CGRect)rect {
context = UIGraphicsGetCurrentContext();
[[UIColor grayColor] setFill];
[[UIColor greenColor] setStroke];
UIBezierPathExtension *aPath = [[UIBezierPathExtension alloc] init];
aPath.pathId = 1;
[aPath moveToPoint:CGPointMake(227,34.25)];
[aPath addLineToPoint:CGPointMake(298.25,34.75)];
[aPath addLineToPoint:CGPointMake(298.5,82.5)];
[aPath addLineToPoint:CGPointMake(251,83)];
[aPath addLineToPoint:CGPointMake(251,67.5)];
[aPath addLineToPoint:CGPointMake(227.25,66.75)];
[aPath closePath];
aPath.lineWidth = 2;
[aPath fill];
[aPath stroke];
[paths addObject:aPath];
UIBezierPathExtension* aPath2 = [[UIBezierPathExtension alloc] init];
aPath2.pathId = 2;
[aPath2 moveToPoint:CGPointMake(251.25,90.5)];
[aPath2 addLineToPoint:CGPointMake(250.75,83.25)];
[aPath2 addLineToPoint:CGPointMake(298.5,83)];
[aPath2 addLineToPoint:CGPointMake(298.5,90.25)];
[aPath2 closePath];
aPath2.lineWidth = 2;
[aPath2 fill];
[aPath2 stroke];
[paths addObject:aPath2];
...
}
I have also implemented the pan and pinch gesture in the UIViewController.
Now, I'm asking me how I can interact with every single shapes. I would like to detect a single tap on it, change his color and display a menu like that on the selected shape.
Can someone tell me the right direction to take?
Thx in advance
You need to look for touch events (TouchesBegan, TouchesMoved, TouchesEnded, TouchesCancelled) in your view. When you get a touch, you can ask it for its location in your view. You can use this location to test whether that point is inside any of your paths, if it is, do your cool stuff.
Using your example code, here might be a rough TouchesBegan...
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint pointTouched = [touch locationInView:self];
for (UIBezierPath *path in paths) {
if ([path containsPoint:point]) {
// do something cool with your path
// or more likely, set a flag to do the cool thing when drawing
}
}
}
}
Don't forget that you should handle all the touch events and do something sensible with each. Also, the above code is multitouch capable, but you may want to only allow a single touch, in which case there are ways to eliminiate the "touches" loop.

Resources