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 = UIColor.blue { 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 = UIColor.black { 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 initialize() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) // Call the internal initializer initialize() } //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) { super.draw(rect) } 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.begin() CATransaction.setCompletionBlock { // closure block self.delegate?.finishedProgress(forCircle: self) completion?() } self.value = value self.progressLayer.value = value CATransaction.commit() } }
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)" } self.sizeToFit() } } 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) UIGraphicsPushContext(ctx) // Draw the Circle drawOuterCircle() drawInnerCircle() // Draw the text label drawTextLabel() UIGraphicsPopContext() } 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 outerCircleColor.setStroke() outerPath.stroke() } /** 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 innerCircleColor.setStroke() innerPath.stroke() } /** 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 valueLabel.center = 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() { super.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:))) self.progressView.addGestureRecognizer(tapgesture) // 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() { super.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.