
Here’s a SwiftUI implementation of a music player UI with album art, track information, and playback controls arranged using VStack
and HStack
, along with explanations:
First, you need to create a new project in SwiftUI. I’ve already created a project, so now I’ll guide you on how to implement the following features: building a music player app that plays and stops music when the user clicks on the play/pause button, and includes backward and forward buttons to navigate through tracks. You can implement these features using the same approach.

Let’s break down the code with an explanation of each component’s purpose:
import SwiftUI
import AVFoundation
SwiftUI: Framework for building user interfaces.AVFoundation: Framework for working with audio and video.
struct MusicPlayerView: View {
@State private var isPlaying = false
@State private var currentTime: Double = 0.0
@State private var audioPlayer: AVAudioPlayer?
let albumArt: Image // Image for the album art
let trackName: String
let artistName: String
let audioURL: URL
MusicPlayerView: The main view for the music player.@State: Used to create state variables that SwiftUI can monitor and update the UI accordingly.
isPlaying
: Tracks whether the audio is playing.currentTime
: Tracks the current playback time of the audio.audioPlayer
: The audio player instance.
let: Constant properties for album art, track name, artist name, and audio URL.
var body: some View {
VStack(alignment: .center, spacing: 20) {
body: The main view’s content.VStack: A vertical stack for arranging elements vertically.
alignment: .center
: Centers the items horizontally.spacing: 20
: Adds 20 points of spacing between items.
Image(systemName: "music.note")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250, height: 250)
.background(Color.gray.opacity(0.1))
.cornerRadius(20)
.shadow(radius: 10)
Image(systemName: “music.note”): Displays a music note icon.resizable(): Makes the image resizable.aspectRatio(contentMode: .fit): Maintains the aspect ratio while fitting the image within the frame.frame(width: 250, height: 250): Sets the image frame to 250×250 points.background(Color.gray.opacity(0.1)): Adds a light gray background.cornerRadius(20): Rounds the corners of the image.shadow(radius: 10): Adds a shadow around the image.
VStack(alignment: .center, spacing: 5) {
Text(trackName)
.font(.headline)
Text(artistName)
.font(.subheadline)
.foregroundColor(.secondary)
}
VStack: A vertical stack for arranging track information.
alignment: .center
: Centers the items horizontally.spacing: 5
: Adds 5 points of spacing between items.
Text(trackName): Displays the track name with a headline font.Text(artistName): Displays the artist name with a subheadline font and secondary color.
Slider(value: $currentTime, in: 0...(audioPlayer?.duration ?? 0.0))
.padding(.horizontal)
Slider: A slider for controlling playback time.
value: $currentTime
: Binds the slider’s value tocurrentTime
.in: 0...(audioPlayer?.duration ?? 0.0)
: Sets the range from 0 to the audio duration, defaulting to 0.0 ifaudioPlayer
isnil
.
padding(.horizontal): Adds horizontal padding around the slider.
HStack(spacing: 30) {
Button(action: {
// Previous track logic
}) {
Image(systemName: "backward.fill")
.font(.largeTitle)
}
Button(action: {
isPlaying.toggle()
if isPlaying {
audioPlayer?.play()
} else {
audioPlayer?.pause()
}
}) {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.font(.largeTitle)
}
Button(action: {
// Next track logic
}) {
Image(systemName: "forward.fill")
.font(.largeTitle)
}
}
HStack: A horizontal stack for arranging playback controls.
spacing: 30
: Adds 30 points of spacing between items.
Button: Creates buttons for previous, play/pause, and next actions.
action
: Defines the button’s action.- Image: Displays the button’s icon.
- font(.largeTitle): Sets the icon size to large title.
.padding()
.onAppear {
prepareAudioPlayer() // Load the audio when the view appears
startPlaybackTimer() // Start the timer to update the slider
}
}
padding(): Adds padding around the entire view.onAppear: Calls functions to prepare the audio player and start the playback timer when the view appears.
func prepareAudioPlayer() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
audioPlayer?.prepareToPlay()
} catch {
print("Error loading audio: \(error)")
}
}
func startPlaybackTimer() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
if let player = audioPlayer, player.isPlaying {
currentTime = player.currentTime
}
}
}
prepareAudioPlayer(): Initializes the AVAudioPlayer
with the provided audioURL
and prepares it for playback.
- do-catch block: Handles potential errors when loading the audio.
startPlaybackTimer(): Starts a timer that updates currentTime
every second if the audio is playing.
#Preview {
MusicPlayerView(
albumArt: Image("album_art"), // Replace with your image
trackName: "Sample Track",
artistName: "Artist Name",
audioURL: Bundle.main.url(forResource: "upbeat", withExtension: "mp3")!
)
}
- #Preview: Provides a preview of the
MusicPlayerView
with sample data.- Replace
"album_art"
and"upbeat"
with your actual image and audio file names.
- Replace
If you need the complete code for this app, you can find it below.
import SwiftUI
import AVFoundation
struct MusicPlayerView: View {
@State private var isPlaying = false
@State private var currentTime: Double = 0.0
@State private var audioPlayer: AVAudioPlayer?
let albumArt: Image // Image for the album art
let trackName: String
let artistName: String
let audioURL: URL
var body: some View {
VStack(alignment: .center, spacing: 20) {
Image(systemName: "music.note")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 250, height: 250)
.background(Color.gray.opacity(0.1))
.cornerRadius(20)
.shadow(radius: 10)
VStack(alignment: .center, spacing: 5) { // Track info
Text(trackName)
.font(.headline)
Text(artistName)
.font(.subheadline)
.foregroundColor(.secondary)
}
Slider(value: $currentTime, in: 0...(audioPlayer?.duration ?? 0.0))
.padding(.horizontal)
HStack(spacing: 30) { // Playback controls
Button(action: {
// Previous track logic
}) {
Image(systemName: "backward.fill")
.font(.largeTitle)
}
Button(action: {
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.font(.largeTitle)
}
Button(action: {
// Next track logic
}) {
Image(systemName: "forward.fill")
.font(.largeTitle)
}
}
}
.padding()
.onAppear {
prepareAudioPlayer() // Load the audio when the view appears
startPlaybackTimer() // Start the timer to update the slider
}
}
func prepareAudioPlayer() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
audioPlayer?.prepareToPlay()
} catch {
print("Error loading audio: \(error)")
}
}
func startPlaybackTimer() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
if let player = audioPlayer, player.isPlaying {
currentTime = player.currentTime
}
}
}
}
#Preview {
MusicPlayerView(
albumArt: Image("album_art"), // Replace with your image
trackName: "Sample Track",
artistName: "Artist Name",
audioURL: Bundle.main.url(forResource: "upbeat",
withExtension: "mp3")!
)
}
Thanks for reading this article.
Are you still looking at getting your website done/ completed? Contact e.solus@gmail.com
Struggling to rank on Google? Our SEO experts can help. Contact es.olus@gmail.com