as first step we make a struct view in this case we'll call it LoaderView
struct LoaderView: View {}
Next, we will declare a boolean variable that will start the animation and make the colors array to animation.
struct LoaderView: View {
let isAnimating: Bool
private let color: [Color] = [.red, .orange, .yellow, .green, .blue]
}
After declaring our variables, we will create a state variable to rotate our component.
struct LoaderView: View {
let isAnimating: Bool
private let color: [Color] = [.red, .orange, .yellow, .green, .blue]
@State private var rotations: [Double] = Array(repeating: 0, count: 5)
}
Use the body property required by the View protocol; within that property, add a horizontal stack, and inside the stack, use a ForEach to iterate over your design.
struct LoaderView: View {
let isAnimating: Bool
private let color: [Color] = [.red, .orange, .yellow, .green, .blue]
@State private var rotations: [Double] = Array(repeating: 0, count: 5)
var body: some View {
HStack {
ForEach(0..<5, id: \.self) { i in
}}}
}
Inside the forEach, we will use a Circle() and add a rotationEffect(.degrees()) to it; for the degrees, we will pass the rotations state corresponding to the circle's position.
struct LoaderView: View {
let isAnimating: Bool
private let colors: [Color] = [.red, .orange, .yellow, .green, .blue]
@State private var rotations: [Double] = Array(repeating: 0, count: 5)
var body: some View {
HStack {
ForEach(0..<5, id: \.self) { i in
Spacer()
Circle()
.strokeBorder(
style: StrokeStyle(lineWidth: 4, lineCap: .round, dash: [2,4,6]),
antialiased: true
)
.foregroundColor(colors[i])
.frame(width: 50, height: 50)
.rotationEffect(.degrees(rotations[i]))
}
}
In the end, we will have the design shown in the following image:
To animate the rotation of each circle, we will use a task within the HStack that executes whenever the isAnimating variable changes; as long as the task is not cancelled, it will perform the animation over a period of 0.6 seconds, with a change occurring every 0.25 seconds.
In the end, our code will look like this:
struct LoaderView: View {
let isAnimating: Bool
private let colors: [Color] = [.red, .orange, .yellow, .green, .blue]
@State private var rotations: [Double] = Array(repeating: 0, count: 5)
var body: some View {
HStack {
ForEach(0..<5, id: \.self) { i in
Spacer()
Circle()
.strokeBorder(
style: StrokeStyle(lineWidth: 4, lineCap: .round, dash: [2,4,6]),
antialiased: true
)
.foregroundColor(colors[i])
.frame(width: 50, height: 50)
.rotationEffect(.degrees(rotations[i]))
}
}
.task(id: isAnimating) {
guard isAnimating else { return }
while !Task.isCancelled {
for i in 0..<colors.count {
withAnimation(.linear(duration: 0.6)) {
rotations[i] += 360
}
guard (try? await Task.sleep(nanoseconds: 250_000_000)) != nil else { return }
}
}
}
}
}
Now, whenever you want to use your loader, you simply pass true or false; this gives you control over it whenever you call a service or just want to load the screen.
LoaderView(isAnimating: false)
LoaderView(isAnimating: true)

Top comments (0)