Reputation: 16764
I have a UIView
added in code at run time.
I want to draw a UIBezierPath
in it, but does this means i have to override the drawRect
for UIView?
Or is there another way of drawing to it on the custom made UIView
?
Here is the code for generating the UIView
:
UIView* shapeView = [[UIView alloc]initWithFrame:CGRectMake(xOrigin,yOrigin+(i*MENU_BLOCK_FRAME_HEIGHT), self.shapeScroll.frame.size.width, MENU_BLOCK_FRAME_HEIGHT)];
shapeView.clipsToBounds = YES;
And here is the function to create and return a UIBezierPath
:
- (UIBezierPath*)createPath
{
UIBezierPath* path = [[UIBezierPath alloc]init];
[path moveToPoint:CGPointMake(100.0, 50.0)];
[path addLineToPoint:CGPointMake(200.0,50.0)];
[path addLineToPoint:CGPointMake(200.0, 200.0)];
[path addLineToPoint:CGPointMake(100.0, 200.0)];
[path closePath];
return path;
}
Upvotes: 77
Views: 84634
Reputation: 131398
As the other posters pointed out, using a shape layer is a good way to go.
Shape layers are likely to give you better performance than overriding drawRect.
If you want to draw your path yourself then yes, you need to override drawRect for your custom view class.
Upvotes: 3
Reputation: 105
Drawing UIBezierPath on code generated UIView, you can use 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 UIBezierPath using UIView Touchevents as below:
/*Touch Start*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
startingPoint = [touch locationInView:self];
}
/*Touch End*/
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
/*Touch Move*/
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
endingPoint = [touch locationInView:self];
[self makeLineLayer:self.layer lineFromPointA:startingPoint
toPointB:endingPoint];
}
/*Draw UIBezierPath*/
-(void)makeLineLayer:(CALayer *)layer lineFromPointA:(CGPoint)pointA
toPointB:(CGPoint)pointB
{
CAShapeLayer *line = [CAShapeLayer layer];
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];// Start Point
[linePath addLineToPoint:pointB];//End Point
line.path=linePath.CGPath;
line.fillColor = nil;
line.opacity = 2.0;
line.lineWidth = 4.0;
line.strokeColor = [UIColor redColor].CGColor;
[layer addSublayer:line];
}
Upvotes: 0
Reputation: 511558
It wasn't long ago that I didn't even know how to pronounce Bézier, let alone know how to use Bézier paths to make a custom shape. The following is what I have learned. It turns out that they aren't as scary as they seem at first.
These are the main steps:
drawRect
or using a CAShapeLayer
.You could do anything, but as an example I have chosen the shape below. It could be a popup key on a keyboard.
Look back at your shape design and break it down into simpler elements of lines (for straight lines), arcs (for circles and round corners), and curves (for anything else).
Here is what our example design would look like:
We'll arbitrarily start in the bottom left corner and work clockwise. I'll use the grid in the image to get the x and y values for the points. I'll hardcode everything here, but of course you wouldn't do that in a real project.
The basic process is:
UIBezierPath
moveToPoint
addLineToPoint
addArcWithCenter
addCurveToPoint
closePath
Here is the code to make the path in the image above.
func createBezierPath() -> UIBezierPath {
// create a new path
let path = UIBezierPath()
// starting point for the path (bottom left)
path.move(to: CGPoint(x: 2, y: 26))
// *********************
// ***** Left side *****
// *********************
// segment 1: line
path.addLine(to: CGPoint(x: 2, y: 15))
// segment 2: curve
path.addCurve(to: CGPoint(x: 0, y: 12), // ending point
controlPoint1: CGPoint(x: 2, y: 14),
controlPoint2: CGPoint(x: 0, y: 14))
// segment 3: line
path.addLine(to: CGPoint(x: 0, y: 2))
// *********************
// ****** Top side *****
// *********************
// segment 4: arc
path.addArc(withCenter: CGPoint(x: 2, y: 2), // center point of circle
radius: 2, // this will make it meet our path line
startAngle: CGFloat(M_PI), // π radians = 180 degrees = straight left
endAngle: CGFloat(3*M_PI_2), // 3π/2 radians = 270 degrees = straight up
clockwise: true) // startAngle to endAngle goes in a clockwise direction
// segment 5: line
path.addLine(to: CGPoint(x: 8, y: 0))
// segment 6: arc
path.addArc(withCenter: CGPoint(x: 8, y: 2),
radius: 2,
startAngle: CGFloat(3*M_PI_2), // straight up
endAngle: CGFloat(0), // 0 radians = straight right
clockwise: true)
// *********************
// ***** Right side ****
// *********************
// segment 7: line
path.addLine(to: CGPoint(x: 10, y: 12))
// segment 8: curve
path.addCurve(to: CGPoint(x: 8, y: 15), // ending point
controlPoint1: CGPoint(x: 10, y: 14),
controlPoint2: CGPoint(x: 8, y: 14))
// segment 9: line
path.addLine(to: CGPoint(x: 8, y: 26))
// *********************
// **** Bottom side ****
// *********************
// segment 10: line
path.close() // draws the final line to close the path
return path
}
Note: Some of the above code can be reduced by adding a line and an arc in a single command (since the arc has an implied starting point). See here for more details.
We can draw the path either in a layer or in drawRect
.
Method 1: Draw path in a layer
Our custom class looks like this. We add our Bezier path to a new CAShapeLayer
when the view is initialized.
import UIKit
class MyCustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
// Create a CAShapeLayer
let shapeLayer = CAShapeLayer()
// The Bezier path that we made needs to be converted to
// a CGPath before it can be used on a layer.
shapeLayer.path = createBezierPath().cgPath
// apply other properties related to the path
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.position = CGPoint(x: 10, y: 10)
// add the new layer to our custom view
self.layer.addSublayer(shapeLayer)
}
func createBezierPath() -> UIBezierPath {
// see previous code for creating the Bezier path
}
}
And creating our view in the View Controller like this
override func viewDidLoad() {
super.viewDidLoad()
// create a new UIView and add it to the view controller
let myView = MyCustomView()
myView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)
myView.backgroundColor = UIColor.yellow
view.addSubview(myView)
}
We get...
Hmm, that's a little small because I hardcoded all the numbers in. I can scale the path size up, though, like this:
let path = createBezierPath()
let scale = CGAffineTransform(scaleX: 2, y: 2)
path.apply(scale)
shapeLayer.path = path.cgPath
Method 2: Draw path in draw
Using draw
is slower than drawing to the layer, so this is not the recommended method if you don't need it.
Here is the revised code for our custom view:
import UIKit
class MyCustomView: UIView {
override func draw(_ rect: CGRect) {
// create path (see previous code)
let path = createBezierPath()
// fill
let fillColor = UIColor.white
fillColor.setFill()
// stroke
path.lineWidth = 1.0
let strokeColor = UIColor.blue
strokeColor.setStroke()
// Move the path to a new location
path.apply(CGAffineTransform(translationX: 10, y: 10))
// fill and stroke the path (always do these last)
path.fill()
path.stroke()
}
func createBezierPath() -> UIBezierPath {
// see previous code for creating the Bezier path
}
}
which gives us the same result...
I really recommend looking at the following materials. They are what finally made Bézier paths understandable for me. (And taught me how to pronounce it: /ˈbɛ zi eɪ/.)
Upvotes: 307
Reputation: 77621
You can use a CAShapeLayer
to do this.
Like this...
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [self createPath].CGPath;
shapeLayer.strokeColor = [UIColor redColor].CGColor; //etc...
shapeLayer.lineWidth = 2.0; //etc...
shapeLayer.position = CGPointMake(100, 100); //etc...
[self.layer addSublayer:shapeLayer];
This will then add and draw the path without having to override drawRect
.
Upvotes: 17
Reputation: 20021
Yes , You have to override the drawrect if you want to draw anything.Creating a UIBezierPath can be done anywhere ,But to draw something you have to do it inside the drawrect
method
You should be calling setNeedsDisplay
if you override drawRect in a subclass of UIView which is basically a custom view drawing something on the screen, like lines,image , rectangle.
Upvotes: 1
Reputation: 3369
There are multiple ways to accomplish what you wish. The ones I've seen most are: override drawRect, draw your shape into a CAShapeLayer and then add it as a sublayer to your view, or draw your path onto another context, save that out as an image, and then add it to your view.
All of these are reasonable choices, and which one is best depends on many other factors such as are you going to be continually adding shapes, how often it's called, etc.
Upvotes: 6
Reputation: 25907
It would be easier if you would use a CAShapeLayer
, like this:
CAShapeLayer *shapeView = [[CAShapeLayer alloc] init];
And set its path
:
[shapeView setPath:[self createPath].CGPath];
Finally add it:
[[self.view layer] addSublayer:shapeView];
Upvotes: 54