Implementing Swing Animation in SwiftUI
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 providedtext
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 theoffset
state variable..animation(.easeInOut(duration: 2), value: offset)
: This sets up an ease-in-out animation that lasts 2 seconds, and it triggers whenever theoffset
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 theanimateText
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 callinganimateText
recursively 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 aSwingTextView
with a sample text and a width of 200 points.ContentView_Previews
: A preview provider to renderContentView
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!