How to make rounded corner progress bar in swift?
Although you have set the corner radius, you also need to tell the view not to draw anything outside of the view's bounds by setting
self.progressView.clipsToBounds = true
Making UIProgressView Rounded corners
After searching and trying I decided to create my own custom progress view. Here is the code for anyone who may find them selevs in same problem.
import Foundation
import UIKit
class CustomHorizontalProgressView: UIView {
var progress: CGFloat = 0.0 {
didSet {
setProgress()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clearColor()
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = CGRectGetHeight(self.frame) / 2.0
self.layer.borderColor = UIColor.grayColor().CGColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (CGRectGetWidth(self.frame) - margin) * progress
let height = CGRectGetHeight(self.frame) - margin
if (width < height) {
width = height
}
let pathRef = UIBezierPath(roundedRect: CGRect(x: margin / 2.0, y: margin / 2.0, width: width, height: height), cornerRadius: height / 2.0)
UIColor.redColor().setFill()
pathRef.fill()
UIColor.clearColor().setStroke()
pathRef.stroke()
pathRef.closePath()
self.setNeedsDisplay()
}
}
Just put above code in a swift file and drag drop a UIView in IB and give it class CustomHorizontalProgressView
. and That is it.
How to make a rounded or filled and rounded progress view swift in swift (with CAShapeLayers)
It was not simple for me to find out how to make a progress bar rounded.
So I created a reusable code, so everybody can use it simple. I created a class.
Here is the class:
import UIKit
class CircularProgressView: UIView {
fileprivate var progressLayer = CAShapeLayer()
fileprivate var trackLayer = CAShapeLayer()
fileprivate var didConfigureLabel = false
fileprivate var rounded: Bool
fileprivate var filled: Bool
fileprivate let lineWidth: CGFloat?
var timeToFill = 3.43
var progressColor = UIColor.white {
didSet{
progressLayer.strokeColor = progressColor.cgColor
}
}
var trackColor = UIColor.white {
didSet{
trackLayer.strokeColor = trackColor.cgColor
}
}
var progress: Float {
didSet{
var pathMoved = progress - oldValue
if pathMoved < 0{
pathMoved = 0 - pathMoved
}
setProgress(duration: timeToFill * Double(pathMoved), to: progress)
}
}
fileprivate func createProgressView(){
self.backgroundColor = .clear
self.layer.cornerRadius = frame.size.width / 2
let circularPath = UIBezierPath(arcCenter: center, radius: frame.width / 2, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true)
trackLayer.fillColor = UIColor.blue.cgColor
trackLayer.path = circularPath.cgPath
trackLayer.fillColor = .none
trackLayer.strokeColor = trackColor.cgColor
if filled {
trackLayer.lineCap = .butt
trackLayer.lineWidth = frame.width
}else{
trackLayer.lineWidth = lineWidth!
}
trackLayer.strokeEnd = 1
layer.addSublayer(trackLayer)
progressLayer.path = circularPath.cgPath
progressLayer.fillColor = .none
progressLayer.strokeColor = progressColor.cgColor
if filled {
progressLayer.lineCap = .butt
progressLayer.lineWidth = frame.width
}else{
progressLayer.lineWidth = lineWidth!
}
progressLayer.strokeEnd = 0
if rounded{
progressLayer.lineCap = .round
}
layer.addSublayer(progressLayer)
}
func trackColorToProgressColor() -> Void{
trackColor = progressColor
trackColor = UIColor(red: progressColor.cgColor.components![0], green: progressColor.cgColor.components![1], blue: progressColor.cgColor.components![2], alpha: 0.2)
}
func setProgress(duration: TimeInterval = 3, to newProgress: Float) -> Void{
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = progressLayer.strokeEnd
animation.toValue = newProgress
progressLayer.strokeEnd = CGFloat(newProgress)
progressLayer.add(animation, forKey: "animationProgress")
}
override init(frame: CGRect){
progress = 0
rounded = true
filled = false
lineWidth = 15
super.init(frame: frame)
filled = false
createProgressView()
}
required init?(coder: NSCoder) {
progress = 0
rounded = true
filled = false
lineWidth = 15
super.init(coder: coder)
createProgressView()
}
init(frame: CGRect, lineWidth: CGFloat?, rounded: Bool) {
progress = 0
if lineWidth == nil{
self.filled = true
self.rounded = false
}else{
if rounded{
self.rounded = true
}else{
self.rounded = false
}
self.filled = false
}
self.lineWidth = lineWidth
super.init(frame: frame)
createProgressView()
}
}
And here is how to use:
Create a new progress view
let progressView = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), lineWidth: 15, rounded: false)
If you use nil for the line width, a filled circle will appear like the second picture above.
Set the progress- and the track color:
progressView.progressColor = .blue
progressView.trackColor = .lightGray
If you use progressView.trackColorToProgressColor() set a track color to the progress color, but with a less alpha.
Set the position of the progressView (example):
progressView.center = view.center
You can use this because the progressView is of type UIView.
Add the progressView as a subview of the ViewControllers view:
view.addSubview(progressView)
Set the progress of the progressView:
progressView.progress = 0.6
The progress will be animated. If you don't want to animate the progress you can set the progressView.timeToFill to 0. If you want a faster or lower animating you can use this method, too.
I hope this code was helpful.
Rounded corner throughout the whole progress
I think you should search into the sublayer to find de blue one and apply the same treatment
Creating a progress indicator with a rounded rectangle
I found a solution so the loading indicator works for round corners:
let queueShapeLayer = CAShapeLayer()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Queue timer
let radius = addToQueue.layer.cornerRadius
let diameter = radius * 2
let totalLength = (addToQueue.frame.width - diameter) * 2 + (CGFloat.pi * diameter)
let queuePath = UIBezierPath(roundedRect: addToQueue.frame, cornerRadius: radius)
queueShapeLayer.path = queuePath.cgPath
queueShapeLayer.lineWidth = 5
queueShapeLayer.strokeColor = UIColor.white.cgColor
queueShapeLayer.fillColor = UIColor.clear.cgColor
queueShapeLayer.strokeStart = 0.25 - CGFloat.pi * diameter / 3 / totalLength // Change the '0.25' to 0.5, 0.75 etc. wherever you want the bar to start
queueShapeLayer.strokeEnd = queueShapeLayer.strokeStart + 0.5 // Change this to the value you want it to go to (in this case 0.5 or 50% loaded)
view.layer.addSublayer(queueShapeLayer)
}
After I had did this though, I was having problems that I couldn't animate the whole way round. To get around this, I created a second animation (setting strokeStart
to 0
) and then I placed completion blocks so I could trigger the animations at the correct time.
Tip:
Add animation.fillMode = CAMediaTimingFillMode.forwards
& animation.isRemovedOnCompletion = false
when using a CABasicAnimation
for the animation to wait until you remove it.
I hope this formula helps anyone in the future!
If you need help, you can always message me and I am willing to help. :)
Gradient progress bar with rounded corners SpriteKit Swift
It's about SKCropNode + its maskNode property. From the docs:
SKCropNode is a container node that you use to crop other nodes in the
scene. You add other nodes to a crop node and set the crop node's
maskNode property. For example, here are some ways you might specify a
mask:An untextured sprite that limits content to a rectangular portion of
the scene.A textured sprite that works as a precise per-pixel mask.
A collection of child nodes that form a unique shape.
You can animate the shape or contents of the mask to implement
interesting effects such as hiding or revealing.
So, a simple example would be like this:
class GameScene: SKScene {
override func sceneDidLoad() {
super.sceneDidLoad()
createProgressBar()
}
private func createProgressBar(){
let barFrame = CGRect(x: 0, y: 0, width: 300, height: 15)
if let cgImage = createImage(frame: barFrame) {
let texture = SKTexture(cgImage: cgImage)
let sprite = SKSpriteNode(texture: texture)
let cropNode = SKCropNode()
let mask = SKSpriteNode(color: .gray, size: barFrame.size)
cropNode.addChild(sprite)
cropNode.maskNode = mask
sprite.anchorPoint = CGPoint(x: 0.0, y: 0.5)
mask.anchorPoint = CGPoint(x: 0.0, y: 0.5)
var counter:Double = 0
let action = SKAction.run {[weak self, sprite] in
guard let `self` = self, counter < 100 else {
sprite?.removeAction(forKey: "loop")
return
}
counter += 1
let newWidth = self.getWidth(percents: counter, spriteWidth: barFrame.width)
print("Bar width \(newWidth), percentage \(counter)")
mask.size = CGSize(width: newWidth, height: barFrame.height)
}
let wait = SKAction.wait(forDuration: 0.05)
let sequence = SKAction.sequence([wait, action])
let loop = SKAction.repeatForever(sequence)
addChild(cropNode)
cropNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)
sprite.run(loop, withKey: "loop")
}
}
private func getWidth(percents:Double, spriteWidth:Double)->Double{
let onePercent = spriteWidth / 100.0
return onePercent * percents
}
private func createImage(frame barFrame:CGRect) -> CGImage?{
if let ciFilter = CIFilter(name: "CILinearGradient"){
let ciContext = CIContext()
ciFilter.setDefaults()
let startColor = CIColor(red: 0.75, green: 0.35, blue: 0.45, alpha: 1)
let endColor = CIColor(red: 0.45, green: 0.35, blue: 0.75, alpha: 1)
let startVector = CIVector(x: 0, y: 0)
let endVector = CIVector(x: barFrame.width, y: 0)
ciFilter.setValue(startColor, forKey: "inputColor0")
ciFilter.setValue(endColor, forKey: "inputColor1")
ciFilter.setValue(startVector, forKey: "inputPoint0")
ciFilter.setValue(endVector, forKey: "inputPoint1")
if let outputImage = ciFilter.outputImage {
let cgImage = ciContext.createCGImage(outputImage, from: CGRect(x: 0, y: 0, width: barFrame.width, height: barFrame.height))
return cgImage
}
}
return nil
}
}
Now cause this is just an example I won't go all the way to implement this right, but You can maybe make a class of it with designable and inspectable properties, optimize code, make it reusable etc. But the general idea is shown here.
You use SKCropNode
to add progress bar in it, and use maskNode
property to reveal progress bar as percentage increases. Also I gave a method to create texture programatically, but You can use just a .png file instead.
Crop node is here used only cause of a gradient (cause we don't wan't to scale image, but rather to show it part by part). Obviously, crop node is not needed if a progress bar had only one color.
Here is final result:
How to create non rounded corner UIProgressView in iOS
Simply change the progressViewStyle
to UIProgressView.Style.bar
, Default is UIProgressView.Style.default
.
Swift 3.0 and above
self.progressViewStyle = .bar /* default is .default */
Swift 2.0
self.progressViewStyle = UIProgressViewStyle.Bar
Related Topics
Blue Highlighting/Focus Ring on Catalyst App
Changing Color of Button Text and State
Convert Timestamp String with Epochal Time and Timezone into Nsdate
iOS Tabbar Item Title Issue in iOS13
Firebase Sign Out Not Working in Swift
What Is Other Option Available in Swift Instead of Refactoring and Renaming Class or Attribute Name
In Swift, How to Remove a Uiview from Memory Completely
Smooth Transition Between 2 Scrollviews
Swift - Class Method Which Must Be Overridden by Subclass
Multi-Component Picker (Uipickerview) in Swiftui
How Are Optional Values Implemented in Swift
Firebase Query Containing Value
Reversing the Order of a String Value
Wait Until an Asynchronous API Call Is Completed - Swift/Ios
Swift 3:Fatal Error: Double Value Cannot Be Converted to Int Because It Is Either Infinite or Nan