制作 UIProgressView 圆角
Making UIProgressView Rounded corners
我创建了一个 UIProgressView
具有以下属性
progressView.progressTintColor = UIColor.appChallengeColorWithAlpha(1.0)
progressView.trackTintColor = UIColor.clearColor()
progressView.clipsToBounds = true
progressView.layer.cornerRadius = 5
我正在使用 UIView
作为边框。看起来他的进度=1,这正是我想要的方式。
但如果进度值小于 1,则边角不圆。
我错过了什么吗?我怎样才能让它变成圆角?
是的,遗漏了一件事...拐角半径设置为 progressview 并且按预期反映..
但是如果你想让你的轨道图像是圆形的,你必须自定义你的进度视图。
你必须使用带圆角的图像。
[progressView setTrackImage:[UIImage imageNamed:@"roundedTrack.png"]];
//roundedTrack.png must be of rounded corner
以上代码将帮助您更改进度视图的 trackView 图像。
您可能会遇到图像拉伸不当的问题。您必须使图像可调整大小。
如果出现问题,下面的 link 可能会有用
https://www.natashatherobot.com/ios-stretchable-button-uiedgeinsetsmake/
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = ProgessView(frame: CGRect(x: 20, y: 200, width: 100, height: 10))
view.addSubview(v)
//v.progressLayer.strokeEnd = 0.8
}
}
class ProgessView: UIView {
lazy var progressLayer: CAShapeLayer = {
let line = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 5, y: 5))
path.addLine(to: CGPoint(x: self.bounds.width - 5, y: 5))
line.path = path.cgPath
line.lineWidth = 6
line.strokeColor = UIColor(colorLiteralRed: 127/255, green: 75/255, blue: 247/255, alpha: 1).cgColor
line.strokeStart = 0
line.strokeEnd = 0.5
line.lineCap = kCALineCapRound
line.frame = self.bounds
return line
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
layer.cornerRadius = 5
layer.borderWidth = 1
layer.borderColor = UIColor(colorLiteralRed: 197/255, green: 197/255, blue: 197/255, alpha: 1).cgColor
layer.addSublayer(progressLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
测试我的代码。您可以根据需要设计高度和宽度。您可以使用 strokeEnd
来更改 progressView 的进度。您可以为其添加动画。但实际上,它已经是可动画的,您可以更改 strokeEnd
的值以查看其主要效果。如果你想设计自己的动画。像下面那样尝试 CATransaction
。
func updateProgress(_ progress: CGFloat) {
CATransaction.begin()
CATransaction.setAnimationDuration(3)
progressLayer.strokeEnd = progress
CATransaction.commit()
}
设置线上限:
.lineCap = kCALineCapRound;
我遇到了完全相同的问题,这就是我疯狂谷歌搜索后想到你的问题的原因。问题是双重的。首先,如何使进度条的内部在最后变成圆形(季亨达的回答显示了如何做),其次,如何使您添加的 CAShapeLayer 的圆形末端与原始进度的方形末端匹配下面的栏(另一个 Whosebug 问题的答案对此有帮助)如果你替换季亨达的答案中的这行代码:
path.addLine(到: CGPoint(x: self.bounds.width - 5, y: 5))
有了这个:
path.addLine(to: CGPoint(x: (Int(self.progress * Float(self.bounds.width))), y: 5))
您一定会得到想要的结果。
经过搜索和尝试,我决定创建自己的自定义进度视图。以下代码适用于可能会在同一问题中找到他们的人。
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()
}
}
只需将上面的代码放在 swift 文件中,然后在 IB 中拖放一个 UIView 并给它 class CustomHorizontalProgressView
。就是这样。
UIProgressView 有两部分,进度部分和轨迹部分。如果你使用 Reveal,你可以看到它只有两个子视图。进度视图层次结构非常简单。所以...
Objective-C
- (void)layoutSubviews
{
[super layoutSubviews];
[self.progressView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.layer.masksToBounds = YES;
obj.layer.cornerRadius = kProgressViewHeight / 2.0;
}];
}
Swift(3、4 和 5+)
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = kProgressViewHeight / 2.0
}
}
我承认subclass
或extend
progressView
是推荐的方式。如果您不想为了如此简单的效果而这样做,这可能会奏效。
记住 Apple 会更改视图层次结构,可能会出错 的情况。
只需在初始化中执行此操作
layer.cornerRadius = *desired_corner_radius*
clipsToBounds = true
使用swift 4.0我是这样操作的:
let progressViewHeight: CGFloat = 4.0
// Set progress view height
let transformScale = CGAffineTransform(scaleX: 1.0, y: progressViewHeight)
self.progressView.transform = transformScale
// Set progress round corners
self.progressView.layer.cornerRadius = progressViewHeight
self.progressView.clipsToBounds = true
//更新 swift 4
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.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = self.frame.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (self.frame.width - margin) * progress
let height = self.frame.height - 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.red.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
self.setNeedsDisplay()
}
}
基本上进度视图的(默认样式)子视图由 2 个图像视图组成。
一份给 "progress",一份给 "track"。
您可以循环进度视图的子视图,并设置每个图像视图的角半径。
for let view: UIView in self.progressView.subviews {
if view is UIImageView {
view.clipsToBounds = true
view.layer.cornerRadius = 15
}
}
Swift 4.2 版本来自 Umair Afzal 的解决方案
class CustomHorizontalProgressView: UIView {
var strokeColor: UIColor?
var progress: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = frame.size.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (frame.size.width - margin) * progress
let height = frame.size.height - 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)
strokeColor?.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
}
}
并使用它
var progressView: CustomHorizontalProgressView = {
let view = CustomHorizontalProgressView()
view.strokeColor = UIColor.orange
view.progress = 0.5
return view
}()
很晚才回答,但实际上我遇到了同样的问题。
这是我最简单的解决方案(无需代码!):
- 添加一个容器来嵌入您的进度视图
- 容器的圆角(10 = 容器高度 / 2)
- 结果:)
另一个混合答案,超级 hacky 但使用起来非常快。
您可以只抓取子层并设置其半径。无需编写自己的 UIProgressView 或弄乱剪辑路径。
progressView.layer.cornerRadius = 5
progressView.layer.sublayers[1].cornerRadius = 5
progressView.subviews[1]. clipsToBounds = true
progressView.layer.masksToBounds = true
所以你绕过整个 UIProgressView 的角落(不需要 ClipsToBounds)
然后填充条是第二个子层,所以你可以抓住它并绕过它的角落,但你还需要将该层的子视图设置为 clipsToBounds。
然后将整个图层设置为其边界遮罩,一切看起来都不错。
显然,这 大量 依赖于 UIProgressView 的设置不变,第二个 subview/layer 是填充视图。
但是。如果您对这个假设感到满意,那么使用超级简单的代码。
我创建了一个 UIProgressView
具有以下属性
progressView.progressTintColor = UIColor.appChallengeColorWithAlpha(1.0)
progressView.trackTintColor = UIColor.clearColor()
progressView.clipsToBounds = true
progressView.layer.cornerRadius = 5
我正在使用 UIView
作为边框。看起来他的进度=1,这正是我想要的方式。
但如果进度值小于 1,则边角不圆。
我错过了什么吗?我怎样才能让它变成圆角?
是的,遗漏了一件事...拐角半径设置为 progressview 并且按预期反映..
但是如果你想让你的轨道图像是圆形的,你必须自定义你的进度视图。 你必须使用带圆角的图像。
[progressView setTrackImage:[UIImage imageNamed:@"roundedTrack.png"]];
//roundedTrack.png must be of rounded corner
以上代码将帮助您更改进度视图的 trackView 图像。
您可能会遇到图像拉伸不当的问题。您必须使图像可调整大小。 如果出现问题,下面的 link 可能会有用 https://www.natashatherobot.com/ios-stretchable-button-uiedgeinsetsmake/
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = ProgessView(frame: CGRect(x: 20, y: 200, width: 100, height: 10))
view.addSubview(v)
//v.progressLayer.strokeEnd = 0.8
}
}
class ProgessView: UIView {
lazy var progressLayer: CAShapeLayer = {
let line = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 5, y: 5))
path.addLine(to: CGPoint(x: self.bounds.width - 5, y: 5))
line.path = path.cgPath
line.lineWidth = 6
line.strokeColor = UIColor(colorLiteralRed: 127/255, green: 75/255, blue: 247/255, alpha: 1).cgColor
line.strokeStart = 0
line.strokeEnd = 0.5
line.lineCap = kCALineCapRound
line.frame = self.bounds
return line
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
layer.cornerRadius = 5
layer.borderWidth = 1
layer.borderColor = UIColor(colorLiteralRed: 197/255, green: 197/255, blue: 197/255, alpha: 1).cgColor
layer.addSublayer(progressLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
测试我的代码。您可以根据需要设计高度和宽度。您可以使用 strokeEnd
来更改 progressView 的进度。您可以为其添加动画。但实际上,它已经是可动画的,您可以更改 strokeEnd
的值以查看其主要效果。如果你想设计自己的动画。像下面那样尝试 CATransaction
。
func updateProgress(_ progress: CGFloat) {
CATransaction.begin()
CATransaction.setAnimationDuration(3)
progressLayer.strokeEnd = progress
CATransaction.commit()
}
设置线上限:
.lineCap = kCALineCapRound;
我遇到了完全相同的问题,这就是我疯狂谷歌搜索后想到你的问题的原因。问题是双重的。首先,如何使进度条的内部在最后变成圆形(季亨达的回答显示了如何做),其次,如何使您添加的 CAShapeLayer 的圆形末端与原始进度的方形末端匹配下面的栏(另一个 Whosebug 问题的答案对此有帮助
path.addLine(到: CGPoint(x: self.bounds.width - 5, y: 5))
有了这个:
path.addLine(to: CGPoint(x: (Int(self.progress * Float(self.bounds.width))), y: 5))
您一定会得到想要的结果。
经过搜索和尝试,我决定创建自己的自定义进度视图。以下代码适用于可能会在同一问题中找到他们的人。
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()
}
}
只需将上面的代码放在 swift 文件中,然后在 IB 中拖放一个 UIView 并给它 class CustomHorizontalProgressView
。就是这样。
UIProgressView 有两部分,进度部分和轨迹部分。如果你使用 Reveal,你可以看到它只有两个子视图。进度视图层次结构非常简单。所以...
Objective-C
- (void)layoutSubviews
{
[super layoutSubviews];
[self.progressView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.layer.masksToBounds = YES;
obj.layer.cornerRadius = kProgressViewHeight / 2.0;
}];
}
Swift(3、4 和 5+)
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { subview in
subview.layer.masksToBounds = true
subview.layer.cornerRadius = kProgressViewHeight / 2.0
}
}
我承认subclass
或extend
progressView
是推荐的方式。如果您不想为了如此简单的效果而这样做,这可能会奏效。
记住 Apple 会更改视图层次结构,可能会出错 的情况。
只需在初始化中执行此操作
layer.cornerRadius = *desired_corner_radius*
clipsToBounds = true
使用swift 4.0我是这样操作的:
let progressViewHeight: CGFloat = 4.0
// Set progress view height
let transformScale = CGAffineTransform(scaleX: 1.0, y: progressViewHeight)
self.progressView.transform = transformScale
// Set progress round corners
self.progressView.layer.cornerRadius = progressViewHeight
self.progressView.clipsToBounds = true
//更新 swift 4
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.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = self.frame.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (self.frame.width - margin) * progress
let height = self.frame.height - 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.red.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
self.setNeedsDisplay()
}
}
基本上进度视图的(默认样式)子视图由 2 个图像视图组成。 一份给 "progress",一份给 "track"。 您可以循环进度视图的子视图,并设置每个图像视图的角半径。
for let view: UIView in self.progressView.subviews {
if view is UIImageView {
view.clipsToBounds = true
view.layer.cornerRadius = 15
}
}
Swift 4.2 版本来自 Umair Afzal 的解决方案
class CustomHorizontalProgressView: UIView {
var strokeColor: UIColor?
var progress: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
super.draw(rect)
setProgress()
}
func setProgress() {
var progress = self.progress
progress = progress > 1.0 ? progress / 100 : progress
self.layer.cornerRadius = frame.size.height / 2.0
self.layer.borderColor = UIColor.gray.cgColor
self.layer.borderWidth = 1.0
let margin: CGFloat = 6.0
var width = (frame.size.width - margin) * progress
let height = frame.size.height - 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)
strokeColor?.setFill()
pathRef.fill()
UIColor.clear.setStroke()
pathRef.stroke()
pathRef.close()
}
}
并使用它
var progressView: CustomHorizontalProgressView = {
let view = CustomHorizontalProgressView()
view.strokeColor = UIColor.orange
view.progress = 0.5
return view
}()
很晚才回答,但实际上我遇到了同样的问题。
这是我最简单的解决方案(无需代码!):
- 添加一个容器来嵌入您的进度视图
- 容器的圆角(10 = 容器高度 / 2)
- 结果:)
另一个混合答案,超级 hacky 但使用起来非常快。
您可以只抓取子层并设置其半径。无需编写自己的 UIProgressView 或弄乱剪辑路径。
progressView.layer.cornerRadius = 5
progressView.layer.sublayers[1].cornerRadius = 5
progressView.subviews[1]. clipsToBounds = true
progressView.layer.masksToBounds = true
所以你绕过整个 UIProgressView 的角落(不需要 ClipsToBounds) 然后填充条是第二个子层,所以你可以抓住它并绕过它的角落,但你还需要将该层的子视图设置为 clipsToBounds。
然后将整个图层设置为其边界遮罩,一切看起来都不错。
显然,这 大量 依赖于 UIProgressView 的设置不变,第二个 subview/layer 是填充视图。
但是。如果您对这个假设感到满意,那么使用超级简单的代码。