Hello SwiftUI learners,
Welcome back to a new post on Learning SwiftUI. This week I had an interesting meeting with the Harmony Apps team. We have released our first company app, called Harmony Journal. Which, as the name indicates, is a journal app for you to write about your daily adventures. You can download it here: Download Harmony Journal.
This week I have played around with some progress bars, both for when you load a new view and when you track something. This week we will focus on task tracking and how you can update the view once the task is done. So let’s start simple by creating a trimmed circle.
struct TaskTrack: View {
@State private var statusText: CGFloat = 0.0
var body: some View {
VStack {
ZStack {
Circle()
.trim(from: 0, to: statusText / 100.0)
.stroke(style: StrokeStyle(lineWidth: 15))
.padding()
.animation(.easeInOut, value: statusText)
}
}
}
}
Right now, you will still have an empty view as the trim is set to 0. So the circle won’t show. Let’s add a slider so we can update the statusText value.
struct TaskTrack: View {
@State private var statusText: CGFloat = 0.0
var body: some View {
VStack {
ZStack {
Circle()
.trim(from: 0, to: statusText / 100.0)
.stroke(style: StrokeStyle(lineWidth: 15))
.padding()
.animation(.easeInOut, value: statusText)
}
Slider(value: $statusText, in: 0...100)
.padding()
}
}
}
Now we added a slider that is bound to our statusText property wrapper.
Next what we want to do is to track our statusText value and as soon it hits 100, we want a text view to appear that says “task completed”. For this we can use the onChange modifier to track for changes on our statusText.
struct TaskTrack: View {
@State private var statusText: CGFloat = 0.0
@State private var presentText: Bool = false
var body: some View {
VStack {
ZStack {
Circle()
.trim(from: 0, to: statusText / 100.0)
.stroke(style: StrokeStyle(lineWidth: 15))
.padding()
.animation(.easeInOut, value: statusText)
VStack {
Text("\(Int(statusText)) %")
.font(.title)
Text("Task Completed")
.font(.largeTitle)
.opacity(presentText ? 1 : 0)
}
}
Slider(value: $statusText, in: 0...100)
.padding()
.onChange(of: statusText, perform: { newValue in
withAnimation(.easeInOut(duration: 1.5)) {
if newValue >= 100 {
presentText = true
} else {
presentText = false
}
}
})
}
}
}
Pretty neat right? All we had to do was to add a new boolean property wrapper called presentText and attach it to the text view. And then use the onChange modifier to track for a certain value in statusText. We also added a nice little animation so the text appears smoothly. But to be honest, the animation only works for presenting the text. Doesn’t look that good to remove it.
To be fair, this is a neat way to present the text. This is what I have been told to use, but I actually managed to get the same result with way less code. Just by using the animation modifier, check it out below.
struct TaskTrack: View {
@State private var statusText: CGFloat = 0.0
@State private var presentText: Bool = false
var body: some View {
VStack {
ZStack {
Circle()
.trim(from: 0, to: statusText / 100.0)
.stroke(style: StrokeStyle(lineWidth: 15))
.padding()
.animation(.easeInOut, value: statusText)
VStack {
Text("\(Int(statusText)) %")
.font(.title)
Text("Task Completed")
.font(.largeTitle)
.opacity(statusText >= 100 ? 1 : 0)
.animation(.easeInOut(duration: 1.5), value: statusText)
}
}
Slider(value: $statusText, in: 0...100)
.padding()
}
}
}
This way we reduced the code quite much, but I am unsure which way is the best way to go long term. Anyone reading this newsletter might know and can point out the pro’s and con’s by using either of the solutions?
Would really appreciate it :)
That’s was it for this week, I hope you learned something. I know I did.
Have a great day!
/Mr SwiftUI