I have a UIBezierPath:
CGPathRef tapTargetPath = CGPathCreateCopyByStrokingPath(tracePath.CGPath, NULL, marginError, tracePath.lineCapStyle, tracePath.lineJoinStyle, tracePath.miterLimit);
UIBezierPath *tracePath = [UIBezierPath bezierPathWithCGPath:tapTargetPath];
when it is touched in the method:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
I get a point. When i retrieve 2 points i want to get the segment(area or perimeter) of the UIBezierPath that i am touching, how can i accomplish this?
CGPathGetBoundingBox does not take into account the brush size and will cut the borders.
-(CGRect) getBiggerFrameFromPath:(CGPathRef) path{
CGRect tmpFrame = CGPathGetBoundingBox(path);
CGRect biggerFrame = CGRectMake(tmpFrame.origin.x-self.brushSize/2,
tmpFrame.origin.y-self.brushSize/2,
tmpFrame.size.width+self.brushSize,
tmpFrame.size.height+self.brushSize);
return biggerFrame;
}
-(void) refreshDisplayWithPath:(CGPathRef) path{
[self setNeedsDisplayInRect:[self getBiggerFrameFromPath:path]];
}
Use CGPathGetBoundingBox to get the bounding box of the path, i.e., the smallest rectangle enclosing all points in the path:
CGRect pathBounds = CGPathGetBoundingBox(tracePath.CGPath)
Related
Is there any way to change the UIBezierPath drawing shape ,see the below image it like a line when user drag the finger,but i want star ,circle and other is there any way to achieve that.
My Expectation is
This is my UIBezierPath code:
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
touchPoint = [touch locationInView:self];
if (!CGPointEqualToPoint(startingPoint, CGPointZero))
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(touchPoint.x,touchPoint.y)];
[path addLineToPoint:CGPointMake(startingPoint.x,startingPoint.y)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [single.arrColor[single.i] CGColor];
if([UIDevice currentDevice].userInterfaceIdiom ==UIUserInterfaceIdiomPad)
{
shapeLayer.lineWidth = 7.0;
}
else
{
shapeLayer.lineWidth = 5.0;
}
shapeLayer.fillColor = [[UIColor redColor] CGColor];
[self.layer addSublayer:shapeLayer];
[clearBeizer addObject:shapeLayer];
}
startingPoint=touchPoint;
// [arrLayer addObject:shapeLayer];
NSLog(#"Touch moving point =x : %f Touch moving point =y : %f", touchPoint.x, touchPoint.y);
}
Yes, it is doable but it is not trivial. What you essentially want is to stroke a path with stars instead of normal dashes. As far as I know, iOS only provides an API for a standard methods, i.e. stroking with a rectangular dash pattern.
If you want to implement custom stroking, you have to do it yourself. You probably have to flatten the bezier path first. Then "walk" along the path and draw stars/circle/squirrels at certain interval manually. It is especially difficult if you need the interval between the stars to be equal.
You can have a look at DrawKit library for MacOS. DrawKit is for MacOS, not iOS! This is just a reference for you to get the idea.
DrawKit has NSBezierPath+Geometry.h category on NSBezierPath class. You can start with (NSBezierPath*)bezierPathWithZig:(CGFloat)zig zag:(CGFloat)zag method and see how zig-zagy path is implemented
https://github.com/DrawKit/DrawKit/.../NSBezierPath-Geometry.m#L1206
or wavy path [(NSBezierPath*)bezierPathWithWavelength:amplitude:spread:]
https://github.com/DrawKit/DrawKit/..../NSBezierPath-Geometry.m#L1270
Just FYI: UIBezierPath (iOS) often lacking methods that are available in NSBezierPath (MacOS)
If DrawKit confuses you, there are probably open-sourced drawing libraries for iOS on the Internet, try searching them and see how custom drawing is done.
Yes You can do that. But You have to get the custom shaped icons for this task.
You can try this excellent answer provided by RobMayoff here: and the git repo
Here is another way to do it:
I have made a simple Image Editing App similar to what your are doing .
You can draw the heart shapes on the image:
Like that, you can draw many custom shapes:
The code is pretty straight forward and simple:
You need to create few custom shaped erasers. I call them eraser becuase they just erase the pic :P .
Here are the methods for customising the eraser:
- (void)newMaskWithColor:(UIColor *)color eraseSpeed:(CGFloat)speed {
wipingInProgress = NO;
eraseSpeed = speed; //how fast eraser should move
maskColor = color; //eraser color
[self setNeedsDisplay];
}
-(void)setErase:(UIImage *)img{
eraser =img; //set the custom shaped image here
}
And to draw the custom shaped eraser on view:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
wipingInProgress = YES;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if ([touches count] == 1) {
UITouch *touch = [touches anyObject];
location = [touch locationInView:self];
location.x -= [eraser size].width/2;
location.y -= [eraser size].width/2;
[self setNeedsDisplay];
}
}
and finally the draw rect method:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
if (wipingInProgress) {
if (imageRef) {
// Restore the screen that was previously saved
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, rect, imageRef);
CGImageRelease(imageRef);
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
}
[eraser drawAtPoint:location blendMode:kCGBlendModeDestinationOut alpha:eraseSpeed];
}
// Save the screen to restore next time around
imageRef = CGBitmapContextCreateImage(context);
}
Here are some variables declared in the .h file:
CGPoint location;
CGImageRef imageRef;
UIImage *eraser;
BOOL wipingInProgress;
UIColor *maskColor;
CGFloat eraseSpeed;
I am simply done this using UIImageView :
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:single.arrimages[single.colorimages]]];
imageView.frame = CGRectMake(touchPoint.x, touchPoint.y, 30,30);
[self addSubview:imageView];
just add image while user touch the screen.
get the x , y coordination of user touch and give it to uiimageview frame.
i Hope it will help for some one.
I'm using ACEDrawingView to draw within a view.
How would I detect the width and height of the drawing, so that I can crop around it, something like this:
Update: After #Duncan pointed me in the right direction, I was able to look through the source code and found the following:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// save all the touches in the path
UITouch *touch = [touches anyObject];
previousPoint2 = previousPoint1;
previousPoint1 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
if ([self.currentTool isKindOfClass:[ACEDrawingPenTool class]]) {
CGRect bounds = [(ACEDrawingPenTool*)self.currentTool addPathPreviousPreviousPoint:previousPoint2 withPreviousPoint:previousPoint1 withCurrentPoint:currentPoint];
CGRect drawBox = bounds;
drawBox.origin.x -= self.lineWidth * 2.0;
drawBox.origin.y -= self.lineWidth * 2.0;
drawBox.size.width += self.lineWidth * 4.0;
drawBox.size.height += self.lineWidth * 4.0;
self.drawingBounds = bounds; // I added this property to allow me to extract the bounds and use it in my view controller
[self setNeedsDisplayInRect:drawBox];
}
else if ([self.currentTool isKindOfClass:[ACEDrawingTextTool class]]) {
[self resizeTextViewFrame: currentPoint];
}
else {
[self.currentTool moveFromPoint:previousPoint1 toPoint:currentPoint];
[self setNeedsDisplay];
}
}
However I get this when I test the bounds:
I'm going to keep trying to figure it out, but if anyone could help that would be great!
Update 3: Using CGContextGetPathBoundingBox I was finally able to achieve it.
Every time you get a touchesMoved, record the location of the point you are now drawing. When you are all done, you have all the points. Now look at the largest x value and the smallest x value and the largest y value and the smallest y value in all of those points. That's the bounding box of the drawing.
Another approach (which you've already discovered) is to save the CGPath and then call CGContextGetPathBoundingBox. Basically that does exactly the same thing.
Note that a path has no thickness, whereas your stroke does. You will need to inset the bounding box negatively to allow for this (my screencast doesn't do that).
I'm not familiar with the AceDrawingView class. I can tell you how to do it with iOS frameworks though:
Create your path as a UIBezierPath.
Interrogate the bounds property of the path.
I'm using core text API to draw text on UI. (I don't know other methods, because most Google search results lead me to core text api).
I have read some tutorials online about using core text API. In those tutorial, I see step by step to config from matrix, attribute string to framesetter ... but none of them explain carefully meaning of each step, so I cannot modify by myself.
Below code is the function draw text on screen with (x,y) is the location which I want to draw. This piece of code describe clearly step by step do draw on screen. Nevertheless I don't know where to put x and y parameters, so text will start to draw at this point in rectangle.
// draw text on screen.
+ (void)drawText:(CGContextRef)context bound:(CGRect)rect text:(NSString *)text x:(float)x y:(float) y color:(UIColor *)color size:(float)textSize{
CGContextSaveGState(context);
// always remember to reset the text matrix before drawing.
// otherwise the result will be unpredictable like using uninitialize memory
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0f, -1.0f));
// Flip the coordinate system. because core Text uses different coordinate system with UI Kit
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// step 1: prepare attribute string
NSDictionary *attributes;
attributes = #{
(NSString *) kCTFontAttributeName : [UIFont fontWithName:#"Helvetica" size:textSize],
(NSString *) kCTForegroundColorAttributeName : color
};
NSAttributedString *str = [[NSAttributedString alloc]
initWithString:text attributes:attributes];
CFAttributedStringRef attrString = (__bridge CFAttributedStringRef)str;
// step 2: create CTFFrameSetter
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
// step 3. create CGPath
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, rect);
// step 4. get the frame
// use the CGPath and CTFrameSetter to create CTFrame. then drawn it in currently context
CTFrameRef frame = CTFramesetterCreateFrame
(framesetter, CFRangeMake(0, 0), path, NULL);
CTFrameDraw(frame, context);
// step 5. release resource
//CFRelease(attrString);
CFRelease(framesetter);
CFRelease(frame);
CFRelease(path);
CGContextRestoreGState(context);
}
Moreover, I see there are function: CGContextSetTextPosition This seem what I need, but where should I put in above code. I have tried some, but no succeed. Please tell me how to fix this.
Thanks :)
This is not CoreText solution but after your comment under your question I assume this would be a proper answer.
Basic text drawing on a view.
.h
#import <UIKit/UIKit.h>
#interface aView : UIView
#end
.m
#import "aView.h"
#implementation aView
- (void)drawText:(CGFloat)xPosition yPosition:(CGFloat)yPosition canvasWidth:(CGFloat)canvasWidth canvasHeight:(CGFloat)canvasHeight
{
//Draw Text
CGRect textRect = CGRectMake(xPosition, yPosition, canvasWidth, canvasHeight);
NSMutableParagraphStyle* textStyle = NSMutableParagraphStyle.defaultParagraphStyle.mutableCopy;
textStyle.alignment = NSTextAlignmentLeft;
NSDictionary* textFontAttributes = #{NSFontAttributeName: [UIFont fontWithName: #"Helvetica" size: 12], NSForegroundColorAttributeName: UIColor.redColor, NSParagraphStyleAttributeName: textStyle};
[#"Hello, World!" drawInRect: textRect withAttributes: textFontAttributes];
}
- (void)drawRect:(CGRect)rect {
[self drawText:0 yPosition:0 canvasWidth:200 canvasHeight:150];
}
#end
Starting with IOS 7 you can have the str render itself into the current context. To do this import UIKit. This will extend the defenition of NSAttributedString with drawAtPoint: method. So for example you might do something like this:
[str drawAtPoint:CGPointMake(x,y)]
For more info about this and other related methods please see the following:
https://developer.apple.com/library/ios/documentation/uikit/reference/NSAttributedString_UIKit_Additions/Reference/Reference.html#//apple_ref/occ/instm/NSAttributedString/drawAtPoint:
On draw text selected view you can used UIView touch events as below.
create global variable for Touch start point and Touch end Point as below
CGPoint startingPoint;
CGPoint endingPoint;
And then draw text or line as below
#pragma mark- Touch Event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
startingPoint = [touch locationInView:self];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
endingPoint = [touch locationInView:self];
[self makeLineLayer:self.layer lineFromPointA:startingPoint
toPointB:endingPoint];
}
-(void)makeLineLayer:(CALayer *)layer lineFromPointA:(CGPoint)pointA
toPointB:(CGPoint)pointB
{
CAShapeLayer *line = [CAShapeLayer layer];
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:pointB];
line.path=linePath.CGPath;
line.fillColor = nil;
line.opacity = 2.0;
line.lineWidth = 4.0;
line.strokeColor = [UIColor redColor].CGColor;
[layer addSublayer:line];
}
Although this thread is a bit old, but this is my work around for future visitors. This work requires a little bit modification and it starts moving the text to position (x, y).
You have to change step 3 like this:
// step 3. create CGPath
CGMutablePathRef path = CGPathCreateMutable();
CGRect newRect = rect;
newRect.origin.x = x;
newRect.origin.y = -y;
CGPathAddRect(path, NULL, newRect);
and the coordinate (x, y) will start working. The minus sign is due to the different axis of CoreTextAPI.
Also changed the TextMatrix to below for getting the correct transformation or it was giving a flipped Y-Axis text:
// always remember to reset the text matrix before drawing.
// otherwise the result will be unpredictable like using uninitialize memory
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0f, 1.0f));
Attached screenshot from IB (Designable):
Hope it helps!
I have a UIBezierPath made of an array of CGPoint´s using the function addCurveToPoint, here is a little of the code:
UIBezierPath *path = [[UIBezierPath alloc] init];
[path setLineWidth:10];
[path moveToPoint:pts[0]];
for(int i = 0; i<[array count]; i++) {
[path addCurveToPoint:[array objectAtIndex:i] controlPoint1:pts[1] controlPoint2:pts[2]];
}
When a touch is recognized on the view, i get the point of the touch and i need to compare it to the GCPoint´s of the UIBezierPath and say if it is inside of the UIBezierPath or not. What i have used is the function containsPoint but it didn´t work, here is my code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
CGPoint point = CGPointMake(p.x, p.y);
NSLog(#"actual point x %f y %f", point.x, point.y);
if ([path containsPoint:point]) {
NSLog(#"it contains the point");
}
}
At the end, i need to get all the set of the touched points and say if the touched points are inside of the UIBezierPath(or most of them) and add a margin error so the touch doesnt need to be so precisely, also how can i say how far is the point of the UIBezierPath?
Use CGPathCreateCopyByStrokingPath to create a thickly stroked version of your path (for example, with lineWidth set to 22). Then test whether the point is inside either of that stroked path or your original path.
Ole Begemann: CGPath Hit Testing
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.