I need help creating this style BarChart, only the rounded end edges.
Here is an example:
I'm using the library "ios-charts" but it does not have this feature. Please let me know how to do this - rounded end edges and horizontal bar chart with negative values.
It's not that that hard to draw bars that like that yourself. Perhaps fork that library and improve it and make a push request. Here's some simple code that draws a red bar pointing to the right.
import UIKit
#IBDesignable
class RoundedBarView: UIView {
override func drawRect(rect: CGRect) {
let color = UIColor.redColor()
let barRect = CGRectMake(0.0,0.0,self.bounds.width, self.bounds.height)
var rectanglePath = UIBezierPath(roundedRect: barRect, byRoundingCorners: UIRectCorner.TopRight | UIRectCorner.BottomRight, cornerRadii: CGSizeMake(barRect.height/2.0, barRect.height/2.0))
rectanglePath.closePath()
color.setFill()
rectanglePath.fill()
}
}
Related
I'm using this approach to cut out a rounded rect "window" from a background view:
override func draw(_ rect: CGRect) {
guard let rectsArray = rectsArray else {
return
}
for holeRect in rectsArray {
let holeRectIntersection = rect.intersection(holeRect)
if let context = UIGraphicsGetCurrentContext() {
let roundedWindow = UIBezierPath(roundedRect: holeRect, cornerRadius: 15.0)
if holeRectIntersection.intersects(rect) {
context.addPath(roundedWindow.cgPath)
context.clip()
context.clear(holeRectIntersection)
context.setFillColor(UIColor.clear.cgColor)
context.fill(holeRectIntersection)
}
}
}
}
In layoutSubviews() I update the background colour add my "window frame" rect:
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = self.baseMoodColour
isOpaque = false
self.rectsArray?.removeAll()
self.rectsArray = [dragAreaView.frame]
}
I'm adding the rect here because layoutSubviews() updates the size of the "window frame" (i.e., the rect changes after layoutSubviews() runs).
The basic mechanism works as expected, however, if I change the background colour, the cutout window fills with black. So I'm wondering how I can animate a background colour change with this kind of setup? That is, I want to animate the colour of the area outside the cutout window (the window remains clear).
I've tried updating backgroundColor directly, and also using didSet in the accessor of a custom colour variable in my UIView subclass, but both cause the same filling-in of the "window".
var baseMoodColour: UIColor {
didSet {
self.backgroundColor = baseMoodColour
self.setNeedsDisplay()
}
}
Try to use UIView.animate, you can check it here
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveEaseOut], animations: {
self.backgroundColor = someNewColour
//Generally
//myView.backgroundColor = someNewColor
}, nil)
The problem in the short run is that that is simply what clear does if the background color is opaque. Just give your background color some transparency — even a tiny bit of transparency, so tiny that the human eye cannot perceive it — and now clear will cut a hole in the view.
For example, your code works fine if you set the view's background color to UIColor.green.withAlphaComponent(0.99).
By the way, you should delete the lines about UIColor.clear; that's a red herring. You should also cut the lines about the backgroundColor; you should not be repainting the background color into your context. They are two different things.
The problem in the long run is that what you're doing is not how to punch a hole in a view. You should be using a mask instead. That's the only way you're going to get the animation while maintaining the hole.
Answering my own question, based on #matt's suggestion (and linked example), I did it with a CAShapeLayer. There was an extra "hitch" in my requirements, since I have a couple of views on top of the one I needed to mask out. So, I did the masking like this:
func cutOutWindow() {
// maskedBackgroundView is an additional view, inserted ONLY for the mask
let r = self.maskedBackgroundView.bounds
// Adjust frame for dragAreaView's border
var dragSize = self.dragAreaView.frame.size
var dragPosition = self.dragAreaView.frame.origin
dragSize.width -= 6.0
dragSize.height -= 6.0
dragPosition.x += 3.0
dragPosition.y += 3.0
let r2 = CGRect(x: dragPosition.x, y: dragPosition.y, width: dragSize.width, height: dragSize.height)
let roundedWindow = UIBezierPath(roundedRect: r2, cornerRadius: 15.0)
let mask = CAShapeLayer()
let path = CGMutablePath()
path.addPath(roundedWindow.cgPath)
path.addRect(r)
mask.path = path
mask.fillRule = kCAFillRuleEvenOdd
self.maskedBackgroundView.layer.mask = mask
}
Then I had to apply the colour change to maskedBackgroundView.layer.backgroundColor (i.e., to the layer, not the view). With that in place, I get the cutout I need, with animatable colour changes. Thanks #matt for pointing me in the right direction.
I want to draw a line in UIViewController without using code, so like the constraints for the lines can be given in storyboard itself. if there is a way to do it, please tell me. Now i have drawn a line by placing an UIView in storyboard and used the following code to draw line on it. Is this the best way ?. Or do i have any better way ?. Thanks in advance.
class line: UIView {
override func draw(_ rect: CGRect) {
let aPath = UIBezierPath()
aPath.move(to: CGPoint(x:0, y:1))
aPath.addLine(to: CGPoint(x:500, y:1))
//Keep using the method addLineToPoint until you get to the one where about to close the path
aPath.close()
//If you want to stroke it with a red color
UIColor.blue.set()
aPath.stroke()
//If you want to fill it as well
aPath.fill()
}
}
Using Swift 3.0 and Xcode 8.2
Use #IBDesignable as follows. Make your view with height of one as suggested and the #IBDesignable will show your line in your storyboard.:
import UIKit
#IBDesignable class line: UIView {
override func draw(_ rect: CGRect) {
let aPath = UIBezierPath()
aPath.move(to: CGPoint(x: 0, y: 1))
aPath.addLine(to: CGPoint(x: 250, y: 1))
aPath.close()
UIColor.blue.set()
aPath.stroke()
}
}
Add a UIView with a height of 1 point.
Add constraints for it.
I created a button on top of my mapView.
In order to make that Button more visible I added a blur view below that button.
I don't like the sharp edges of my blur view.
How do I make the blur fade out slowly transitioning into the mapView?
EDIT: To be more specific. I mean a fading blurred gradient with round corners.
I think this can help you, first you need subclass your button and add this code in drawRect and replace UIColor.blueColor().CGColor by yourColor.CGColor
class UICustomBackgroundButton: UIButton {
override func draw(_ rect: CGRect) {
// Drawing code
super.draw(rect)
let ctx = UIGraphicsGetCurrentContext();
let path = CGPath(roundedRect: rect.insetBy(dx: self.frame.size.height/4, dy: self.frame.size.height/4) , cornerWidth: self.frame.size.height/8, cornerHeight: self.frame.size.height/8, transform: nil)
ctx?.setShadow(offset: CGSize.zero, blur: self.frame.size.height/4, color: UIColor.blue.cgColor);
ctx?.setFillColor(UIColor.blue.cgColor);
//if number of cicles is our glow is darker
for _ in 0..<6
{
ctx?.addPath(path);
ctx?.fillPath();
}
}
}
I Hope this helps you.
and this is how it look
Have you tried the cornerRadius property? That should remove the sharp edges
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.
Basing on the source code below:
#IBOutlet var myUIImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.makingRoundedImageProfileWithRoundedBorder()
}
private func makingRoundedImageProfileWithRoundedBorder() {
// Making a circular image profile.
// self.myUIImageView.layer.cornerRadius = self.myUIImageView.frame.size.width / 2
// Making a rounded image profile.
self.myUIImageView.layer.cornerRadius = 20.0
self.myUIImageView.clipsToBounds = true
// Adding a border to the image profile
self.myUIImageView.layer.borderWidth = 10.0
self.myUIImageView.layer.borderColor = UIColor.whiteColor().CGColor
}
Indeed I am able to render a circular or rounded UIImageView, but the problem is that if we add the border, the image leaks a bit. It's way worse with a circular UIImageView, it leaks whenever the border is bent, so LEAKS EVERYWHERE! You can find a screenshot of the result below:
Any way to fix that in Swift? Any sample code which answers to this question will be highly appreciated.
Note: as far as possible the solution has to be compatible with iOS 7 and 8+.
First Solution
Basing on the #Jesper Schläger suggestion
"If I may suggest a quick and dirty solution:
Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0."
Please find the Swift implementation below:
import UIKit
class ViewController: UIViewController {
#IBOutlet var myUIImageView: UIImageView!
#IBOutlet var myUIViewBackground: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Making a circular UIView: cornerRadius = self.myUIImageView.frame.size.width / 2
// Making a rounded UIView: cornerRadius = 10.0
self.roundingUIView(self.myUIImageView, cornerRadiusParam: 10)
self.roundingUIView(self.myUIViewBackground, cornerRadiusParam: 20)
}
private func roundingUIView(let aView: UIView!, let cornerRadiusParam: CGFloat!) {
aView.clipsToBounds = true
aView.layer.cornerRadius = cornerRadiusParam
}
}
Second Solution
Would be to set a circle mask over a CALayer.
Please find the Objective-C implementation of this second solution below:
CALayer *maskedLayer = [CALayer layer];
[maskedLayer setFrame:CGRectMake(50, 50, 100, 100)];
[maskedLayer setBackgroundColor:[UIColor blackColor].CGColor];
UIBezierPath *maskingPath = [UIBezierPath bezierPath];
[maskingPath addArcWithCenter:maskedLayer.position
radius:40
startAngle:0
endAngle:360
clockwise:TRUE];
CAShapeLayer *maskingLayer = [CAShapeLayer layer];
[maskingLayer setPath:maskingPath.CGPath];
[maskedLayer setMask:maskingLayer];
[self.view.layer addSublayer:maskedLayer];
If you comment out from line UIBezierPath *maskingPath = [UIBezierPath bezierPath]; through [maskedLayer setMask:maskingLayer]; you will see that the layer is a square. However when these lines are not commented the layer is a circle.
Note: I neither tested this second solution nor provided the Swift implementation, so feel free to test it and let me know if it works or not through the comment section below. Also feel free to edit this post adding the Swift implementation of this second solution.
If I may suggest a quick and dirty solution:
Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0.
I worked on improving the code but it keeps crashing. I'll work on it, but I appear to have got a (rough) version working:
Edit Updated with a slightly nicer version. I don't like the init:coder method but maybe that can factored out/improved
class RoundedImageView: UIView {
var image: UIImage? {
didSet {
if let image = image {
self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
}
}
var cornerRadius: CGFloat?
private class func frameForImage(image: UIImage) -> CGRect {
return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
override func drawRect(rect: CGRect) {
if let image = self.image {
image.drawInRect(rect)
let cornerRadius = self.cornerRadius ?? rect.size.width/10
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
UIColor.whiteColor().setStroke()
path.lineWidth = cornerRadius
path.stroke()
}
}
}
let image = UIImage(named: "big-teddy-bear.jpg")
let imageView = RoundedImageView()
imageView.image = image
Let me know if that's the sort of thing you're looking for.
A little explanation:
As I'm sure you've found, the "border" that iOS can apply isn't perfect, and shows the corners for some reason. I found a few other solutions but none seemed to work. The reason this is a subclass of UIView, and not UIImageView, is that drawRect: is not called for subclasses of UIImageView. I am not sure about the performance of this code, but it seems good from my (limited) testing
Original code:
class RoundedImageView: UIView {
var image: UIImage? {
didSet {
if let image = image {
self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
}
}
private class func frameForImage(image: UIImage) -> CGRect {
return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
override func drawRect(rect: CGRect) {
if let image = self.image {
self.image?.drawInRect(rect)
let path = UIBezierPath(roundedRect: rect, cornerRadius: 50)
UIColor.whiteColor().setStroke()
path.lineWidth = 10
path.stroke()
}
}
}
let image = UIImage(named: "big-teddy-bear.jpg")
let imageView = RoundedImageView()
imageView.image = image
imageView.layer.cornerRadius = 50
imageView.clipsToBounds = true