How to draw shapes like circles, oval, ractangles dynamically in iOS? - ios

I have a UICollectionView of showing shapes like circles, oval, ractangles. So when I clicked on Cell I need to draw same shape on UIView and after I need to move and resize that view which was drawn by clicking in Cell. How it can achieve in iOS ? Thanks in advance.

If you use a UICollectionView, your reusable cell will have the "layer" to draw on.
What to do?
1. Create a UIView subclass and place it inside your reusable cell.
2. Override drawRect(_:) method, you'll do all the drawing inside.
3. Add your shape/line drawing code to drawRect method.
For example use UIBezierPath class for lines, arcs etc. You'll be able to create all sorts of shapes.
You should read the CoreGraphics API Ref:
https://developer.apple.com/reference/coregraphics
also learn about Layers, Contexts and drawRect:
A very basic example of a circle:
class CircleView: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
context.addEllipse(in: rect)
context.setFillColor(.red.cgColor)
context.fillPath()
}
}
and drawing lines (rects, stars etc.) with UIBezier paths:
override func drawRect(rect: CGRect) {
var path = UIBezierPath()
path.moveToPoint(CGPoint(x:<point x>, y:<point y>))
path.addLineToPoint(CGPoint(x:<next point x>, y:<next point y>))
point.closePath()
UIColor.redColor().set()
point.stroke()
point.fill()
}

study about bezier path. It is possible with this. hope it will work for you.

You can use CAShapeLayer. eg. Draw a circle.
shapeLayer = [CAShapeLayer layer];
shapeLayer.frame = aFrame;
UIBezierPath *spath = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:12 startAngle:0 endAngle:(2 * M_PI) clockwise:YES];
shapeLayer.strokeColor = [UIColor colorWithRed:71/255.0 green:157/255.0 blue:252/255.0 alpha:1].CGColor;
shapeLayer.path = spath.CGPath;
shapeLayer.strokeStart = 0;
shapeLayer.strokeEnd = 0;
shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 2;
[self.layer addSublayer:shapeLayer];
I hope it help you

You can use layer's properties:
cornerRadius
borderWidth
borderColor

Related

Using CAShapeLayer and removeFromSuperLayer

I am making an app, which should draw object from touch points.
I am using CAShapeLayerand UIBezierPath, but when I use removeFromSuperlayer() nothing happens.
I need to delete old shapes and draw just the new one.
I do not know why, but after one build and upload this app to my iPad, Xcode give me an error:
Cannot use optional chaining on non-optional value of type CAShapeLayer
Anyone who can help me?
And another question is:
How to draw just one line?
When I have 2 points, it draws nothing, the app draws just object after 3 points and more.
Here is a part of my code:
private func drawObj(){
let objectPath = UIBezierPath()
objectPath.move(to: CGPoint(x: pointsX[0], y: pointsY[0]))
let xx = pointsX.count - 1
print(xx)
for i in 1...xx {
objectPath.addLine(to: CGPoint(x: pointsX[i], y: pointsY[i]))
}
objectPath.close()
let object = CAShapeLayer()
object.removeFromSuperlayer()
object.path = objectPath.cgPath
object.fillColor = UIColor.red.cgColor
object.opacity = 0.2
self.view.layer.addSublayer(object)
}
If you create a new CAShapeLayer, there's no point in calling removeFromSuperlayer on that layer. You only do that for layers that have previously been added as sublayer of some other layer. So, don't call removeFromSuperlayer using this new layer that you just created, but save a reference to the previous one. That's the one that you should remove.
Or, as Dmitry pointed out, you should just add the shape layer once, and just update its path:
private var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.opacity = 0.2
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 1.0
return shapeLayer
}()
// add this to your view's layer in the logical place, e.g. `viewDidLoad`
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(shapeLayer)
}
// now your draw method only needs to update the path
private func drawObj() {
let objectPath = UIBezierPath()
objectPath.move(to: CGPoint(x: pointsX[0], y: pointsY[0]))
for i in 1 ..< pointsX.count {
objectPath.addLine(to: CGPoint(x: pointsX[i], y: pointsY[i]))
}
objectPath.close()
shapeLayer.path = object.cgPath
}
Note, I've also updated the lineWidth and the strokeColor of the shape layer so that if there are only two points and there is nothing to "fill", you can at least see the stroked path.
There is no need to remove the old CAShapeLayer from the parent layer and inserting a new one. Just create one CAShapeLayer, insert it to the parent layer. And then just update the 'path' parameter of it when you need to change it.

Filing UIBezierPath in Swift

How can I color the inside of the UIBezier path? I am making a simple triangle and want to color inside of the triangle with red color.
class GraphView : UIView {
override func drawRect(rect: CGRect) {
UIColor.redColor().setFill()
UIColor.greenColor().setStroke()
let path = UIBezierPath(rect: rect)
path.moveToPoint(CGPointMake(100,620))
path.addLineToPoint(CGPointMake(300,100))
path.addLineToPoint(CGPointMake(500,620))
path.addLineToPoint(CGPointMake(100,620))
path.closePath()
path.fill()
path.stroke()
}
}
let graphView = GraphView(frame: CGRectMake(0,0,960,640))
XCPlaygroundPage.currentPage.liveView = graphView
The above code is written in the playground: Here is the result:
The problem is with how you're creating your UIBezierPath. You're creating it with a rectangle of the view's bounds, and then adding a triangle inside that rectangle. Therefore they're both getting filled – resulting in the entire view being red.
If you set usesEvenOddFillRule to true, you'll better see the result of the unwanted rectangle in your path:
If you just want to fill a triangle then replace this line:
let path = UIBezierPath(rect: rect)
with this:
let path = UIBezierPath()
This will create a new empty UIBezierPath that you can add your triangle to.

Set bool for UIButton Class in View controller (Swift)

I have a Custom Class for my UIButton. In this class I have the code below to draw the button. I would like the user to tap a button and toggle one part hidden/ not hidden. I don't know how to set the Bool value for this. Thanks
import UIKit
class CheckboxChecked: UIButton {
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
func drawTicked(#showTick: Bool) {
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(3, 3, 34, 34), cornerRadius: 10)
UIColor.darkGrayColor().setStroke()
rectanglePath.lineWidth = 3
rectanglePath.stroke()
if (showTick) {
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(9.5, 19.5))
bezierPath.addLineToPoint(CGPointMake(18.5, 26.5))
bezierPath.addLineToPoint(CGPointMake(30.5, 10.5))
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.darkGrayColor().setStroke()
bezierPath.lineWidth = 3
bezierPath.stroke()
}
}
override func drawRect(rect: CGRect) {
// Drawing code
}
}
I would suggest that instead you make a new UIView with an internal UIButton component - as well as what ever other component you want to hide.
You can then add a click handler to your UIButton portion of the control which would then control the .visible state of the other component. In fact you could probably mock this up in a Playground.
And if you wanted to get really fancy you could look at #IBInspectible Check out this post on custom components: https://www.weheartswift.com/make-awesome-ui-components-ios-8-using-swift-xcode-6/
I managed to achieve this by using the code below.
I have a UIButton class with this:
override func drawRect(rect: CGRect) {
// Drawing code
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(3, 3, 34, 34), cornerRadius: 10)
UIColor.darkGrayColor().setStroke()
rectanglePath.lineWidth = 3
rectanglePath.stroke()
if (darwCheck == true) {
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(9.5, 19.5))
bezierPath.addLineToPoint(CGPointMake(18.5, 26.5))
bezierPath.addLineToPoint(CGPointMake(30.5, 10.5))
bezierPath.lineCapStyle = kCGLineCapRound;
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.darkGrayColor().setStroke()
bezierPath.lineWidth = 3
bezierPath.stroke()
}
}
And in the ViewController I had a button that changed a global Boolean variable and redrew the button.
drawCheck = true
checkboxButton.setNeedsDisplay()

How to draw horizontal bar graphs without showing axises

I need to implement a horizontal bar graph similar to the one shown below. Here I don't need to show axises along with the graph, still I need to imagine there is one:
How can I achieve this?
You can easily do this by using
UIBezierPath with CAShapeLayer
You can use the following function to draw an horizontal bar
func drawLine(startpoint start:CGPoint, endpint end:CGPoint, linecolor color: CGColor , linewidth widthline:CGFloat){
var path = UIBezierPath()
path.moveToPoint(start)
path.addLineToPoint(end)
var shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = color
shapeLayer.lineWidth = widthline
view.layer.addSublayer(shapeLayer)
}
How to call this function in swift is
let start = CGPoint(x:20,y:100)
let end = CGPoint(x:200,y:100)
//red part of line
drawLine(startpoint: start, endpint: end,linecolor: UIColor.redColor().CGColor,linewidth:11.0)
You can find out complete code in here
http://bestarticlesall.blogspot.com/2014/12/draw-horizontal-bar-chart-using-swift.html

Gradient mask on UIView

I have a UIView and want to have the bottom part of it fade out to 0 opacity.
May this be done to a UIView (CALayer) in order to affect an entire UIView and its content?
Yes, you can do that by setting CAGradientLayer as your view's layer mask:
#import <QuartzCore/QuartzCore.h>
...
CAGradientLayer *maskLayer = [CAGradientLayer layer];
maskLayer.frame = view.bounds;
maskLayer.colors = #[(id)[UIColor whiteColor].CGColor, (id)[UIColor clearColor].CGColor];
maskLayer.startPoint = CGPointMake(0.5, 0);
maskLayer.endPoint = CGPointMake(0.5, 1.0);
view.layer.mask = maskLayer;
P.S. You also need to link QuartzCore.framework to your app to make things work.
Using an Image as the mask to prevent Layout issues.
Although Vladimir's answer is correct, the issue is to sync the gradient layer after view changes. For example when you rotate the phone or resizing the view. So instead of a layer, you can use an image for that:
#IBDesignable
class MaskableLabel: UILabel {
var maskImageView = UIImageView()
#IBInspectable
var maskImage: UIImage? {
didSet {
maskImageView.image = maskImage
updateView()
}
}
override func layoutSubviews() {
super.layoutSubviews()
updateView()
}
func updateView() {
if maskImageView.image != nil {
maskImageView.frame = bounds
mask = maskImageView
}
}
}
Then with a simple gradient mask like this, You can see it even right in the storyboard.
Note:
You can use this class and replace UILabel with any other view you like to subclass.
🎁 Bones:
You can use any other shaped image as the mask just like that.

Resources