怎样运用swiftUI制作进度条

这个标题名起完之后,我就觉得小了,格式小了!由于在本篇文章的内容里,不仅仅有一个绘图的功用,还会交叉一下比较实用的开发tips。

本文代码来自于我个人研发的满满财表,感兴趣的小伙伴能够去下载一个体验一下,针关于个人的财政管理东西。

终究作用

SwiftUI开发总结(三)

拆解问题

这是咱们惯用的解题策略,一旦遇到一个相对复杂的问题时,算法题也好,数学题也好,程序开发也好,首先要想到的一定是化繁为简。

假如一个复杂问题,不能被拆解成为若干个或者是单个重复的简略问题,那么,咱们的思路一定是错的。

上面这句话并非毫无根据的主观臆断,而是遵从于傅立叶改换的思想办法。

因而第一步,咱们先将终究作用的款式拆分一下,首先是环形进度条,与之前的风格相同,不废话直接上代码:

struct XXRingShape: Shape {
  var progress: Double = 0.0
  var size: CGSize
  var animatableData: Double {
    get { progress }
    set { progress = newValue }
  }
  func path(in rect: CGRect) -> Path {
    var path = Path()
    let center = CGPoint(x: size.width/2, y: size.height/2)
    let radius: CGFloat = (size.width - XXCircleProgressBar.spacing)/2
    path.addArc(center: center,
          radius: radius,
          startAngle: .degrees(-90),
          endAngle: .degrees(-90 + (360 * progress)),
          clockwise: false)
    return path
  }
}

其间的Shape能够理解为是Core Animation框架中的CAShapeLayer,假如不清楚CAShapeLayer是什么的,也没有问题,CAShapeLayer就像是一个画家,能够用来制作和烘托2D形状。它能够创立复杂的形状,如圆形、矩形和曲线,并供给了许多特点来控制形状的外观和行为。它还能够与动画一同运用,以创立流通的过渡作用,要结合path运用。

path的每个参数都非常简略,因而就不过多介绍了,这里需求留意的是animatableData这个特点。

Shape需求运用animatableData才干履行动画是由于Shape是根据途径制作的,而animatableData能够帮助咱们控制途径的改变,然后完成Shape的动画作用。

在往上一层,在运用XXRingShape的时分,需求清晰给它一个size,那么怎样给它一个清晰的尺寸呢?

运用GeometryReader

之前挖了一个坑,在说到List上拉下拉的时分,咱们提过,想要知道父视图的矩形框,swiftUI为咱们供给了GeometryReader用来解决此类问题。

那么,应该怎样运用呢?上代码:

struct XXCircleProgressBar: View {
  var progress: Double
  var color: Color
  @State var scaleValue: Double = 0
  static let spacing: Double = 8
  var lineWidth: Double
  init(progress: Double, color: Color, lineWidth: Double) {
    self.progress = progress
    self.color = color
    self.lineWidth = lineWidth
  }
  var body: some View {
    GeometryReader { geometry in
      ZStack() {
                XXRingShape(progress: 1, size: geometry.size)
        .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
        .foregroundColor(color.opacity(0.2))
       
        XXRingShape(progress: scaleValue, size: geometry.size)
        .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
        .foregroundColor(color)
        .animation(.easeOut(duration: 1), value: scaleValue)
        .onAppear {
          scaleValue = progress
        }
        .onDisappear {
          scaleValue = 0
        }
        .frame(width: geometry.size.width, height: geometry.size.width)
      }
    }
  }
}

GeometryReader就像是一个丈量东西,用来丈量你的swiftUI父视图的大小和方位,帮助你更好地理解和布局你的应用程序。其间的geometry能够理解为是你的父视图,经过对他的大小计算,然后确认包内视图的大小及方位。

一旦咱们需求准确布局,完成一些自定义的动画作用,上拉下拉等作用,就离不开运用GeometryReader

在第一篇 《swiftUI总结》 中咱们提到了特点修饰器– @propertyWrapper,也简略地讲述了conbime是什么。而@State便是swiftUI供给的,用来观察特点改变的特点修饰器。swiftUI中的@State修饰符用于在视图中声明和管理状况。与其他修饰符不同,@State是用于管理视图内部状况的修饰符。能够运用@State来声明变量,并且能够在视图中运用它们。这些变量的值能够更改,并且当值更改时,视图将主动从头制作。

完好代码

没错,历来不拿你们当外人,直接把开头代码的作用代码甩出来:

import SwiftUI
extension Color {
  init(hexString: String) {
    let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
    var int = UInt64()
    Scanner(string: hex).scanHexInt64(&int)
    let r, g, b: UInt64
    switch hex.count {
    case 3: // RGB (12-bit)
      (r, g, b) = ((int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
    case 6: // RGB (24-bit)
      (r, g, b) = (int >> 16, int >> 8 & 0xFF, int & 0xFF)
    case 8: // ARGB (32-bit)
      (r, g, b) = (int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
    default:
      (r, g, b) = (0, 0, 0)
    }
    self.init(red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255)
  }
}
​
public struct XXChartTargetCircleModel {
  var outProgress: Double = 0.3
  var midProgress: Double = 0.2
  var inProgress: Double = 0.5
  var progress: Int = 81
  let outColor: Color = Color(hexString: "#1F8A70")
  let midColor: Color = Color(hexString: "#BFDB38")
  let inColor: Color = Color(hexString: "#FC7300")
  var target: String = ""
​
}
​
struct XXAimCircleView: View {
  @Environment(.colorScheme) var colorScheme: ColorScheme
  var title: String
  var data: XXChartTargetCircleModel
  public var formSize:CGSize
  @State var progress: Int = 0
  
  init(title: String, data: XXChartTargetCircleModel) {
    self.title = title
    self.data = data
    self.formSize = CGSize(width: 180, height: 180)
  }
  
  var body: some View {
    ZStack {
      Rectangle()
        .fill(Color(hexString: "#F5F5F5"))
        .cornerRadius(20)
      VStack() {
        ZStack {
          XXCircleProgressBar(progress: data.outProgress, color: data.outColor, lineWidth: 16)
            .frame(width: self.formSize.width - 30,
                height: self.formSize.width - 30)
          XXCircleProgressBar(progress: data.midProgress, color: data.midColor, lineWidth: 12)
            .frame(width: self.formSize.width - 66,
                height: self.formSize.width - 66)
          XXCircleProgressBar(progress: data.inProgress, color: data.inColor, lineWidth: 10)
            .frame(width: self.formSize.width - 94,
                height: self.formSize.width - 94)
          HStack(alignment: .lastTextBaseline) {
            Text("(progress)")
              .foregroundColor(.black)
              .font(.title3) + Text("%")
              .foregroundColor(.black)
              .font(.subheadline)
              
          }
          .onAppear {
            Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { timer in
              if progress < Int(data.progress) {
                progress += 1
              } else {
                timer.invalidate()
              }
            }
          }
        }
        .padding([.top])
        HStack {
          Text("(Image(systemName: "target"))")
          Spacer()
          Text("(title)挑战")
            .font(.bold(.body)())
            .foregroundColor(.black)
        }
        .padding([.bottom, .leading, .trailing])
      }
    }.frame(minWidth:self.formSize.width,
        maxWidth:self.formSize.width,
        minHeight:self.formSize.height,
        maxHeight:self.formSize.height)
  }
}
struct XXCircleProgressBar: View {
  var progress: Double
  var color: Color
  @State var scaleValue: Double = 0
  static let spacing: Double = 8
  var lineWidth: Double
  init(progress: Double, color: Color, lineWidth: Double) {
    self.progress = progress
    self.color = color
    self.lineWidth = lineWidth
  }
  var body: some View {
    GeometryReader { geometry in
      ZStack() {
        XXRingShape(progress: 1, size: geometry.size)
        .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
        .foregroundColor(color.opacity(0.2))
​
        XXRingShape(progress: scaleValue, size: geometry.size)
        .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
        .foregroundColor(color)
        .animation(.easeOut(duration: 1), value: scaleValue)
        .onAppear {
          scaleValue = progress
        }
        .onDisappear {
          scaleValue = 0
        }
        .frame(width: geometry.size.width, height: geometry.size.width)
      }
    }
  }
}
​
struct XXRingShape: Shape {
  var progress: Double = 0.0
  var size: CGSize
  var animatableData: Double {
    get { progress }
    set { progress = newValue }
  }
  func path(in rect: CGRect) -> Path {
    var path = Path()
    let center = CGPoint(x: size.width/2, y: size.height/2)
    let radius: CGFloat = (size.width - XXCircleProgressBar.spacing)/2
    path.addArc(center: center,
          radius: radius,
          startAngle: .degrees(-90),
          endAngle: .degrees(-90 + (360 * progress)),
          clockwise: false)
    return path
  }
}
struct XXAimCircleView_Previews: PreviewProvider {
  static var previews: some View {
    XXAimCircleView(title: "今天", data: XXChartTargetCircleModel())
  }
}
​

结束语

近期chatGPT的爆火,让一个有趣的问题又从头进入人们的视界——AI是否能够代替人类作业?

这个问题看看chatGPT是怎样答复的。

SwiftUI开发总结(三)

很巧的是,在我问它这个问题之前,我能想到的答案与它的答复大致相同。没错,随着科技的开展,咱们的生产东西的确获得一日千里的前进,因而,一些东西威胁论会发生也是非常天然的或许,人类关于自己无法控制的力量总是充满敬畏。

但我相信,斧子不会自己杀人,能杀死人的只要会用斧子的人。

所以,与其担心会不会斧子砍死,不如先学会运用斧子。

最后,感谢一切为了科学前进,为了社会前进,为了人类前进的伟大科学家。Respect!!!

封面是运用Midjourney制作。再次感谢那些一切推动科技开展的人!