Implementing Swing Animation in SwiftUI

Rohit Saini
5 min readJun 14, 2024

Swing animation can be a compelling way to catch the user’s eye and make your text dynamic. In this article, we will explore how to create a Swing Animated Text View in SwiftUI, where the text moves back and forth horizontally within a confined space.
Instead of Text View you can animate any SwiftUI View like images, colors etc

SwiftUI Code Breakdown

Below is the complete implementation of the SwingTextView and a ContentView to demonstrate its usage. We will go through each part of the code in detail.

1. Enum for Direction

We define an enum Direction to handle the direction of the text movement.

enum Direction {
case forward
case backward
}

Explanation:

  • This enum helps us track whether the text should move forward (to the right) or backward (to the left). We have two possible values: .forward and .backward.

2. SwingTextView Struct

The main structure of our SwingTextView includes the properties text, width, and padding. We also use state variables for managing the offset and direction.

struct SwingTextView: View {
var text: String
var width: CGFloat
var padding: CGFloat = 16
@State private var offset: CGFloat = 0
@State private var direction: Direction = .forward

Explanation:

  • text: The text to be displayed and animated.
  • width: The width of the visible frame where the text will swing.
  • padding: Padding around the text inside its container.
  • offset: A state variable to control the horizontal position of the text.
  • direction: A state variable to keep track of the current direction of text movement.

3. Body of the View

Inside the body, we use a ScrollView to contain the text, which will move horizontally. The text is styled with a specific font and color, and its width is calculated dynamically based on its content.

var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
Text(text)
.font(.system(size: 16, weight: .bold))
.foregroundColor(.white)
.frame(minWidth: getTextWidth(text: text, font: .boldSystemFont(ofSize: 16)) + padding, alignment: .center)
.offset(x: offset)
.animation(.easeInOut(duration: 2), value: offset)
}
.padding(.vertical, padding)
.padding(.horizontal, padding / 2)
.background(Color.black)
.cornerRadius(12)
.frame(width: width)
.disabled(true)
.onAppear {
animateText()
}
}

Explanation:

  • ScrollView: A horizontal scroll view to hold the text. We disable scroll indicators.
  • Text: The text view displaying the provided text string. It is styled with a bold font and white color.
  • .frame(minWidth:): The width of the frame is set dynamically based on the text width and padding. This ensures the text fits within the frame and allows for smooth animation.
  • .offset(x:): This controls the horizontal position of the text based on the offset state variable.
  • .animation(.easeInOut(duration: 2), value: offset): This sets up an ease-in-out animation that lasts 2 seconds, and it triggers whenever the offset value changes.
  • .padding: Adds vertical and horizontal padding around the text.
  • .background(Color.black): Sets the background color of the text container to black.
  • .cornerRadius(12): Rounds the corners of the text container.
  • .frame(width: width): Sets the fixed width for the visible frame of the SwingTextView.
  • .disabled(true): Disables user interaction with the scroll view.
  • .onAppear: Calls the animateText function when the view appears.

4. Animating the Text

The animateText function handles the animation logic. It calculates the necessary offset based on the text width and updates the direction after a delay using DispatchQueue.

private func animateText() {
let textWidth = getTextWidth(text: text, font: .boldSystemFont(ofSize: 16)) + padding
let margin = textWidth + padding - width
if textWidth <= width { return }
withAnimation {
switch direction {
case .forward:
offset = 0
case .backward:
offset = -margin
}
}

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
if offset == 0 {
direction = .backward
} else if offset == -margin {
direction = .forward
}
self.animateText()
}
}

Explanation:

  • textWidth: Calculates the total width of the text plus padding.
  • margin: Determines the distance the text needs to travel by subtracting the visible frame width from the text width.
  • if textWidth <= width { return }: If the text fits within the visible frame, no animation is needed.
  • withAnimation: Animates the change in offset based on the current direction.
  • offset = 0: Moves the text to the initial position if the direction is forward.
  • offset = -margin: Moves the text to the end position if the direction is backward.
  • DispatchQueue.main.asyncAfter: Sets a 2-second delay before reversing the direction and calling animateTextrecursively to continue the animation.

5. Calculating Text Width

The getTextWidth function is a helper to calculate the width of the text using a UILabel.

private func getTextWidth(text: String, font: UIFont) -> CGFloat {
let label = UILabel()
label.font = font
label.text = text
label.sizeToFit()
return label.bounds.width
}

Explanation:

  • UILabel(): Creates a UILabel to measure the text.
  • label.font: Sets the font of the label.
  • label.text: Sets the text of the label.
  • label.sizeToFit(): Sizes the label to fit the text content.
  • label.bounds.width: Returns the width of the label, which corresponds to the width of the text.

6. ContentView for Demonstration

Finally, we create a ContentView to demonstrate the SwingTextView in action.

struct ContentView: View {
var body: some View {
SwingTextView(text: "This dummy text navigating from left to right and vice versa", width: 200)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Explanation:

  • SwingTextView: Initializes a SwingTextView with a sample text and a width of 200 points.
  • ContentView_Previews: A preview provider to render ContentView in Xcode's canvas.
//
// ContentView.swift
// BuildUI
//
// Created by Rohit Saini on 11/05/23.
//

import SwiftUI

enum Direction {
case forward
case backward
}

struct SwingTextView: View {
var text: String
var width: CGFloat
var padding: CGFloat = 16
@State private var offset: CGFloat = 0
@State private var direction: Direction = .forward

var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
Text(text)
.font(.system(size: 16, weight: .bold))
.foregroundColor(.white)
.frame(minWidth: getTextWidth(text: text, font: .boldSystemFont(ofSize: 16)) + padding, alignment: .center)
.offset(x: offset)
.animation(.easeInOut(duration: 2), value: offset)
}
.padding(.vertical, padding)
.padding(.horizontal, padding / 2)
.background(Color.black)
.cornerRadius(12)
.frame(width: width)
.disabled(true)
.onAppear {
animateText()
}
}

private func animateText() {
let textWidth = getTextWidth(text: text, font: .boldSystemFont(ofSize: 16)) + padding
let margin = textWidth + padding - width
if textWidth <= width { return }
withAnimation {
switch direction {
case .forward:
offset = 0
case .backward:
offset = -margin
}
}

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
if offset == 0 {
direction = .backward
} else if offset == -margin {
direction = .forward
}
self.animateText()
}
}

private func getTextWidth(text: String, font: UIFont) -> CGFloat {
let label = UILabel()
label.font = font
label.text = text
label.sizeToFit()
return label.bounds.width
}
}

struct ContentView: View {
var body: some View {
SwingTextView(text: "This dummy text navigating from left to right and vice versa", width: 100)
SwingTextView(text: "This dummy text navigating from left to right and vice versa", width: 200)
SwingTextView(text: "This dummy text navigating from left to right and vice versa", width: 300)
SwingTextView(text: "This dummy text navigating from left to right and vice versa", width: 350)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Conclusion

By following the above steps, you can create an engaging Swing Animation in SwiftUI. This component can be customized further to fit the specific needs of your application, whether it’s adjusting the text, changing colors, or tweaking the animation timing.

Feel free to integrate this into your projects and modify it as needed. Happy coding!

--

--

Rohit Saini

कर्म मुझे बांधता नहीं, क्योंकि मुझे कर्म के प्रतिफल की कोई इच्छा नहीं |