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 to currentTime.
  • in: 0...(audioPlayer?.duration ?? 0.0): Sets the range from 0 to the audio duration, defaulting to 0.0 if audioPlayer is nil.

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.


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.