How to Add Download Button in Your iOS App Using Progress View

download button

Today, you can build an awesome app, with the most compelling copywriting, strong backend, and few unique features that nobody has implemented yet, but still it’s all not worth if the basic UI of your app is poorly designed.

Poor UI means users cannot understand where to tap for a particular action or don’t know if an action is in the progress or not.

Today, we’re going to talk about the download button in iOS apps.

You may have seen the progress bar while downloading a video or downloading an app from Apple App Store.

It looks something like this:


Now, there are many ways you can design your progressview with different color and style ranges.

In this iPhone app tutorial, we’ll build a demo to see how to add a download button in iOS app using Progress view.

Let’s Get Started

First, open your Xcode and click on new Xcode project.

Now, select Single View Application and click on next button.


Next, give your app name and select language Swift as shown below.


With this, you’ll successfully create a new project.

Now, create a new file of UIView in your project by clicking on XCode’s file option => New => File.

It will open an window like below.


Click on Cocoa Touch class and click on next button.

It will open a new screen as below where you have to give the file name as Progress View and press next button.


Add the following code in ProgressView file.

@IBDesignable open class ProgressView: UIView {

	open weak var delegate: ProgressViewDelegate?
	@IBInspectable open var value: CGFloat = 0 {
    	didSet {
        	self.progressLayer.value = self.value

	open var currentValue: CGFloat? {
    	get {
        	if isAnimating {
            	return self.layer.presentation()?.value(forKey: "value") as? CGFloat
        	} else {
            	return self.value
	@IBInspectable open var maxValue: CGFloat = 100 {
    	didSet {
        	self.progressLayer.maxValue = self.maxValue
	@IBInspectable open var viewStyle: Int = 1 {
    	didSet {
        	self.progressLayer.viewStyle = self.viewStyle
	open var patternForDashes: [CGFloat] = [7.0, 7.0] {
    	didSet {
        	self.progressLayer.patternForDashes = self.patternForDashes
	@IBInspectable open var startAngle: CGFloat = 0 {
    	didSet {
        	self.progressLayer.startAngle = self.startAngle
	@IBInspectable open var endAngle: CGFloat = 360 {
    	didSet {
        	self.progressLayer.endAngle = self.endAngle
	@IBInspectable open var outerCircleWidth: CGFloat = 10.0 {
    	didSet {
        	self.progressLayer.outerCircleWidth = self.outerCircleWidth
	@IBInspectable open var outerCircleColor: UIColor = UIColor.gray {
    	didSet {
        	self.progressLayer.outerCircleColor = self.outerCircleColor
	@IBInspectable open var innerCircleWidth: CGFloat = 5.0 {
    	didSet {
        	self.progressLayer.innerCircleWidth = self.innerCircleWidth
	@IBInspectable open var innerCircleColor: UIColor = {
    	didSet {
        	self.progressLayer.innerCircleColor = self.innerCircleColor
	@IBInspectable open var innerCircleSpacing: CGFloat = 1 {
    	didSet {
        	self.progressLayer.innerCircleSpacing = self.innerCircleSpacing
	@IBInspectable open var isShowText: Bool = true {
    	didSet {
        	self.progressLayer.isShowText = self.isShowText
	@IBInspectable open var textColor: UIColor = {
    	didSet {
        	self.progressLayer.textColor = self.textColor
	@IBInspectable open var font: UIFont = UIFont.systemFont(ofSize: 12) {
    	didSet {
        	self.progressLayer.font = self.font
	@IBInspectable open var textProgresser: String = "%" {
    	didSet {
        	self.progressLayer.textProgresser = self.textProgresser
	open var animationStyle: String = kCAMediaTimingFunctionEaseIn {
    	didSet {
        	self.progressLayer.animationStyle = self.animationStyle
	open var isAnimating: Bool {
    	get { return (self.layer.animation(forKey: "value") != nil) ? true : false }
	internal var progressLayer: UIProgressLayer {
    	return self.layer as! UIProgressLayer
	override open class var layerClass: AnyClass {
    	get {
        	return UIProgressLayer.self
	override public init(frame: CGRect) {
    	super.init(frame: frame)
    	// Call the internal initializer
	required public init?(coder aDecoder: NSCoder) {
    	super.init(coder: aDecoder)
    	// Call the internal initializer
	//MARK:- initialization -
	internal func initialize() {
    	self.layer.contentsScale = UIScreen.main.scale
    	self.layer.shouldRasterize = true
    	self.layer.rasterizationScale = UIScreen.main.scale * 2
    	self.progressLayer.value = value
    	self.progressLayer.maxValue = maxValue
    	self.progressLayer.viewStyle = viewStyle
    	self.progressLayer.patternForDashes = patternForDashes
    	self.progressLayer.startAngle = startAngle
    	self.progressLayer.endAngle = endAngle
    	self.progressLayer.outerCircleWidth = outerCircleWidth
    	self.progressLayer.outerCircleColor = outerCircleColor
    	self.progressLayer.innerCircleWidth = innerCircleWidth
    	self.progressLayer.innerCircleColor = innerCircleColor
    	self.progressLayer.innerCircleSpacing = innerCircleSpacing
    	self.progressLayer.isShowText = isShowText
    	self.progressLayer.textProgresser = textProgresser
    	self.progressLayer.textColor = textColor
    	self.progressLayer.font = font
    	self.backgroundColor = UIColor.clear
    	self.progressLayer.backgroundColor = UIColor.clear.cgColor
	open override func draw(_ rect: CGRect) {
	public typealias ProgressCompletion = (() -> Void)
 Sets the current value for the progress ring, calling this method while Circle is animating will cancel the previously set animation and start a new one.
	open func setProgress(value: CGFloat, animationDuration: TimeInterval, completion: ProgressCompletion? = nil) {
    	if isAnimating { self.layer.removeAnimation(forKey: "value") }
    	self.progressLayer.animated = animationDuration > 0
    	self.progressLayer.animationDuration = animationDuration
    	CATransaction.setCompletionBlock {
        	// closure block
        	self.delegate?.finishedProgress(forCircle: self)
    	self.value = value
    	self.progressLayer.value = value

Now create New file for CALayer for add animation to progressView. As show in below screenshot.


Add the following code to UIProgressLayer

 A private extension to CGFloat in order to provide simple
 conversion from degrees to radians, used when drawing the rings.
private extension CGFloat {
	var toRads: CGFloat { return self * CGFloat(M_PI) / 180 }

private extension UILabel {
	func update(withValue value: CGFloat, valueIndicator: String, showsDecimal: Bool, decimalPlaces: Int) {
    	if showsDecimal {
        	self.text = String(format: "%.\(decimalPlaces)f", value) + "\(valueIndicator)"
    	} else {
        	self.text = "\(Int(value))\(valueIndicator)"

class UIProgressLayer: CAShapeLayer {

	@NSManaged var value: CGFloat
	@NSManaged var maxValue: CGFloat
	@NSManaged var viewStyle: Int
	@NSManaged var patternForDashes: [CGFloat]
	@NSManaged var startAngle: CGFloat
	@NSManaged var endAngle: CGFloat
	@NSManaged var outerCircleWidth: CGFloat
	@NSManaged var outerCircleColor: UIColor
	@NSManaged var innerCircleWidth: CGFloat
	@NSManaged var innerCircleColor: UIColor

	@NSManaged var innerCircleSpacing: CGFloat
	@NSManaged var isShowText: Bool
	@NSManaged var textColor: UIColor
	@NSManaged var font: UIFont
	@NSManaged var textProgresser: String
	var animationDuration: TimeInterval = 1.0
	var animationStyle: String = kCAMediaTimingFunctionEaseInEaseOut
	var animated = false
 	lazy private var valueLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
	override func draw(in ctx: CGContext) {
    	super.draw(in: ctx)
    	// Draw the Circle
    	// Draw the text label
	override class func needsDisplay(forKey key: String) -> Bool {
    	if key == "value" {
        	return true
    	return super.needsDisplay(forKey: key)
	override func action(forKey event: String) -> CAAction? {
    	if event == "value" && self.animated {
        	let animation = CABasicAnimation(keyPath: "value")
        	animation.fromValue = self.presentation()?.value(forKey: "value")
        	animation.timingFunction = CAMediaTimingFunction(name: animationStyle)
        	animation.duration = animationDuration
        	return animation
    	return super.action(forKey: event)
 	Draws the outer circle for the view.
 	Sets path properties according to how the user set properties.
	private func drawOuterCircle() {
    	guard outerCircleWidth > 0 else { return }
    	let width = bounds.width
    	let height = bounds.width
    	let center = CGPoint(x: bounds.midX, y: bounds.midY)
    	let outerRadius = max(width, height)/2 - outerCircleWidth/2
    	let outerPath = UIBezierPath(arcCenter: center,
                                 	radius: outerRadius,
                                 	startAngle: startAngle.toRads,
                                 	endAngle: endAngle.toRads,
                                 	clockwise: true)
    	outerPath.lineWidth = outerCircleWidth
    	outerPath.lineCapStyle = .round
 	Draws the inner circle for the view.
 	Sets path properties according to how the user set properties.
	private func drawInnerCircle() {
    	guard innerCircleWidth > 0 else { return }
    	let center = CGPoint(x: bounds.midX, y: bounds.midY)
    	// Calculate the center difference between the end and start angle
    	let angleDiff: CGFloat = endAngle.toRads - startAngle.toRads
    	// Calculate how much we should draw depending on the value set
    	let arcLenPerValue = angleDiff / CGFloat(maxValue)
    	// The inner end angle some basic math is done
    	let innerEndAngle = arcLenPerValue * CGFloat(value) + startAngle.toRads
    	// The radius for style 1 is set below
    	// The radius for style 1 is a bit less than the outer, this way it looks like its inside the circle
    	var radiusIn = (max(bounds.width - outerCircleWidth*2 - innerCircleSpacing, bounds.height - outerCircleWidth*2 - innerCircleSpacing)/2) - innerCircleWidth/2
    	// If the style is different, mae the radius equal to the outerRadius
    	if viewStyle >= 2 {
        	radiusIn = (max(bounds.width, bounds.height)/2) - (outerCircleWidth/2)
    	// Start drawing
    	let innerPath = UIBezierPath(arcCenter: center,
                                 	radius: radiusIn,
                                 	startAngle: startAngle.toRads,
                                 	endAngle: innerEndAngle,
                                 	clockwise: true)
    	innerPath.lineWidth = innerCircleWidth
    	innerPath.lineCapStyle = .round
 	Draws the text label for the view.
 	Only drawn if shouldShowValueText = true
	private func drawTextLabel() {
    	guard isShowText else { return }
    	// Draws the text field
    	// Some basic label properties are set
    	valueLabel.font = self.font
    	valueLabel.textAlignment = .center
    	valueLabel.textColor = textColor
    	valueLabel.update(withValue: value, valueIndicator: textProgresser,
                      	showsDecimal: false, decimalPlaces: 1)
    	// Deterime what should be the center for the label = CGPoint(x: bounds.midX, y: bounds.midY)
    	valueLabel.drawText(in: self.bounds)

Now add one Swift file  for getting notification  after completion of animation.

For Adding Swift file open  Xcode’s File option  -> New – > File

This will open Dialog as below.


Here, click on Swift file as shown above screen and click on Next option.

give the File name as ProgressViewDelegate

import Foundation
public protocol ProgressViewDelegate: class {
func finishedProgress(forCircle circle: ProgressView)

Now go to storyboard there is blank ViewController. Add UIView to center of screen as show in below screen.


Now, select View add to viewController set the file ProgressView as sub class  as shown below screenshot.


Add the Outlet of view to your view controller as shown in below screenshot.


Select view from view controller then click on Attribute inspector as shown in below screenshot.


Set the value as shown in screenshot this will show you circle as shown in above screenshot.

Now add the following code in your ViewController.

 override func viewDidLoad() {
     	Set the animation style for progressView and font of the text
    	progressView.animationStyle = kCAMediaTimingFunctionLinear
    	progressView.font = UIFont.systemFont(ofSize: 17)
    	progressView.delegate = self
    	progressView.isUserInteractionEnabled  = true
    	let tapgesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.startTheProgress(gesture:)))
    	// Do any additional setup after loading the view, typically from a nib.

func startTheProgress(gesture:UITapGestureRecognizer)
    	self.progressView.animationStyle = kCAMediaTimingFunctionLinear
    	self.progressView.setProgress(value: 100, animationDuration: 5, completion: nil)

	override func didReceiveMemoryWarning() {
    	// Dispose of any resources that can be recreated.

	//MARK:- ProgressViewDelegate method -
	func finishedProgress(forCircle circle: ProgressView) {
 	if circle == progressView{
    	print("completed progress")

And Done!

Once you successfully implement this code, you can easily make add download button in your iPhone app. If you’d like to get to more advanced level such as adding different styles of download buttons, it is possible to create such download buttons matching your overall app design. Though, if you’re planning for your business mobile app, you should hire iPhone app development company to add this feature in your app.

Get a free copy of Progress View Demo from Github.

Also Read:

This page was last edited on November 17th, 2020, at 3:46.

Author Bio

Hitesh Trivedi

Hitesh Trivedi

Designation: iOS Team Lead

Hitesh Trivedi is an iOS Team Lead at Space-O Technologies. He has over 9 years of experience in iOS app development. He has guided to develop over 100 iPhone apps with unique features and functionalities. He has special expertise in Swift and Objective-C.


Have an Idea for iPhone App? Let's Discuss!

Get your free consultation now

Leave a Reply

Your email address will not be published. Required fields are marked *