Paragon Paragon - 2 months ago 7
Swift Question

UIView horizontal bar animation in swift

I am working on this animation where a number will be received every second and progress bar has to fill or go down based on the double value.

I have created the views and have added all the views in the UIStackView. Also made the outlet collection for all the views. (sorting them by the tag and making them round rect).

I can loop the views and change their background color but trying to see if there is a better way to do it. Any suggestions?

Thanks

Answer Source

I'm a firm believer in learning through solving organic problems and slowly building my global knowledge on a subject. So I'm afraid I don't have any good tutorials for you.

Here is an example that will jump start you, though.

import UIKit

@IBDesignable
class VerticalProgessView: UIControl {

    @IBInspectable
    var numberOfSegments: UInt = 0

    @IBInspectable
    var verticalSegmentGap: CGFloat = 4.0

    @IBInspectable
    var outerColor: UIColor = UIColor(red: 33, green: 133, blue: 109)

    @IBInspectable
    var unfilledColor: UIColor = UIColor(red: 61, green: 202, blue: 169)

    @IBInspectable
    var filledColor: UIColor = UIColor.white

    private var _progress: Float = 0.25
    @IBInspectable
    open var progress: Float {
        get {
            return _progress
        }
        set {
            self.setProgress(newValue, animated: false)
        }
    }

    open func setProgress(_ progress: Float, animated: Bool) {

        if progress < 0 {
            _progress = 0
        } else if progress > 1.0 {
            _progress = 1
        } else {
            // Clamp the percentage to discreet values
            let discreetPercentageDistance: Float = 1.0 / 28.0
            let nearestProgress = discreetPercentageDistance * round(progress/discreetPercentageDistance)

            _progress = nearestProgress
        }

        CATransaction.begin()
        if !animated {
            CATransaction.setDisableActions(true)
        } else {
            CATransaction.setAnimationDuration(1.0)
        }

        let properties = progressLayerProperties()
        progressLayer.bounds = properties.bounds
        progressLayer.position = properties.position

        CATransaction.commit()
    }

    let progressLayer = CALayer()

    let maskLayer = CAShapeLayer()

    override func prepareForInterfaceBuilder() {
        awakeFromNib()
    }

    override func awakeFromNib() {
        super.awakeFromNib()

        self.backgroundColor = UIColor.clear

        self.layer.backgroundColor = unfilledColor.cgColor

        // Initialize and add the progressLayer
        let properties = progressLayerProperties()
        progressLayer.bounds = properties.bounds
        progressLayer.position = properties.position
        progressLayer.backgroundColor = filledColor.cgColor
        self.layer.addSublayer(progressLayer)

        // Initialize and add the maskLayer (it has the holes)
        maskLayer.frame = self.layer.bounds
        maskLayer.fillColor = outerColor.cgColor
        maskLayer.fillRule = kCAFillRuleEvenOdd
        self.layer.addSublayer(maskLayer)

        // Layer hierarchy

        // self.maskLayer
        // self.progressLayer
        // self.layer
    }

    override func updateConstraints() {
        super.updateConstraints()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let properties = progressLayerProperties()
        progressLayer.bounds = properties.bounds
        progressLayer.position = properties.position

        let size = self.bounds.size
        let anchorPoint = maskLayer.anchorPoint
        maskLayer.bounds = CGRect(origin: CGPoint.zero, size: size)
        maskLayer.position = CGPoint(x: size.width * anchorPoint.x, y: size.height * anchorPoint.y)

        // Here is were we "draw" the segments
        maskLayer.path = maskPath(for: maskLayer.bounds)
    }

    // Provides a path that will mask out all the holes to show self.layer and the progressLayer behind
    private func maskPath(for rect: CGRect) -> CGPath {

        let horizontalSegmentGap: CGFloat = 5.0

        let segmentWidth = rect.width - horizontalSegmentGap * 2
        let segmentHeight = rect.height/CGFloat(numberOfSegments) - verticalSegmentGap + verticalSegmentGap/CGFloat(numberOfSegments)

        let segmentSize = CGSize(width: segmentWidth, height: segmentHeight)
        let segmentRect = CGRect(x: horizontalSegmentGap, y: 0, width: segmentSize.width, height: segmentSize.height)

        let path = CGMutablePath()
        for i in 0..<numberOfSegments {

            // Literally, just move it down by the y value here
            // this simplifies the math of calculating the starting points and what not
            let transform = CGAffineTransform.identity.translatedBy(x: 0, y: (segmentSize.height + verticalSegmentGap) * CGFloat(i))

            let segmentPath = UIBezierPath(roundedRect: segmentRect, cornerRadius: segmentSize.height / 2)
            segmentPath.apply(transform)

            path.addPath(segmentPath.cgPath)
        }

        // Without the outerPath, we'll end up with a bunch of squircles instead of a bunch of holes
        let outerPath = CGPath(rect: rect, transform: nil)

        path.addPath(outerPath)

        return path
    }

    /// Provides the current and correct bounds and position for the progressLayer
    private func progressLayerProperties() -> (bounds: CGRect, position: CGPoint) {
        let frame = self.bounds
        let height = frame.height * CGFloat(progress)
        let y = frame.height * CGFloat(1 - progress)
        let width = frame.width

        let bounds = CGRect(x: 0, y: 0, width: width, height: height)
        let position = CGPoint(x: 0 + width / 2, y: y + height / 2)

        return (bounds: bounds, position: position)
    }

    // Implement functions to further mimic UIProgressView
}

extension UIColor {
    convenience init(red: Int, green: Int, blue: Int) {
        self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1)
    }
}

Using in a storyboard

enter image description here

Enjoy the magic

enter image description here