当前位置:网站首页>3-progressbar and secondary cropping
3-progressbar and secondary cropping
2022-06-23 08:24:00 【the Summer Palace】
In this section, we will create a custom progress bar component . Before that , We need to be right about RectanglePainer and TextPainter Make some extensions .
Expand RectanglePainter
stay RectanglePainter Add two functions to :
// 1
public static func drawBorder(_ frame: CGRect, borderColor: UIColor, borderWidth: CGFloat, cornerRadius: CGFloat) {
// 2
let rect = insetFrame(frame, delta: borderWidth)
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
// 3
borderColor.setStroke()
path.lineWidth = borderWidth
path.stroke()
}
// 4
public static func drawFillColor(_ frame: CGRect, fillColor: UIColor, cornerRadius: CGFloat) {
// 5
let path = UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius)
// 6
fillColor.setFill()
path.fill()
context?.restoreGState()
}
- drawBorder Method to draw the border of a rounded rectangle , But don't fill .
- Create a path , Calculation frame Line width will be deducted .
- this 3 Sentence draws a rectangular border .
- drawFillColor Method to draw a filled rectangle ( Non gradient ).
- Create a path .
- Fill color .
Expand TextPainter
First of all to drawText Method add one innerShadow Parameters :
public static func drawText(_ rect: CGRect, text: Text, innerShadow: NSShadow?) {
stay text.text.draw(in: textRect, withAttributes: fontAttributes) Add code after a sentence :
if let shadow = innerShadow, let color = shadow.shadowColor as? UIColor {
context?.setAlpha(color.cgColor.alpha)
context?.beginTransparencyLayer(auxiliaryInfo: nil)
let textOpaqueTextShadow = color.withAlphaComponent(1)
context?.setShadow(offset: shadow.shadowOffset, blur: shadow.shadowBlurRadius, color: textOpaqueTextShadow.cgColor)
context?.setBlendMode(.sourceOut)
context?.beginTransparencyLayer(auxiliaryInfo: nil)
textOpaqueTextShadow.setFill()
let textInnerShadowFontAttributes = [
.font: text.font,
.foregroundColor: color,
.paragraphStyle: paragraphStyle,
] as [NSAttributedString.Key: Any]
text.text.draw(in: textRect, withAttributes: textInnerShadowFontAttributes)
context?.endTransparencyLayer()
context?.endTransparencyLayer()
}
If innerShadow Not empty , Draw text shadow . The method of drawing inner shadow of text is similar to that of inner shadow of rectangle , Draw a shaded text over the original text again .
ProgressBar
Next, we'll implement ProgressBar, The first is the attribute declaration :
class ProgressBar: UIControl {
var bgColor = UIColor(red: 0.7, green: 0.9, blue: 0.9, alpha: 1.000)
var borderColor = UIColor(red: 0.0, green: 0.553, blue: 0.459, alpha: 1.000)
var textColor = UIColor(red: 0.173, green: 0.165, blue: 0.165, alpha: 1.000)
var gradient = CGGradient(colorsSpace: nil, colors: [UIColor.black.cgColor, UIColor.white.cgColor] as CFArray, locations: [1, 0])!
var cornerRadius:CGFloat = 15
var borderWidth:CGFloat = 2
var progress: CGFloat = 0 {
didSet{
if progress > 1 {
progress = 1
}else if progress < 0{
progress = 0
}
setNeedsDisplay()
}
}
lazy var barInnerShadow: NSShadow = {
let innerShadow = NSShadow()
innerShadow.shadowColor = UIColor.white
innerShadow.shadowOffset = CGSize(width: 3, height: 3)
innerShadow.shadowBlurRadius = 5
return innerShadow
}()
lazy var innerShadow: NSShadow = {
let innerShadow = NSShadow()
innerShadow.shadowColor = UIColor.gray
innerShadow.shadowOffset = CGSize(width: 3, height: 3)
innerShadow.shadowBlurRadius = 5
return innerShadow
}()
lazy var backgroundPath: UIBezierPath = {
let path = UIBezierPath(roundedRect: CGRect(x: borderWidth, y: borderWidth, width: frame.width-borderWidth*2, height: frame.height-borderWidth*2), cornerRadius: cornerRadius)
return path
}()
}
And then the most important draw Method :
override func draw(_ rect: CGRect) {
// 1
RectanglePainter.drawBorder(rect, borderColor: borderColor, borderWidth: borderWidth, cornerRadius: cornerRadius)
// 2
let frame = RectanglePainter.insetFrame(frame, delta: borderWidth)
RectanglePainter.drawFillColor(frame, fillColor: bgColor, cornerRadius: cornerRadius)
// 3
RectanglePainter.drawInnerShadow(frame, cornerRadius: cornerRadius, innerShadow: innerShadow)
// 4
let barFrame = CGRect(x:frame.minX, y: frame.minY, width: frame.width*progress, height: frame.height)
RectanglePainter.drawGradient(gradient: gradient, frame: barFrame, cornerRadius: cornerRadius, outerShadow: barInnerShadow)
// 5
RectanglePainter.drawInnerShadow(barFrame, cornerRadius: cornerRadius, innerShadow: barInnerShadow)
// 6
let str = String(format: "%.0f%%", progress*100)
let text = Text(text: str, font:UIFont.systemFont(ofSize: UIFont.systemFontSize), color: textColor)
TextPainter.drawText(frame, text: text, innerShadow: barInnerShadow)
}
- bound box .
- Draw a fill color background .
- Draw the inner shadow of the background .
- Draw gradient fill , I.e. sliding bar of progress bar .
- Draw the inner shadow of the slider .
- Draw text ( Progress value )
test
For testing purposes , Add one more run Method :
func run() {
var date = Date()
date.addTimeInterval(1)
let timer = Timer(fire: date, interval: 0.1, repeats: true) { [weak self] t in
if let progress = self?.progress, progress >= 1 {
self?.progress = 0
}else {
self?.progress += 0.01
}
}
RunLoop.current.add(timer, forMode: .common)
}
stay viewDidLoad In the method :
let bar = ProgressBar(frame: CGRect(x: 100, y:200, width: 190, height: 47))
bar.backgroundColor = .white
addSubview(bar)
bar.run()
The effect is as follows :

You can see , When the progress bar starts moving , Somewhat bug. The slider is larger than the border . We need to solve this problem next .
modify Bug
This problem is not easy to solve , Because this is Core Graphics A limitation in drawing rounded rectangles . Here is just a simple solution , When the width of the slider is too narrow , We draw ellipses instead of rounded rectangles .
First, we need to extend RectanglePainter Of drawGradient Functions and drawInnerShadow function :
public static func drawGradient(gradient: CGGradient?, path: UIBezierPath, cornerRadius: CGFloat?, outerShadow: NSShadow?) {
let context = UIGraphicsGetCurrentContext()
if let context = context {
let frame = path.bounds
Rectangle Drawing
context.saveGState()
if let shadow = outerShadow, let color = shadow.shadowColor as? UIColor {
context.setShadow(offset: shadow.shadowOffset, blur: shadow.shadowBlurRadius, color: color.cgColor)
}
context.beginTransparencyLayer(auxiliaryInfo: nil)
path.addClip()
if let gradient = gradient {
context.drawLinearGradient(gradient, start: CGPoint(x: frame.midX, y: frame.minY), end: CGPoint(x: frame.midX, y: frame.maxY), options: [])
}else {
path.fill()
}
context.endTransparencyLayer()
context.restoreGState()
}
}
public static func drawInnerShadow(_ path: UIBezierPath, cornerRadius: CGFloat?, innerShadow:NSShadow) {
let context = UIGraphicsGetCurrentContext()
if let context = context, let color = innerShadow.shadowColor as? UIColor {
context.saveGState()
context.clip(to: path.bounds)
// context.setAlpha(color.cgColor.alpha)
context.beginTransparencyLayer(auxiliaryInfo: nil)
let rectangleOpaqueShadow = color // color.withAlphaComponent(1)
context.setShadow(offset: innerShadow.shadowOffset, blur: innerShadow.shadowBlurRadius, color: rectangleOpaqueShadow.cgColor)
context.setBlendMode(.sourceOut)
context.beginTransparencyLayer(auxiliaryInfo: nil)
rectangleOpaqueShadow.setFill()
path.fill()
context.endTransparencyLayer()
context.endTransparencyLayer()
context.restoreGState()
}
}
Compared with the original function , These two functions accept a path Parameters , And draw this path. Other code is basically the same .
And then in ProgressBar in , Add a function :
private func rectanglePathFixed(_ frame: CGRect, cornerRadius: CGFloat?) -> UIBezierPath {
var radius = cornerRadius ?? 0
var rect = frame
if frame.width < frame.height {
let fix = (frame.height-frame.width)/2
rect = CGRect(x: frame.minX, y: frame.minY+fix, width: frame.width, height: frame.width)
radius = rect.width/2
return UIBezierPath(ovalIn: rect)
}
return UIBezierPath(roundedRect: rect, cornerRadius: radius)
}
This function generates different values according to the width and height of the current slider path, If frame The width of is less than the height , Then we generate an ellipse to return , Otherwise, return to the normal rounded rectangle .
stay draw In the method , Replace old drawGradient Functions and drawInnerShadow function :
let path = rectanglePathFixed(barFrame, cornerRadius: cornerRadius)
RectanglePainter.drawGradient(gradient: gradient, path: path, cornerRadius: cornerRadius, outerShadow: barInnerShadow)
// 5
RectanglePainter.drawInnerShadow(path, cornerRadius: cornerRadius, innerShadow: barInnerShadow)
This plan is not perfect , But it is much better than before :

Path clipping clip
To solve this problem perfectly , Path clipping is required . First let's look at drawInnerShadow Method , Add a clipRect Parameters :
public static func drawInnerShadow(_ frame: CGRect, cornerRadius: CGFloat?, innerShadow:NSShadow, clipRect: CGRect?) {
let context = UIGraphicsGetCurrentContext()
if let context = context, let color = innerShadow.shadowColor as? UIColor {
let path = UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius ?? 0)
context.saveGState()
// 1
if let rect = clipRect {
context.clip(to: rect)
}else {
path.addClip()
}
context.beginTransparencyLayer(auxiliaryInfo: nil)
let rectangleOpaqueShadow = color // color.withAlphaComponent(1)
context.setShadow(offset: innerShadow.shadowOffset, blur: innerShadow.shadowBlurRadius, color: rectangleOpaqueShadow.cgColor)
context.setBlendMode(.sourceOut)
context.beginTransparencyLayer(auxiliaryInfo: nil)
rectangleOpaqueShadow.setFill()
path.fill()
context.endTransparencyLayer()
context.endTransparencyLayer()
context.restoreGState()
}
}
- Added a judgment , but clipRect When the parameter is not empty , Path clipping uses clipRect Parameters , Otherwise, use path. This ensures that the current inner shadow does not draw the border of the control .
And then there was drawGradient function , Also add a clipRect Parameters :
public static func drawGradient(gradient: CGGradient?, frame: CGRect, cornerRadius: CGFloat?, outerShadow: NSShadow?, clipRect: CGRect?) {
let context = UIGraphicsGetCurrentContext()
if let context = context {
let path = UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius ?? 0)
context.saveGState()
if let shadow = outerShadow, let color = shadow.shadowColor as? UIColor {
context.setShadow(offset: shadow.shadowOffset, blur: shadow.shadowBlurRadius, color: color.cgColor)
}
// 1
if let rect = clipRect {
let clipPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius ?? 0)
clipPath.addClip()
}
// 2
path.addClip()
if let gradient = gradient {
context.drawLinearGradient(gradient, start: CGPoint(x: frame.midX, y: frame.minY), end: CGPoint(x: frame.midX, y: frame.maxY), options: [])
}else {
path.fill()
}
// 3
context.restoreGState()
}
}
If clipRect Parameter is not empty , take clipRect Set as the cutting area and cut .
take path Add cutting area for secondary cutting .
Restore the state .
Then we modified the other two overloaded methods , add clipRect Parameters :
public static func drawGradient(gradient: CGGradient?, frame: CGRect, cornerRadius: CGFloat?, outerShadow: NSShadow?) {
drawGradient(gradient: gradient, frame: frame, cornerRadius: cornerRadius, outerShadow: outerShadow, clipRect: nil)
}
public static func drawInnerShadow(_ frame: CGRect, cornerRadius: CGFloat?, innerShadow:NSShadow) {
drawInnerShadow(frame, cornerRadius: cornerRadius, innerShadow: innerShadow, clipRect: nil)
}
modify ProgressBar Of draw(rect:) Method , Add... When drawing the slider clipRect Parameters :
RectanglePainter.drawGradient(gradient: gradient, frame: barFrame, cornerRadius: cornerRadius, outerShadow: barInnerShadow, clipRect: frame)
RectanglePainter.drawInnerShadow(barFrame, cornerRadius: cornerRadius, innerShadow: barInnerShadow, clipRect: frame)
Running results :

边栏推荐
- How to solve the problem that flv video stream cannot be played and TS file generation fails due to packet loss?
- Apache Solr arbitrary file read replication
- Crawler frame
- 3-ProgressBar和二次裁剪
- 驱动架构 & platform平台总线驱动模型
- 渲染效果图哪家好?2022最新实测(四)
- Fillet the tabbar with the flutter
- Easycvr accesses the website through the domain name. How to solve the problem that the video cannot be viewed back?
- Do not put files with garbled names into the CFS of NFS protocol
- What is a dedicated server line
猜你喜欢

开源技术交流丨批流一体数据同步引擎ChunJun数据还原-DDL功能模块解析

QT irregular shape antialiasing
![[paper notes] catching both gray and black swans: open set supervised analog detection*](/img/52/787b25a9818cfc6a1897af81d41ab2.png)
[paper notes] catching both gray and black swans: open set supervised analog detection*

值得反复回味的81句高人高语

Does huangrong really exist?

如何在conda虚拟环境开启jupyter-notebook
![Vulnhub | dc: 4 | [actual combat]](/img/33/b7422bdb18f39e9eb55855dbf1d584.png)
Vulnhub | dc: 4 | [actual combat]

What are the PCB characteristics inspection items?

复选框的基本使用与实现全选和反选功能

图像分割-改进网络结构
随机推荐
黄蓉真的存在吗?
9 ways in which network security may change in 2022
Crawler frame
Vulnhub | DC: 3 |【实战】
正则表达式使用案例
坑爹的“敬业福”:支付宝春晚红包技术大爆发
驱动架构 & platform平台总线驱动模型
odoo项目 发送信息到微信公众号或企业微信的做法
Quickly create a consumer cluster
C# 内存法复制图像bitmap
Regular expression use cases
Check the file through the port
Markdown learning
Vulnhub | dc: 3 | [actual combat]
自组织映射神经网络(SOM)
论文阅读【Quo Vadis, Action Recognition? A New Model and the Kinetics Dataset】
Location of firewalld configuration file
开源软件、自由软件、Copyleft、CC都是啥,傻傻分不清楚?
Production environment server environment setup + project release process
MySQL小册子笔记 5 InnoDB 记录存储结构