I have been watching videos related to SwiftUI in WWDC20 these days. When WATCHING the video Build a SwiftUI View in Swift Playgrounds, I found the ProgressView quite interesting, so I decided to make a wave of imitation of it.

The final effect is shown below:

In Dark mode, it looks like this:

All in all, the effects are ok. I find SwiftUI so interesting because it allows us to instantly realize our ideas. It’s so easy to implement effects or pages in SwiftUI. Of course, the prerequisite is to have a deep understanding of SwiftUI, which is not like the layman think, just drag and drop a few controls into an APP.

The code is pretty simple, but I want to show you how to do this, right?

Task decomposition

We’ll just ignore the slider and button at the bottom, which is not the focus of this article. At the highest level, we can split the MyProgressView into three parts:

  • Background Progress View
  • Current Progress View
  • Progress text View

Focus on

After the task split, we found that the most difficult task is how to cut a part of a circle. To start with an easy task, let’s draw a circle:

struct ContentView: View {
    var body: some View {
        Circle()
            .stroke(Color.orange, lineWidth: 20)
            .padding(50)}}Copy the code

It’s too easy to draw a circle, so what we’re going to do is we’re going to make a notch, and we’re going to call it a sliceSize, and this sliceSize is configurable, which means we can set it any way we want. Fortunately, the system provides a function:

@inlinable public func trim(from startFraction: CGFloat = 0, to endFraction: CGFloat = 1) -> some Shape
Copy the code

This method can be used on all objects that implement Shape in SwiftUI, and it’s easy to use. Just look at the following example:

Note that the order of capture is clockwise, starting from the right side of the horizontal direction. So here’s the problem. Suppose we want sliceSize equal to 0.3, which is:

.trim(from: 0, to: 1 - CGFloat(self.sliceSize))
Copy the code

After running the program, we need our first challenge:

From the figure above, we found that we need to rotate the graph, so we naturally need to calculate the rotation Angle. In order to make it easier for you to understand the content, I have drawn diagrams. In real development, these diagrams are actually a mental calculation process.

If you look at the figure above, it’s actually the orange to green rotation, the rotation of the vector FC to FH, can you see that? The code is as follows:

var rotateAngle: Angle {
    .degrees(90 + sliceSize * 360 * 0.5)}Copy the code

The gradient

Now that we understand the rotation above, we are left with the last step, setting the gradient color for the progress bar. You can go to this article I wrote before to learn related knowledge SwiftUI Gradient detailed explanation.

We chose AngularGradient, or Angle gradient, here:

var strokeGradient: AngularGradient {
    AngularGradient(gradient: gradient, center: /*@START_MENU_TOKEN@*/ .center/*@END_MENU_TOKEN@*/, angle: .degrees(-10))}Copy the code

The core code

public struct MyProgressView: View {
    let gradient = Gradient(colors: [.green, .blue])
    let sliceSize = 0.45
    let progress: Double

    private let percentageFormatter: NumberFormatter = {
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .percent
        return numberFormatter
    }()

    var strokeGradient: AngularGradient {
        AngularGradient(gradient: gradient, center: /*@START_MENU_TOKEN@*/ .center/*@END_MENU_TOKEN@*/, angle: .degrees(-10))}var rotateAngle: Angle {
        .degrees(90 + sliceSize * 360 * 0.5)}init(_ progress: Double = 0.3) {
        self.progress = progress
    }

    private func strokeStyle(_ proxy: GeometryProxy) -> StrokeStyle {
        StrokeStyle(lineWidth: 0.1 * min(proxy.size.width, proxy.size.height),
                    lineCap: .round)
    }

    public var body: some View {
        GeometryReader { proxy in
            ZStack {
                Group {
                    Circle()
                        .trim(from: 0, to: 1 - CGFloat(self.sliceSize))
                        .stroke(self.strokeGradient,
                                style: self.strokeStyle(proxy))
                        .padding(.all, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)

                    Circle()
                        .trim(from: 0, to: CGFloat(self.progress * (1 - self.sliceSize)))
                        .stroke(Color.purple,
                                style: self.strokeStyle(proxy))
                        .padding(.all, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
                }
                .rotationEffect(self.rotateAngle, anchor: /*@START_MENU_TOKEN@*/ .center/*@END_MENU_TOKEN@*/)
                .offset(x: 0, y: 0.1 * min(proxy.size.width, proxy.size.height))

                Text("\ [self.percentageFormatter.string(from: NSNumber(value: self.progress))!)")
                    .font(.largeTitle)
                    .bold()
            }
        }
    }
}
Copy the code

conclusion

Use of the images are used SwiftUI above, have to say that SwiftUI write code is too great, can download the code here https://gist.github.com/agelessman/789ae1b475ac02ca801fb09bd5b19b98, If you have any questions, please leave a message.

Ask for attention, ask for likes, the follow-up content will be more exciting.