gestureRecognizer not working on added subview - ios

I'm adding a subview in a view that holds multiple gesturerecognizer however when this view is being introduced the gesture's won't fire when the user taps inside this newly added view. It's a UIBezierPath that I'm introducing however. I've read that this could be way it doesn't work. My question is, is it possible to make the added UIBezierPath transparent in a way that the device will think the view below the drawn box is being tapped. I hope it makes sense what I'm saying.
This is the code for my UIBezierPath
class drawRecognizerIndicator{
let gestureContainer: UIView!
var view_gesture: UIView!
init(container: UIView){
gestureContainer = container
}
func drawRect(image: UIImage){
let screenSize = gestureContainer.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let gestureX = (screenWidth * 0.75) * 0.5
let gestureY = (screenWidth * 0.75) * 0.5
let gestureWidth = (screenWidth * 0.75)
let gestureHeight = (screenWidth * 0.75)
if(view_gesture != nil){
if(view_gesture.isDescendantOfView(gestureContainer)){
view_gesture.removeFromSuperview()
}
}
view_gesture = UIView(frame: CGRect(
x: (screenWidth * 0.5) - gestureX,
y: (screenHeight * 0.5) - gestureY ,
width: gestureWidth,
height: gestureHeight))
view_gesture.backgroundColor = UIColor.blackColor()
view_gesture.alpha = 0
let maskPath = UIBezierPath(roundedRect: view_gesture.bounds, byRoundingCorners: .AllCorners, cornerRadii: CGSize(width: 10, height: 10))
let maskLayer = CAShapeLayer(layer: maskPath)
maskLayer.frame = view_gesture.bounds
maskLayer.path = maskPath.CGPath
view_gesture.layer.mask = maskLayer
let view_gestureSize = view_gesture.bounds
let gestureIconWidth = view_gestureSize.width
let gestureIconHeight = view_gestureSize.height
let iconX = (gestureIconWidth * 0.8) * 0.5
let iconY = (gestureIconHeight * 0.8) * 0.5
let iconWidth = gestureIconWidth * 0.8
let iconHeight = gestureIconHeight * 0.8
let image_gesture = UIImageView(frame: CGRect(
x: (gestureIconWidth * 0.5) - iconX,
y: (gestureIconHeight * 0.5) - iconY,
width: iconWidth,
height: iconHeight))
image_gesture.contentMode = UIViewContentMode.ScaleAspectFit
image_gesture.image = image
image_gesture.userInteractionEnabled = true
view_gesture.addSubview(image_gesture)
view_gesture.userInteractionEnabled = true
gestureContainer.addSubview(view_gesture)
animateGestureNotification(view_gesture)
}
func animateGestureNotification(view_gesture: UIView){
UIView.animateWithDuration(0.5, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
view_gesture.alpha = 0.4
}, completion: nil)
UIView.animateWithDuration(0.5, delay: 2, options: UIViewAnimationOptions.CurveEaseOut, animations: {
view_gesture.alpha = 0
}, completion: nil)
}
}

Check the documentation for
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer
otherGestureRecognizer: UIGestureRecognizer) -> Bool {...}
It helps to decide what happens when there are several gesture recognizers on the same view controller.
I would start by putting some print statements in there to see exactly which GRs are being called, and then decide which ones should return true.

Related

Cloud moving animation in Swift? Is it possible?

We are following this tutorial to http://www.invasivecode.com/weblog/core-animation-scroll-layer-cascrolllayer
As per their tutorial animation will stop based on the layer bounds reached. How to do the cloud moving animation by using core animation or in Imageview?
Is it possible?
Gif or Lottie animation not needed here, we excepting through code.
Note that we have tried their code and its working but lack is there as i mentioned above.
How to keep on moving animation like cloud does?
Think twice before devoting this question.
Code:
class ViewController: UIViewController {
var translation: CGFloat = 0.0
public var currentWidth : CGFloat = {
let width = UIScreen.main.bounds.width
return width
}()
public var currentHeight : CGFloat = {
let width = UIScreen.main.bounds.height
return width
}()
lazy var scrollLayer : CAScrollLayer = {
let scrollLayer = CAScrollLayer() // 8
scrollLayer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth, height: currentHeight) // 9
scrollLayer.position = CGPoint(x: self.view.bounds.size.width/2, y: self.view.bounds.size.height/2) // 10
scrollLayer.borderColor = UIColor.black.cgColor // 11
// scrollLayer.borderWidth = 5.0 // 12
scrollLayer.scrollMode = CAScrollLayerScrollMode.horizontally // 13
return scrollLayer
}()
lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(scrollLayerScroll))
if #available(iOS 15.0, *) {
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 5.0, maximum: 8.0, __preferred: 6.0)
} else {
displayLink.preferredFramesPerSecond = 5
// Fallback on earlier versions
}
return displayLink
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let img = UIImage(named: "new") { // 1
let imageSize = img.size
let layer = CALayer() // 2
layer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth * 50, height: currentHeight * 5) // 3
// layer.position = CGPoint(x: imageSize.width/2, y: imageSize.height/2) // 4
layer.contents = img.cgImage // 5
view.layer.addSublayer(scrollLayer) // 6
scrollLayer.addSublayer(layer) // 7
}
displayLink.add(to: RunLoop.current, forMode: .common)
}
#objc func scrollLayerScroll() {
let newPoint = CGPoint(x: translation, y: 0.0)
scrollLayer.scroll(newPoint)
translation += 10.0
// if translation > 1600.0 {
//// stopDisplayLink()
// }
}
func stopDisplayLink() {
displayLink.invalidate()
}
}
Here setting width too high for animating the layer extra time
layer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth * 50, height: currentHeight * 5) // 3
We ended up with following solution by rob mayoff
Animate infinite scrolling of an image in a seamless loop
Please find the updated code.
class CloudPassingVC: UIViewController {
#IBOutlet weak var imageToAnimate: UIImageView!
var cloudLayer: CALayer? = nil
var cloudLayerAnimation: CABasicAnimation? = nil
override func viewDidLoad() {
super.viewDidLoad()
cloudScroll()
}
func cloudScroll() {
let cloudsImage = UIImage(named: "clouds")
let cloudPattern = UIColor(patternImage: cloudsImage!)
cloudLayer = CALayer()
cloudLayer!.backgroundColor = cloudPattern.cgColor
cloudLayer!.transform = CATransform3DMakeScale(1, -1, 1)
cloudLayer!.anchorPoint = CGPoint(x: 0, y: 1)
let viewSize = imageToAnimate.bounds.size
cloudLayer!.frame = CGRect(x: 0, y: 0, width: (cloudsImage?.size.width ?? 0.0) + viewSize.width, height: viewSize.height)
imageToAnimate.layer.addSublayer(cloudLayer!)
let startPoint = CGPoint.zero
let endPoint = CGPoint(x: -(cloudsImage?.size.width ?? 0.0), y: 0)
cloudLayerAnimation = CABasicAnimation(keyPath: "position")
cloudLayerAnimation!.timingFunction = CAMediaTimingFunction(name: .linear)
cloudLayerAnimation!.fromValue = NSValue(cgPoint: startPoint)
cloudLayerAnimation!.toValue = NSValue(cgPoint: endPoint)
cloudLayerAnimation!.repeatCount = 600
cloudLayerAnimation!.duration = 10.0
applyCloudLayerAnimation()
}
func applyCloudLayerAnimation() {
cloudLayer!.add(cloudLayerAnimation!, forKey: "position")
}
}

Overriding draw function in UIView class of iOS

I created a class from UI view and below is code.
class HudView: UIView {
var text = ""
class func hud(inView view: UIView, animated: Bool) -> HudView {
let hudView = HudView(frame: view.bounds)
hudView.isOpaque = false
view.addSubview(hudView)
hudView.show(animated: animated)
return hudView
}
override func draw(_ rect: CGRect) {
let boxWidth: CGFloat = 96
let boxHeight: CGFloat = 96
let boxRect = CGRect(x: round((bounds.size.width - boxWidth) / 2), y: round((bounds.size.height - boxHeight) / 2), width: boxWidth, height: boxHeight)
let roundRect = UIBezierPath(roundedRect: boxRect, cornerRadius: 10)
UIColor(white: 0.3, alpha: 0.8).setFill()
roundRect.fill()
if let image = UIImage(named: "Checkmark") {
let imagePoint = CGPoint(x: center.x - round(image.size.width / 2), y: center.y - round(image.size.height / 2))
image.draw(at: imagePoint)
}
let attribs = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.white]
let textSize = text.size(withAttributes: attribs)
let textPoint = CGPoint(x: center.x - round(textSize.width / 2), y: center.y - round(textSize.height / 2) + boxHeight / 4)
text.draw(at: textPoint, withAttributes: attribs)
}
func show(animated: Bool) {
if animated {
alpha = 0
transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
UIView.animate(withDuration: 0.3, animations: {
self.alpha = 1
self.transform = CGAffineTransform.identity
})
}
}
When I am creating object of this class, I observed that I don't need to call "draw" function separately and it still draws from this function. Could you help what is the reason, I don't need to call this function on HudView's object.
Below is code for creating object from class and even without calling "draw" func, it is still able to draw with given specifications.
let hudView = HudView.hud(inView: navigationController!.view, animated: true)
hudView.text = "Tagged"
from here: https://developer.apple.com/documentation/uikit/uiview/1622529-draw
"This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself"

Circular Transition, change initial circle size

I have a circular transition you can find in the code below. This transition creates an extending circle with the next view controller inside.
It works fine, but I've been trying to increase the circle size on start for a while now.
To be exact, currently on execution the circle will be drawn from zero, but I want it to have an initial size which appears immediately, from where it starts extending.
How can I change the initial size of the circle?
import UIKit
class CircularTransition: NSObject {
var circle = UIView()
var startingPoint = CGPoint.zero {
didSet {
circle.center = startingPoint
}
}
var circleColor = UIColor.white
var duration = 0.2
enum CircularTransitionMode:Int {
case present, dismiss, pop
}
var transitionMode:CircularTransitionMode = .present
}
extension CircularTransition:UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
let viewCenter = presentedView.center
let viewSize = presentedView.frame.size
circle = UIView()
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
circle.backgroundColor = circleColor
circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
containerView.addSubview(circle)
presentedView.center = startingPoint
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.alpha = 0
containerView.addSubview(presentedView)
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform.identity
presentedView.transform = CGAffineTransform.identity
presentedView.alpha = 1
presentedView.center = viewCenter
}, completion: { (success:Bool) in
transitionContext.completeTransition(success)
})
}
}else{
let transitionModeKey = (transitionMode == .pop) ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
if let returningView = transitionContext.view(forKey: transitionModeKey) {
let viewCenter = returningView.center
let viewSize = returningView.frame.size
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.center = self.startingPoint
returningView.alpha = 0
if self.transitionMode == .pop {
containerView.insertSubview(returningView, belowSubview: returningView)
containerView.insertSubview(self.circle, belowSubview: returningView)
}
}, completion: { (success:Bool) in
returningView.center = viewCenter
returningView.removeFromSuperview()
self.circle.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
}
func frameForCircle (withViewCenter viewCenter:CGPoint, size viewSize:CGSize, startPoint:CGPoint) -> CGRect {
let xLength = fmax(startPoint.x, viewSize.width - startPoint.x)
let yLength = fmax(startPoint.y, viewSize.height - startPoint.y)
let offestVector = sqrt(xLength * xLength + yLength * yLength) * 2
let size = CGSize(width: offestVector, height: offestVector)
return CGRect(origin: CGPoint.zero, size: size)
}
}

UIView animation shadowPath doesn't follow

I have a UIView with a expand function that I made.
When I call this function, the UIView stays centered, and I expand its width.
The issue is that my UIView has a shadow, with a shadowPath. Whenever I expand it, the shadow is imediatly expanded to the maximum width the view will get at the end of the animation, instead of following the bounds.
func expand() {
guard self.expanded == false else {
return
}
let originX = self.frame.origin.x
let originY = self.frame.origin.y
let originWidth = self.frame.width
let originHeight = self.frame.height
let newWidth = self.frame.width * 2
let newRect = CGRect(x: originX - ((newWidth - originWidth) / 2), y: originY, width: newWidth, height: originHeight)
self.expanded = true
UIView.animate(withDuration: 0.4, animations: {
self.frame = newRect
self.layoutIfNeeded()
}) { (completed) in
}
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
}
UIView explicitly (but privately) chooses which of its layer's properties to implicitly animate, and shadowPath is not one of them. You have to animate it yourself.

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
So how you are doing it is fine. Here would be two different ways. The first with Core Graphics. You may want to update methods and even make the color gradient in the sublayer.
1st Way
import UIKit
class Indicator: UIView {
var padding : CGFloat = 5.0
var minimumSpace : CGFloat = 4.0
var indicators : CGFloat = 40
var indicatorColor : UIColor = UIColor.lightGray
var filledIndicatorColor = UIColor.blue
var currentProgress = 0.0
var radiusFactor : CGFloat = 0.25
var fillReversed = false
override init(frame: CGRect) {
super.init(frame: frame)
setUp(animated: false)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp(animated: false)
backgroundColor = UIColor.green
}
func updateProgress(progress:Double, animated:Bool) {
currentProgress = progress
setUp(animated: animated)
}
private func setUp(animated:Bool){
// internal space
let neededPadding = (indicators - 1) * minimumSpace
//calculate height and width minus padding and space since vertical
let height = (bounds.height - neededPadding - (padding * 2.0)) / indicators
let width = bounds.width - padding * 2.0
if animated == true{
let trans = CATransition()
trans.type = kCATransitionFade
trans.duration = 0.5
self.layer.add(trans, forKey: nil)
}
layer.sublayers?.removeAll()
for i in 0...Int(indicators - 1.0){
let indicatorLayer = CALayer()
indicatorLayer.frame = CGRect(x: padding, y: CGFloat(i) * height + padding + (minimumSpace * CGFloat(i)), width: width, height: height)
//haha i don't understand my logic below but it works hahaha
// i know it has to go backwards
if fillReversed{
if CGFloat(1 - currentProgress) * self.bounds.height < indicatorLayer.frame.origin.y{
indicatorLayer.backgroundColor = filledIndicatorColor.cgColor
}else{
indicatorLayer.backgroundColor = indicatorColor.cgColor
}
}else{
if CGFloat(currentProgress) * self.bounds.height > indicatorLayer.frame.origin.y{
indicatorLayer.backgroundColor = indicatorColor.cgColor
}else{
indicatorLayer.backgroundColor = filledIndicatorColor.cgColor
}
}
indicatorLayer.cornerRadius = indicatorLayer.frame.height * radiusFactor
indicatorLayer.masksToBounds = true
self.layer.addSublayer(indicatorLayer)
}
}
//handle rotation
override func layoutSubviews() {
super.layoutSubviews()
setUp(animated: false)
}
}
The second way is using CAShapeLayer and the benefit is that the progress will get a natural animation.
import UIKit
class Indicator: UIView {
var padding : CGFloat = 5.0
var minimumSpace : CGFloat = 4.0
var indicators : CGFloat = 40
var indicatorColor : UIColor = UIColor.lightGray
var filledIndicatorColor = UIColor.blue
var currentProgress = 0.0
var radiusFactor : CGFloat = 0.25
private var progressLayer : CALayer?
private var shapeHoles : CAShapeLayer?
override init(frame: CGRect) {
super.init(frame: frame)
transparentDotsAndProgress()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
transparentDotsAndProgress()
}
func updateProgress(progress:Double) {
if progress <= 1 && progress >= 0{
currentProgress = progress
transparentDotsAndProgress()
}
}
//handle rotation
override func layoutSubviews() {
super.layoutSubviews()
transparentDotsAndProgress()
}
func transparentDotsAndProgress(){
self.layer.masksToBounds = true
let neededPadding = (indicators - 1) * minimumSpace
//calculate height and width minus padding and space since vertical
let height = (bounds.height - neededPadding - (padding * 2.0)) / indicators
let width = bounds.width - padding * 2.0
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height), cornerRadius: 0)
path.usesEvenOddFillRule = true
for i in 0...Int(indicators - 1.0){
let circlePath = UIBezierPath(roundedRect: CGRect(x: padding, y: CGFloat(i) * height + padding + (minimumSpace * CGFloat(i)), width: width, height: height), cornerRadius: height * radiusFactor)
path.append(circlePath)
}
if progressLayer == nil{
progressLayer = CALayer()
progressLayer?.backgroundColor = filledIndicatorColor.cgColor
self.layer.addSublayer(progressLayer!)
}
progressLayer?.frame = CGRect(x: 0, y: -self.bounds.height - padding + CGFloat(currentProgress) * self.bounds.height, width: bounds.width, height: bounds.height)
self.shapeHoles?.removeFromSuperlayer()
shapeHoles = CAShapeLayer()
shapeHoles?.path = path.cgPath
shapeHoles?.fillRule = kCAFillRuleEvenOdd
shapeHoles?.fillColor = UIColor.white.cgColor
shapeHoles?.strokeColor = UIColor.clear.cgColor
self.layer.backgroundColor = indicatorColor.cgColor
self.layer.addSublayer(shapeHoles!)
}
}
Both of these ways should work but the advantage of the CAShapeLayer is you get a natural animation.
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.
// For participating in Simulator's "slow animations"
#_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
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)
}
}
let progressLayer = CALayer()
let maskLayer = CAShapeLayer()
var skipLayoutSubviews = 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()
CATransaction.setCompletionBlock { [weak self] in
self?.skipLayoutSubviews = false
}
if !animated {
CATransaction.setDisableActions(true)
} else {
CATransaction.setAnimationDuration(0.25 * Double(UIAnimationDragCoefficient()))
}
let properties = progressLayerProperties()
progressLayer.bounds = properties.bounds
progressLayer.position = properties.position
skipLayoutSubviews = true
CATransaction.commit() // This triggers layoutSubviews
}
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
maskLayer.path = maskPath(for: maskLayer.bounds)
self.layer.addSublayer(maskLayer)
// Layer hierarchy
// self.maskLayer
// self.progressLayer
// self.layer
}
override func layoutSubviews() {
super.layoutSubviews()
if skipLayoutSubviews {
// Crude but effective, not fool proof though
skipLayoutSubviews = false
return
}
let timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
// Doesn't work for 180° rotations
let duration = UIApplication.shared.statusBarOrientationAnimationDuration * Double(UIAnimationDragCoefficient())
CATransaction.begin()
CATransaction.setAnimationTimingFunction(timingFunction)
CATransaction.setAnimationDuration(duration)
let properties = progressLayerProperties()
progressLayer.bounds = properties.bounds
progressLayer.position = properties.position
let size = self.bounds.size
let anchorPoint = CGPoint(x: 0.5, y: 1.0)
maskLayer.anchorPoint = anchorPoint
maskLayer.bounds = CGRect(origin: CGPoint.zero, size: size)
maskLayer.position = CGPoint(x: size.width * anchorPoint.x, y: size.height * anchorPoint.y)
// Animate the segments
let pathChangeAnimation = CAKeyframeAnimation(keyPath: "path")
let finalPath = maskPath(for: maskLayer.bounds)
pathChangeAnimation.values = [maskLayer.path!, finalPath]
pathChangeAnimation.keyTimes = [0, 1]
pathChangeAnimation.timingFunction = timingFunction
pathChangeAnimation.duration = duration
pathChangeAnimation.isRemovedOnCompletion = true
maskLayer.add(pathChangeAnimation, forKey: "pathAnimation")
CATransaction.setCompletionBlock {
// CAAnimation's don't actually change the value
self.maskLayer.path = finalPath
}
CATransaction.commit()
}
// 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 anchorPoint = maskLayer.anchorPoint
let bounds = CGRect(x: 0, y: 0, width: width, height: height)
let position = CGPoint(x: 0 + width * anchorPoint.x, y: y + height * anchorPoint.x)
return (bounds: bounds, position: position)
}
// TODO: 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
Enjoy the magic

Resources