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
Related
Given a CAShapeLayer defining a path as in the picture below, I want to add a CAGradientLayer that follows the path of the shape layer.
For example, given a gradient from black->red:
the top right rounded piece would be black,
and if the slider was at 100, the top left would be red,
and if the slider was at 50, then half the slider would be black (as below), and the visible gradient would go from black (top right) to a redish-black at the bottom
Every previous post I've found does not actually answer this question.
For example, because I can only add axial CAGradientLayers, I can kind-of do this (pic below), but you can see it's not correct (the top left ends up becoming black again). How do I make the gradient actually follow the path/mask
For the simple circular shape path, the new conic gradient is great. I was able to get this immediately (this is a conic gradient layer masked by a circle shape layer):
I used red and green so as to show the gradient clearly. This doesn't seem to be identically what you're after, but I can't believe it will be very difficult to achieve your goals now that .conic exists.
class MyGradientView : UIView {
override class var layerClass : AnyClass { return CAGradientLayer.self }
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
let lay = self.layer as! CAGradientLayer
lay.type = .conic
lay.startPoint = CGPoint(x:0.5,y:0.5)
lay.endPoint = CGPoint(x:0.5,y:0)
lay.colors = [UIColor.green.cgColor, UIColor.red.cgColor]
}
override func layoutSubviews() {
super.layoutSubviews()
let shape = CAShapeLayer()
shape.frame = self.bounds
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
let b = UIBezierPath(ovalIn: shape.frame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)))
shape.path = b.cgPath
shape.lineWidth = 10
shape.lineCap = .round
shape.strokeStart = 0.1
shape.strokeEnd = 0.9
shape.anchorPoint = CGPoint(x: 0.5, y: 0.5)
shape.transform = CATransform3DMakeRotation(-.pi/2, 0, 0, 1)
self.layer.mask = shape
}
}
Anybody on iOS 12 should go with Matt's suggestion or maybe use SFProgressCircle as DonMag suggested.
My solution:
I personally ended up adding two CAShapeLayers, each with a corresponding CAGradientLayer.
By programmatically splitting the slider in half as in the pic below, I was able to apply a top-to-bottom gradient on each side, which gives the effect I'm looking for. It's invisible to the user.
Currently i am adding the changes as subview to the original image. I want to add an eraser tool so that when the user draws on the screen, the subviews will be cleared and the underlined image will be seen . Same as an eraser tool in "Paint".Below is the code used for drawing a rectangle.How can i add an eraser tool using the same method. Thank you
case 2:
let path = UIBezierPath()
let sh = CAShapeLayer()
sh.strokeColor = selectedcolor.cgColor
sh.lineWidth = 3
sh.fillColor = UIColor.clear.cgColor
tempImage.layer.sublayers?.removeAll()
sh.fillColor = UIColor.clear.cgColor
path.move(to: startPoint)
sh.path = UIBezierPath(rect: rect).cgPath
tempImage.layer.addSublayer(sh)
if usermoving == 0
{
sh.setValue(tag, forKey: "tag")
mainImage.layer.addSublayer(sh)
}
break
Its Objective C Code
If you are using an image as background then you can set the same image as brush pattern to draw the bezierpath it will virtually give you the eraser effect.
Swift:-
UIColor(patternImage: UIImage(named: "background.png"))
I've been trying to solve this for days, and none of the suggestions solved my problem. I have a UIView inside a UITableView. I've been trying to draw my CAShapeLayer inside my UIView, but I just can't get it right.
I need my yellow CAShapeLayer to be exactly center within the gray UIView.
func DrawActivityRect(cell: CalorieDashboardTVCell){
let rectangle = UIBezierPath(rect: cell.calorieBurnUIView.bounds)
let trackLayer = CAShapeLayer()
trackLayer.frame = cell.calorieBurnUIView.bounds
trackLayer.path = rectangle.cgPath
trackLayer.position = cell.calorieBurnUIView.center
trackLayer.strokeColor = UIColor.yellow.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineCap = kCALineCapRound
cell.calorieBurnUIView.layer.addSublayer(trackLayer)
}
Result:
I suspect the cell size change after you have set the layer. The easiest place to fix this is in the cell's layoutSubviews(). Since your path is using absolute coordinates you need to update both the layer frame and the path.
override func layoutSubviews() {
super.layoutSubviews()
// You need to keep trackLayer around after you created it
let rectangle = UIBezierPath(rect: cell.calorieBurnUIView.bounds)
self.trackLayer.frame = cell.calorieBurnUIView.bounds
self.trackLayer.path = rectangle.cgPath
self.trackLayer.position = cell.calorieBurnUIView.center
}
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.
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