Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a multicolor bar in swift

Tags:

ios

swift

I'm trying to make a line (so basically UIView) that has fixed height and width and is divided to nine segments. I want to be able to control the height of each segment and its color. E.g. I want the first segment be yellow and 30% of the total height of the line, the second to be red and 8% of the total height etc.

I'm not really skilled in Swift, so my solution would be to make 9 UIViews, stack them on top of each other on my storyboard and then manually set the height and background color of every view, so they'd seem like a one multicolored line. Is there cleaner and less bulky solution? Thanks

like image 206
Eugleo Avatar asked Dec 25 '22 09:12

Eugleo


2 Answers

I would highly recommend using Core Graphics for this.

As the drawing is dead simple (you just want to stack some colored lines within a view), you can easily achieve this by subclassing UIView and overriding drawRect() and drawing them in Core Graphics.

It's certainly a much cleaner solution than adding 9 subviews!

Something like this should achieve the desired result:

class LineView : UIView {

    let colors:[UIColor] = [UIColor.redColor(), UIColor.blueColor(), UIColor.greenColor()]
    let values:[CGFloat] = [0.35, 0.45, 0.2]

    override func drawRect(rect: CGRect) {

        let r = self.bounds // the view's bounds
        let numberOfSegments = values.count // number of segments to render

        let ctx = UIGraphicsGetCurrentContext() // get the current context

        var cumulativeValue:CGFloat = 0 // store a cumulative value in order to start each line after the last one
        for i in 0..<numberOfSegments {

            CGContextSetFillColorWithColor(ctx, colors[i]) // set fill color to the given color
            CGContextFillRect(ctx, CGRectMake(0, cumulativeValue*r.size.height, r.size.width, values[i]*r.size.height)) // fill that given segment

            cumulativeValue += values[i] // increment cumulative value
        }
    }
}

Going further...

You could allow the colors and values properties to be changed from outside the LineView class, allowing for much greater flexibility. You just have to override the didSet to trigger the view to be redrawn when the properties change.

For example:

class LineView : UIView {

    /// An array of optional UIColors (clearColor is used when nil is provided) defining the color of each segment.
    var colors : [UIColor?] = [UIColor?]() {
        didSet {
            self.setNeedsDisplay()
        }
    }

    /// An array of CGFloat values to define how much of the view each segment occupies. Should add up to 1.0.
    var values : [CGFloat] = [CGFloat]() {
        didSet {
            self.setNeedsDisplay()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.clearColor()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func drawRect(rect: CGRect) {

        let r = self.bounds // the view's bounds
        let numberOfSegments = values.count // number of segments to render

        let ctx = UIGraphicsGetCurrentContext() // get the current context

        var cumulativeValue:CGFloat = 0 // store a cumulative value in order to start each line after the last one
        for i in 0..<numberOfSegments {

            CGContextSetFillColorWithColor(ctx, colors[i]?.CGColor ?? UIColor.clearColor().CGColor) // set fill color to the given color if it's provided, else use clearColor
            CGContextFillRect(ctx, CGRectMake(0, cumulativeValue*r.size.height, r.size.width, values[i]*r.size.height)) // fill that given segment

            cumulativeValue += values[i] // increment cumulative value
        }
    }
}

Usage:

let lineView = LineView(frame: CGRectMake(50, 50, 20, view.bounds.size.height-100))

lineView.colors = [
    UIColor(red: 1.0, green: 31.0/255.0, blue: 73.0/255.0, alpha: 1.0), // red
    UIColor(red:1.0, green: 138.0/255.0, blue: 0.0, alpha:1.0), // orange
    UIColor(red: 122.0/255.0, green: 108.0/255.0, blue: 1.0, alpha: 1.0), // purple
    UIColor(red: 0.0, green: 100.0/255.0, blue: 1.0, alpha: 1.0), // dark blue
    UIColor(red: 100.0/255.0, green: 241.0/255.0, blue: 183.0/255.0, alpha: 1.0), // green
    UIColor(red: 0.0, green: 222.0/255.0, blue: 1.0, alpha: 1.0) // blue
]
lineView.values = [0.15, 0.1, 0.35, 0.15, 0.1, 0.15]

view.addSubview(lineView);

enter image description here

(I've only added 6 colors here, but you can add as many as you want).


Full project: https://github.com/hamishknight/Color-Segment-Line-View

like image 103
Hamish Avatar answered Dec 26 '22 23:12

Hamish


I've just realized that this was not what you needed. I leave the answer anyway so that maybe could be helpful to somebody else in the future.

Make sure your line view has it's own UIView subclass, so that we can override drawRect and achieve your goal.

Then a simple implementation would be:

class BarLine: UIView {

    override func drawRect(rect: CGRect) {

        //Height of each segment, in percentage
        var heights  : [CGFloat]    = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

        //Lets create 9 rects and set each rect width to be 1/9th of the view size, then add them to the array
        let width   : CGFloat   = rect.size.width / 9.0


        var i       : Int       = Int()

        //Loop to generate 9 segmnets
        for (i = 0; i < 9; i++) {

            //Each rect origin must be translated by i * width
            let origin  = CGPointMake(CGFloat(i) * width, rect.height)

            //Generate a random color
            let color   = UIColor(red: heights[i], green: 0.5, blue: 0.5, alpha: 1)
            let segment = CGRect(x: origin.x, y: origin.y, width: width, height: -heights[i] * rect.height)

            //Set the color
            color.set()

            //Add the segment to the view by drawing it
            UIRectFill(segment)

        }


    }

}

This will produce something like :

enter image description here

(Remember to set your UIView class to you custom class in IB)

I hope this helped

like image 41
Alberto Bellini Avatar answered Dec 26 '22 22:12

Alberto Bellini