Amos Gyamfi
Amos GyamfiSeptember 14, 2022

How to make an Audio Visualizer Animation in SwiftUI

Learn how to create a simple looping Audio Visualizer Animation in SwiftUI, useful for audio content in music applications.
How to make an Audio Visualizer Animation in SwiftUI

In SwiftUI, you can animate size properties such as height and width. In this example, you will use animation to change the height of rectangles over time to create a faux audio Visualizer. If you missed our last article, you can find it here.

A brief background

Visualizing audio is not a new concept in software development. It's a great way of indicating music playback or recording levels. It's also a great way of indicating the volume of a microphone or other audio input device.

Recently, Apple added an audio visualizer to the iOS 16 lock screen's playback Live Activity that reacts to the actual audio being played through the device's speakers.

A close up of iOS's lock screen audio visualizer, over a playback Live Activity

In this example, you will create a looping animation that mimics the audio visualizer animation you see in many audio applications with a predefined animation that will be repeated over and over again to create a similar visual effect.

A sneak peek at the audio visualizer we're building, with 6 moving purple bars displayed horizontally

This is what our end result will look like. Let's get started!

Creating the animation

To start, create a blank SwiftUI project or a view file in Xcode and name it AudioVisualizer.swift.

  1. Start by defining the state variable
@State private var drawingHeight = true

This will be used to toggle the height of the rectangles over time. 2. Create a view for the vertical bars. Let's make this a function that returns the view for the bar based on a minimum and maximum height.

func bar(low: CGFloat = 0.0, high: CGFloat = 1.0) -> some View {
    RoundedRectangle(cornerRadius: 3)
        .fill(.indigo.gradient)
        .frame(height: (drawingHeight ? high : low) * 64)
        .frame(height: 64, alignment: .bottom)
}

The frame modifiers here are doing two things:

  • First, set the actual height of the bar based on the state variable and amplify that value by 64, which is the total height.
  • Then, set the full container's height to 64 and align it to the bottom. This will make the bars grow from the bottom up.

  1. Create a view for the audio visualizer. This will be a horizontal stack with 5 bars.
HStack {
    bar(low: 0.4)
    bar(low: 0.3)
    bar(low: 0.5)
    bar(low: 0.3)
    bar(low: 0.5)
}.frame(width: 80)

Make sure to set the width on the HStack so that the bars split evenly across the container. It's a good idea to use varied low and high fractions so that the animation appears more dynamic. Tune them – no pun intended – to your liking.

  1. Create an animation configuration property. Define this in your view struct, since it'll be used in each one of the bars.
var animation: Animation {
    return .linear(duration: 0.5).repeatForever()
}
  1. Attach the animation to the bars. Here, you can mess around and have fun with the properties of the Animation struct. You can use the same easing function for each one of them – like we're doing here – while changing speed, or mix and combine different easing functions.
HStack {
    bar(low: 0.4)
        .animation(animation.speed(1.5), value: drawingHeight)
    bar(low: 0.3)
        .animation(animation.speed(1.2), value: drawingHeight)
    bar(low: 0.5)
        .animation(animation.speed(1.0), value: drawingHeight)
    bar(low: 0.3)
        .animation(animation.speed(1.7), value: drawingHeight)
    bar(low: 0.5)
        .animation(animation.speed(1.0), value: drawingHeight)
}.frame(width: 80)

Changing the speed of the individual animations help improve the faux-audio feeling by ensuring that the easing functions are offset from one another and feel more organic.

The timeline below shows how different animation speeds stack to create unique patterns over time, with different speeds.

Animation timeline

  1. Toggle the animation on appear. Add the following modifier to the HStack.
.onAppear{
    drawingHeight.toggle()
}
  1. Extra: wrap it all in a VStack and add a label with leading alignment.
VStack(alignment: .leading) {
    Text("Audio")
        .bold()
 
    // Add content here
}

Let's put it all together:

ContentView.swift
struct ContentView: View {
 
    @State private var drawingHeight = true
 
    var animation: Animation {
        return .linear(duration: 0.5).repeatForever()
    }
 
    var body: some View {
        VStack(alignment: .leading) {
            Text("Audio")
                .bold()
            HStack {
                bar(low: 0.4)
                    .animation(animation.speed(1.5), value: drawingHeight)
                bar(low: 0.3)
                    .animation(animation.speed(1.2), value: drawingHeight)
                bar(low: 0.5)
                    .animation(animation.speed(1.0), value: drawingHeight)
                bar(low: 0.3)
                    .animation(animation.speed(1.7), value: drawingHeight)
                bar(low: 0.5)
                    .animation(animation.speed(1.0), value: drawingHeight)
            }
            .frame(width: 80)
            .onAppear{
                drawingHeight.toggle()
            }
        }
    }
 
    func bar(low: CGFloat = 0.0, high: CGFloat = 1.0) -> some View {
        RoundedRectangle(cornerRadius: 3)
            .fill(.indigo.gradient)
            .frame(height: (drawingHeight ? high : low) * 64)
            .frame(height: 64, alignment: .bottom)
    }
}

And that was it! Quite easy, I would say.

Make sure to check out the other parts of the SwiftUI Animation series: