Creating a Customizable Breathing Animation with SwiftUI

Pushing the Apple Watch Breath app to another level

Source Apple ©

Guided breathing exercises have been acknowledged for their potential benefits towards mental and physical health. They can help reduce stress, increase focus, and promote better sleep. In light of this, Apple has integrated a simple yet effective Breathe app in its watchOS, promoting mindfulness and wellness among its users.

In this article, we set out to engineer a similar experience to the Apple Watch Breathe app, but with a significant enhancement. Our goal is to construct a dynamic and customizable breathing animation utilizing SwiftUI — Apple’s powerful and innovative UI toolkit. This breathing animation not only presents a visually calming experience, but it also brings an added layer of adaptability. What sets our version apart is the capacity for developers and users to customize various parameters, such as the duration of breath in and out, the holding periods, and even the number of breath cycles. This advanced feature makes our interpretation of the Breathe app more flexible and suited to individual user needs, pushing the concept of a breathing app to a whole new level.

Our goal is to create an immersive, flexible component that can be integrated into a wide variety of health and wellness apps, enabling users to personalize their guided breathing exercises according to their comfort and requirements. Let’s dive in to understand how we can leverage SwiftUI’s capabilities to create this user-friendly, interactive feature.

Explaining the Code

We start by defining the state and properties of our BreatheView:

struct BreatheView: View {
@State private var animate = false
@State private var breathsTaken = 0
@State private var animationPhase: AnimationPhase = .breatheIn
@State private var rotationAngle: Double = 0

let breatheInDuration: Double
let breatheOutDuration: Double
let holdInDuration: Double
let holdOutDuration: Double
let numberOfBreaths: Int

The @State property wrapper is used for properties that will change over time and cause the view to re-render. animate controls whether the animation is in the “breathe in” or “breathe out” phase, breathsTaken keeps track of how many breath cycles have been completed, and animationPhase stores the current phase of the animation.

Subsequently, we established multiple constants to dictate the specifics of our animation. These constants are integral to the customizability of our feature, providing control over the duration of each phase of the animation — the breathing in, holding the breath in, breathing out, and holding the breath out — along with the total number of breath cycles to be completed. This design choice ensures that the animation can be tailored to meet various user preferences and needs, providing a personalized guided breathing experience for each individual user.

Moving forward, we will introduce the AnimationPhase enum, a key player in our code structure. This enum will encapsulate the four potential stages of the breathing cycle: ‘breathing in’, ‘holding in’, ‘breathing out’, and ‘holding out’. By distinctly representing these phases within our enum, we can easily manage the transitions between different stages of the breath cycle, ensuring a smooth, rhythmic flow that aligns with the natural process of breathing.

    enum AnimationPhase {
case breatheIn, holdIn, breatheOut, holdOut
}

As we proceed in our exploration of the BreatheView code, the initializer emerges as a significant asset. This particular function doesn’t just permit customization of our breathing animation—it actively facilitates it. It is designed to accept parameters that define the durations of each phase—breathing in, holding breath in, breathing out, and holding breath out—as well as the total count of breaths in a single cycle.

The strength of this design lies in its adaptability and user-centric approach. When instantiating a BreatheView, the parameters can be fine-tuned to cater to the unique needs or preferences of each user. Even in situations where no specific arguments are provided, the initializer will revert to a set of default values, ensuring the breathing animation remains operational and effective.

This level of personalization is what sets our BreatheView apart, even outpacing the original Breathe app from Apple in terms of adaptability. It is this feature that makes our BreatheView not just versatile, but also more attuned to individual user needs.

In this way, you can create custom breathing exercises, like Box Breathing, Wim Hof breathing, etc.

    init(
breatheInDuration: Double = 3,
breatheOutDuration: Double = 3,
holdInDuration: Double = 1,
holdOutDuration: Double = 1,
numberOfBreaths: Int = 0
) {
self.breatheInDuration = breatheInDuration
self.breatheOutDuration = breatheOutDuration
self.holdInDuration = holdInDuration
self.holdOutDuration = holdOutDuration
self.numberOfBreaths = numberOfBreaths
}

Moving forward in our implementation, we arrive at the body property of the BreatheView. This property represents the visual aspect of our component, and it’s here where the magic of our breathing animation comes to life.

At the heart of the body property, we find a ZStack. This SwiftUI element allows us to layer views on top of one another, creating a sense of depth and visual complexity. In our case, the ZStack houses eight distinct Circle views. These circles are the visual embodiment of the breaths in our animation, pulsating rhythmically in sync with the user’s guided breathing exercise.

The circles’ expansion and contraction are controlled by animation states, creating an engaging visual that mirrors the act of breathing. As the user breathes in, the circles expand, mimicking the filling of lungs with air. Conversely, as the user breathes out, the circles contract, reflecting the release of breath.

This dynamic visual representation of the breathing process is not just aesthetically pleasing, but it also provides a tangible point of focus for the user during the exercise. By visually encapsulating the rhythm of breathing, the animation aids in enhancing the user’s mindfulness and immersion in the exercise, further elevating the effectiveness of the guided breathing experience.

var body: some View {
// Define the gradient colors
let gradientStart = Color(red: 32/255, green: 217/255, blue: 210/255) // Light teal
let gradientEnd = Color(red: 12/255, green: 232/255, blue: 255/255) // Greenish-blue
let gradient = LinearGradient(gradient: Gradient(colors: [gradientStart, gradientEnd]), startPoint: .topLeading, endPoint: .bottomTrailing)

ZStack {
ForEach(0..<8) { i in
let angle = 2 * .pi / 8 * Double(i)
Circle()
.fill(gradient) // Apply the gradient here
.scaleEffect(animate ? 2 : 1)
.opacity(animate ? 0.2 : 1)
.frame(width: 80, height: 80)
.offset(x: animate ? CGFloat(cos(angle)) * 70 : 0,
y: animate ? CGFloat(sin(angle)) * 70 : 0)
}
}
.rotationEffect(.degrees(rotationAngle))
.onAppear(perform: runAnimation)
}

The runAnimation function is where the magic happens. It uses the animationPhase state variable to determine which phase of the animation to execute and uses withAnimation and DispatchQueue.main.asyncAfter to handle the transitions between phases.

    func runAnimation() {
guard numberOfBreaths == 0 || breathsTaken < numberOfBreaths else { return }
switch animationPhase {
case .breatheIn:
withAnimation(Animation.easeInOut(duration: breatheInDuration)) {
animate = true
// Rotate the ZStack by n degrees during the breathe in phase
rotationAngle += 100
}
DispatchQueue.main.asyncAfter(deadline: .now() + breatheInDuration) {
animationPhase = .holdIn
runAnimation()
}
case .holdIn:
DispatchQueue.main.asyncAfter(deadline: .now() + holdInDuration) {
animationPhase = .breatheOut
runAnimation()
}
case .breatheOut:
withAnimation(Animation.easeInOut(duration: breatheOutDuration)) {
animate = false
// Rotate the ZStack in the opposite direction during the breathe out phase
rotationAngle -= 100
}
DispatchQueue.main.asyncAfter(deadline: .now() + breatheOutDuration) {
animationPhase = .holdOut
runAnimation()
}
case .holdOut:
DispatchQueue.main.asyncAfter(deadline: .now() + holdOutDuration) {
breathsTaken += 1
animationPhase = .breatheIn
runAnimation()
}
}
}

Here’s how the runAnimation() function works:

  1. We begin by checking if the maximum number of breaths has been reached. If numberOfBreaths is not 0 and breathsTaken is not less than numberOfBreaths, we return from the function and stop the animation.
  2. Next, we use a switch statement to determine the current phase of the animation. Depending on the phase, we either animate the circles or wait for a specified duration before moving to the next phase.
  3. During the breatheIn and breatheOut phases, we use SwiftUI’s withAnimation function to animate the circles. The animate state variable controls the size and opacity of the circles, creating the illusion of breathing.
  4. During the holdIn and holdOut phases, we use DispatchQueue.main.asyncAfter to wait for a specified duration before moving to the next phase. This creates the pauses in the animation where the user is supposed to hold their breath.
  5. Finally, during the holdOut phase, we increment breathsTaken and reset the animationPhase to breatheIn, starting the next breath cycle.

The last part of the code is a PreviewProvider which provides a preview of the BreatheView in Xcode.

struct BreatheView_Previews: PreviewProvider {
static var previews: some View {
BreatheView(breatheInDuration: 3,
breatheOutDuration: 2,
holdInDuration: 1,
holdOutDuration: 0,
numberOfBreaths: 5)
}
}

In this case, the breatheInDuration is set to 3. This means that the user will spend three seconds in the inhalation phase of the breathing cycle. The circles in our animation will expand over the course of these three seconds, visually guiding the user through the breath intake process.

The breatheOutDuration parameter is set to 2, indicating that the exhalation phase will take two seconds. As such, the user will spend two seconds exhaling, during which time the circles in the animation will contract.

Following the inhalation phase, the holdInDuration parameter specifies a one-second pause. This pause allows the user to hold their breath for one second before transitioning to the exhalation phase.

Interestingly, the holdOutDuration is set to 0. This means that there will be no pause after the exhalation phase; the user will move directly into the next inhalation phase, maintaining a fluid rhythm in the breathing cycle.

The numberOfBreaths parameter is set to 5, indicating that this sequence of inhalation, hold, exhalation, and transition will repeat five times before the animation ends. This configuration caters to users who prefer a structured, guided breathing exercise with a clear beginning and end. It also allows for a longer inhalation and a brief pause, which could potentially encourage deeper, more mindful breathing.

Conclusion

In this walkthrough, we have demonstrated the power of SwiftUI in creating a rich, interactive user experience with a customizable and reusable breathing animation. The flexibility allowed by the defined parameters ensures that the animation can be adjusted to cater to different user preferences, reinforcing the personalized feel of the wellness application. It showcases the versatility of SwiftUI and how it enables developers to create complex animations seamlessly, enhancing the overall user interface of your applications.

Results

I hope this article has shed some light on how to create intricate animations, such as the Breathe App animation, using SwiftUI. Leveraging SwiftUI’s powerful and flexible animation capabilities, you can bring complex designs to life, creating engaging and interactive user interfaces. If you found this article beneficial, please consider giving it a clap and follow for more insightful content on SwiftUI and other iOS development topics.

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job


Creating a Customizable Breathing Animation with SwiftUI was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Emin Emini

Pushing the Apple Watch Breath app to another level

Source Apple ©

Guided breathing exercises have been acknowledged for their potential benefits towards mental and physical health. They can help reduce stress, increase focus, and promote better sleep. In light of this, Apple has integrated a simple yet effective Breathe app in its watchOS, promoting mindfulness and wellness among its users.

In this article, we set out to engineer a similar experience to the Apple Watch Breathe app, but with a significant enhancement. Our goal is to construct a dynamic and customizable breathing animation utilizing SwiftUI — Apple’s powerful and innovative UI toolkit. This breathing animation not only presents a visually calming experience, but it also brings an added layer of adaptability. What sets our version apart is the capacity for developers and users to customize various parameters, such as the duration of breath in and out, the holding periods, and even the number of breath cycles. This advanced feature makes our interpretation of the Breathe app more flexible and suited to individual user needs, pushing the concept of a breathing app to a whole new level.

Our goal is to create an immersive, flexible component that can be integrated into a wide variety of health and wellness apps, enabling users to personalize their guided breathing exercises according to their comfort and requirements. Let’s dive in to understand how we can leverage SwiftUI’s capabilities to create this user-friendly, interactive feature.

Explaining the Code

We start by defining the state and properties of our BreatheView:

struct BreatheView: View {
@State private var animate = false
@State private var breathsTaken = 0
@State private var animationPhase: AnimationPhase = .breatheIn
@State private var rotationAngle: Double = 0

let breatheInDuration: Double
let breatheOutDuration: Double
let holdInDuration: Double
let holdOutDuration: Double
let numberOfBreaths: Int

The @State property wrapper is used for properties that will change over time and cause the view to re-render. animate controls whether the animation is in the "breathe in" or "breathe out" phase, breathsTaken keeps track of how many breath cycles have been completed, and animationPhase stores the current phase of the animation.

Subsequently, we established multiple constants to dictate the specifics of our animation. These constants are integral to the customizability of our feature, providing control over the duration of each phase of the animation — the breathing in, holding the breath in, breathing out, and holding the breath out — along with the total number of breath cycles to be completed. This design choice ensures that the animation can be tailored to meet various user preferences and needs, providing a personalized guided breathing experience for each individual user.

Moving forward, we will introduce the AnimationPhase enum, a key player in our code structure. This enum will encapsulate the four potential stages of the breathing cycle: 'breathing in', 'holding in', 'breathing out', and 'holding out'. By distinctly representing these phases within our enum, we can easily manage the transitions between different stages of the breath cycle, ensuring a smooth, rhythmic flow that aligns with the natural process of breathing.

    enum AnimationPhase {
case breatheIn, holdIn, breatheOut, holdOut
}

As we proceed in our exploration of the BreatheView code, the initializer emerges as a significant asset. This particular function doesn't just permit customization of our breathing animation—it actively facilitates it. It is designed to accept parameters that define the durations of each phase—breathing in, holding breath in, breathing out, and holding breath out—as well as the total count of breaths in a single cycle.

The strength of this design lies in its adaptability and user-centric approach. When instantiating a BreatheView, the parameters can be fine-tuned to cater to the unique needs or preferences of each user. Even in situations where no specific arguments are provided, the initializer will revert to a set of default values, ensuring the breathing animation remains operational and effective.

This level of personalization is what sets our BreatheView apart, even outpacing the original Breathe app from Apple in terms of adaptability. It is this feature that makes our BreatheView not just versatile, but also more attuned to individual user needs.

In this way, you can create custom breathing exercises, like Box Breathing, Wim Hof breathing, etc.

    init(
breatheInDuration: Double = 3,
breatheOutDuration: Double = 3,
holdInDuration: Double = 1,
holdOutDuration: Double = 1,
numberOfBreaths: Int = 0
) {
self.breatheInDuration = breatheInDuration
self.breatheOutDuration = breatheOutDuration
self.holdInDuration = holdInDuration
self.holdOutDuration = holdOutDuration
self.numberOfBreaths = numberOfBreaths
}

Moving forward in our implementation, we arrive at the body property of the BreatheView. This property represents the visual aspect of our component, and it's here where the magic of our breathing animation comes to life.

At the heart of the body property, we find a ZStack. This SwiftUI element allows us to layer views on top of one another, creating a sense of depth and visual complexity. In our case, the ZStack houses eight distinct Circle views. These circles are the visual embodiment of the breaths in our animation, pulsating rhythmically in sync with the user's guided breathing exercise.

The circles’ expansion and contraction are controlled by animation states, creating an engaging visual that mirrors the act of breathing. As the user breathes in, the circles expand, mimicking the filling of lungs with air. Conversely, as the user breathes out, the circles contract, reflecting the release of breath.

This dynamic visual representation of the breathing process is not just aesthetically pleasing, but it also provides a tangible point of focus for the user during the exercise. By visually encapsulating the rhythm of breathing, the animation aids in enhancing the user’s mindfulness and immersion in the exercise, further elevating the effectiveness of the guided breathing experience.

var body: some View {
// Define the gradient colors
let gradientStart = Color(red: 32/255, green: 217/255, blue: 210/255) // Light teal
let gradientEnd = Color(red: 12/255, green: 232/255, blue: 255/255) // Greenish-blue
let gradient = LinearGradient(gradient: Gradient(colors: [gradientStart, gradientEnd]), startPoint: .topLeading, endPoint: .bottomTrailing)

ZStack {
ForEach(0..<8) { i in
let angle = 2 * .pi / 8 * Double(i)
Circle()
.fill(gradient) // Apply the gradient here
.scaleEffect(animate ? 2 : 1)
.opacity(animate ? 0.2 : 1)
.frame(width: 80, height: 80)
.offset(x: animate ? CGFloat(cos(angle)) * 70 : 0,
y: animate ? CGFloat(sin(angle)) * 70 : 0)
}
}
.rotationEffect(.degrees(rotationAngle))
.onAppear(perform: runAnimation)
}

The runAnimation function is where the magic happens. It uses the animationPhase state variable to determine which phase of the animation to execute and uses withAnimation and DispatchQueue.main.asyncAfter to handle the transitions between phases.

    func runAnimation() {
guard numberOfBreaths == 0 || breathsTaken < numberOfBreaths else { return }
switch animationPhase {
case .breatheIn:
withAnimation(Animation.easeInOut(duration: breatheInDuration)) {
animate = true
// Rotate the ZStack by n degrees during the breathe in phase
rotationAngle += 100
}
DispatchQueue.main.asyncAfter(deadline: .now() + breatheInDuration) {
animationPhase = .holdIn
runAnimation()
}
case .holdIn:
DispatchQueue.main.asyncAfter(deadline: .now() + holdInDuration) {
animationPhase = .breatheOut
runAnimation()
}
case .breatheOut:
withAnimation(Animation.easeInOut(duration: breatheOutDuration)) {
animate = false
// Rotate the ZStack in the opposite direction during the breathe out phase
rotationAngle -= 100
}
DispatchQueue.main.asyncAfter(deadline: .now() + breatheOutDuration) {
animationPhase = .holdOut
runAnimation()
}
case .holdOut:
DispatchQueue.main.asyncAfter(deadline: .now() + holdOutDuration) {
breathsTaken += 1
animationPhase = .breatheIn
runAnimation()
}
}
}

Here’s how the runAnimation() function works:

  1. We begin by checking if the maximum number of breaths has been reached. If numberOfBreaths is not 0 and breathsTaken is not less than numberOfBreaths, we return from the function and stop the animation.
  2. Next, we use a switch statement to determine the current phase of the animation. Depending on the phase, we either animate the circles or wait for a specified duration before moving to the next phase.
  3. During the breatheIn and breatheOut phases, we use SwiftUI's withAnimation function to animate the circles. The animate state variable controls the size and opacity of the circles, creating the illusion of breathing.
  4. During the holdIn and holdOut phases, we use DispatchQueue.main.asyncAfter to wait for a specified duration before moving to the next phase. This creates the pauses in the animation where the user is supposed to hold their breath.
  5. Finally, during the holdOut phase, we increment breathsTaken and reset the animationPhase to breatheIn, starting the next breath cycle.

The last part of the code is a PreviewProvider which provides a preview of the BreatheView in Xcode.

struct BreatheView_Previews: PreviewProvider {
static var previews: some View {
BreatheView(breatheInDuration: 3,
breatheOutDuration: 2,
holdInDuration: 1,
holdOutDuration: 0,
numberOfBreaths: 5)
}
}

In this case, the breatheInDuration is set to 3. This means that the user will spend three seconds in the inhalation phase of the breathing cycle. The circles in our animation will expand over the course of these three seconds, visually guiding the user through the breath intake process.

The breatheOutDuration parameter is set to 2, indicating that the exhalation phase will take two seconds. As such, the user will spend two seconds exhaling, during which time the circles in the animation will contract.

Following the inhalation phase, the holdInDuration parameter specifies a one-second pause. This pause allows the user to hold their breath for one second before transitioning to the exhalation phase.

Interestingly, the holdOutDuration is set to 0. This means that there will be no pause after the exhalation phase; the user will move directly into the next inhalation phase, maintaining a fluid rhythm in the breathing cycle.

The numberOfBreaths parameter is set to 5, indicating that this sequence of inhalation, hold, exhalation, and transition will repeat five times before the animation ends. This configuration caters to users who prefer a structured, guided breathing exercise with a clear beginning and end. It also allows for a longer inhalation and a brief pause, which could potentially encourage deeper, more mindful breathing.

Conclusion

In this walkthrough, we have demonstrated the power of SwiftUI in creating a rich, interactive user experience with a customizable and reusable breathing animation. The flexibility allowed by the defined parameters ensures that the animation can be adjusted to cater to different user preferences, reinforcing the personalized feel of the wellness application. It showcases the versatility of SwiftUI and how it enables developers to create complex animations seamlessly, enhancing the overall user interface of your applications.

Results

I hope this article has shed some light on how to create intricate animations, such as the Breathe App animation, using SwiftUI. Leveraging SwiftUI’s powerful and flexible animation capabilities, you can bring complex designs to life, creating engaging and interactive user interfaces. If you found this article beneficial, please consider giving it a clap and follow for more insightful content on SwiftUI and other iOS development topics.

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job


Creating a Customizable Breathing Animation with SwiftUI was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Emin Emini


Print Share Comment Cite Upload Translate Updates
APA

Emin Emini | Sciencx (2023-06-01T13:38:40+00:00) Creating a Customizable Breathing Animation with SwiftUI. Retrieved from https://www.scien.cx/2023/06/01/creating-a-customizable-breathing-animation-with-swiftui/

MLA
" » Creating a Customizable Breathing Animation with SwiftUI." Emin Emini | Sciencx - Thursday June 1, 2023, https://www.scien.cx/2023/06/01/creating-a-customizable-breathing-animation-with-swiftui/
HARVARD
Emin Emini | Sciencx Thursday June 1, 2023 » Creating a Customizable Breathing Animation with SwiftUI., viewed ,<https://www.scien.cx/2023/06/01/creating-a-customizable-breathing-animation-with-swiftui/>
VANCOUVER
Emin Emini | Sciencx - » Creating a Customizable Breathing Animation with SwiftUI. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/06/01/creating-a-customizable-breathing-animation-with-swiftui/
CHICAGO
" » Creating a Customizable Breathing Animation with SwiftUI." Emin Emini | Sciencx - Accessed . https://www.scien.cx/2023/06/01/creating-a-customizable-breathing-animation-with-swiftui/
IEEE
" » Creating a Customizable Breathing Animation with SwiftUI." Emin Emini | Sciencx [Online]. Available: https://www.scien.cx/2023/06/01/creating-a-customizable-breathing-animation-with-swiftui/. [Accessed: ]
rf:citation
» Creating a Customizable Breathing Animation with SwiftUI | Emin Emini | Sciencx | https://www.scien.cx/2023/06/01/creating-a-customizable-breathing-animation-with-swiftui/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.