iOS Programming Cookbook
上QQ阅读APP看书,第一时间看更新

There's more...

We saw how to add UIViews using interface builder or programmatically. These are the most commonly used ways in UIViews while working on an iOS app. In some cases, native components don't fit your needs, and you have to build your own custom UIView. Let's see in action how to build a custom UIView. In the following example, we will see how to build a circular progress bar programmatically and how to use or customize it using interface builder.

Go to Xcode and create a new Swift class CircularProgressBar, which extends UIView. Then, add the following code:

@IBDesignable 
class CircularProgressBar: UIView {

/// The background fixed color of progress bar
@IBInspectable var progressFixedColor : UIColor = UIColor.whiteColor()

/// The progressive color
@IBInspectable var progressColor : UIColor = UIColor.redColor()

/// The line width of the progress circle
@IBInspectable var lineWidth:CGFloat = 5.0

/// The layer where we draw the progressive animation.
private var progressiveLayer : CAShapeLayer?


func updateProgess(progress:CGFloat, animated:Bool,
duration:CFTimeInterval){

if self.progressiveLayer == nil{
self.setNeedsDisplay()
}
if progress <= 1.0{

self.progressiveLayer?.strokeEnd = progress
if animated {

CATransaction.begin()

let animation = CABasicAnimation(keyPath: "strokeEnd");
animation.duration = duration
animation.fromValue = NSNumber(float: 0.0)
animation.toValue = NSNumber(float: Float(progress));
animation.timingFunction = CAMediaTimingFunction(name:
kCAMediaTimingFunctionEaseInEaseOut)
self.progressiveLayer?.addAnimation(animation, forKey:
"animateStrokeEnd")
CATransaction.commit()
}
}
}

override func drawRect(rect: CGRect) {
// Drawing code

let fixedLayer = getShapeLayerForRect(rect, strokeColor:
progressFixedColor)
fixedLayer.strokeEnd = 1.0

self.layer.addSublayer(fixedLayer)

let progressiveLayer = getShapeLayerForRect(rect, strokeColor:
progressColor)
progressiveLayer.strokeEnd = 0.0

self.progressiveLayer = progressiveLayer

self.layer.addSublayer(progressiveLayer)

}


private func getShapeLayerForRect(rect:CGRect, strokeColor
sColor:UIColor) -> CAShapeLayer{

let radius = CGRectGetWidth(rect) / 2 - lineWidth / 2
let newRect = CGRectMake(lineWidth / 2, lineWidth / 2, radius * 2,
radius * 2)
let path = UIBezierPath(roundedRect: newRect, cornerRadius:
radius).CGPath
let shape = CAShapeLayer()
shape.path = path
shape.strokeColor = sColor.CGColor
shape.lineWidth = self.lineWidth
shape.fillColor = nil
return shape
}

}

Let's explain the code step by step:

  1. We marked our class with @IBDesignable, which tells the compiler that this class is a custom UIView that can be rendered in interface builder. This helps you to see what the view looks like without running the on simulator or device.
  2. We listed three parameters to set the colors and line width of the progress view. Each parameter is marked as @IBInspectable, which tells the compiler to display these parameters in Attribute Inspector, so you customize these values as well from interface builder.
  1. Go to interface builder and add a UIView as a subview to the view that is blue. Change its class from UIView to CircularProgressBar from the Identity Inspector tab. Change its background color to clear color and see how the view will be rendered:

  1. Also, if you open the Attribute Inspector, you will see that three additional attributes have been added:

  1. Then, we override the drawRect method. This function should be overridden to make a custom drawing in your view. In drawRect, we created two circular shapes. One shape is the fixed circle shape, which acts as a background. It's strokeEnd value is 1, as it's fixed and a complete circle. The second shape is the progressive circle, which will be animated via strokeEnd to show its progress. We used the getShapeLayerForRect function to create a circle via the CAShapeLayer class in the CoreAnimation framework.

More information about drawing with Bezier Path and Core Animation can be found in Chapter 5, Animations and Graphics.

  1. Then, we add the updateProgess function that updates the progress by animating the progressive layer strokeEnd property.
  2. Now, take a look at an outlet of the progressive view in ViewController.swift:
      @IBOutlet weak var circularProgressView: CircularProgressBar!
  1. Then override viewDidAppear method to update the progress value to 75% like this:
      override func viewDidAppear(animated: Bool) { 
super.viewDidAppear(animated)

self.circularProgressView.updateProgess(0.75, animated: true,
duration: 2)
}
  1. Now, build and run; you should see the progress updates with animation: