When your app is executing a task which will take a little longer, of course you do not execute this task on the main thread.
Nevertheless there are situations, especially during first time launches you need to show some kind of progress before you can render the view, for example you are waiting for an API to have loaded some initial data.
Let’s do it
In this post I’m gonna show you the code for a custom build AnimatedProgressView in SwiftUI which accepts two parameters.
The first parameter is the numberOfDots and the second is the color.
How does it work?
In the body DotView objects are shown in a HStack for the numberOfDots that you have set.
The DotView draws a Circle with the color you have set and performing a scaleEffect which is passed.
The scales are defined in the initializer.
The full code is show here:
import SwiftUI
struct AnimatedProgressView: View {
@State private var scales: [CGFloat]
private let data: [AnimationData]
private var animation = Animation.easeInOut.speed(0.5)
private var color: Color
init(numberOfDots: Int = 3, color: Color = .green) {
self.color = color
_scales = State(initialValue: Array(repeating: 0, count: numberOfDots))
data = Array(repeating: AnimationData(delay: 0.2), count: numberOfDots).enumerated().map { (index, data) in
var modifiedData = data
modifiedData.delay *= Double(index)
return modifiedData
}
}
func animateDots() {
for (index, data) in data.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + data.delay) {
animateDot(binding: $scales[index], animationData: data)
}
}
// Repeat
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
animateDots()
}
}
func animateDot(binding: Binding<CGFloat>, animationData: AnimationData) {
withAnimation(animation) {
binding.wrappedValue = 1
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
withAnimation(animation) {
binding.wrappedValue = 0.2
}
}
}
var body: some View {
VStack {
HStack {
ForEach(0..<scales.count, id: \.self) { index in
DotView(scale: $scales[index], color: color)
}
}
}
.onAppear {
animateDots()
}
}
}
struct AnimationData {
var delay: TimeInterval
}
private struct DotView: View {
@Binding var scale: CGFloat
var color: Color
var body: some View {
Circle()
.fill(color.opacity(scale >= 0.7 ? 1 : 0.7))
.scaleEffect(scale)
.frame(width: 50, height: 50, alignment: .center)
}
}
How to use it?
It could not have been simpeler as this.
struct ContentView: View {
var body: some View {
VStack {
AnimatedProgressView(numberOfDots: 3, color:.blue)
}
.padding()
}
}
You simply define the view with the parameters in the View in which you want to display it and that’s it.
How to include?
You just copy and paste. I don’t like simple solutions like this in Swift packages since you often want to tweak them a little to your personal needs which is way easier to do when you have the source code in your Xcode Project.
Comments are closed