Building a Music Player UI with SwiftUI

SwiftUI is Apple’s modern declarative UI framework for building apps across all Apple platforms, including iOS, macOS, watchOS, and tvOS. Building a sleek and intuitive music player UI is an excellent way to explore SwiftUI’s capabilities. In this comprehensive guide, we’ll walk you through creating a feature-rich music player interface using SwiftUI, complete with code examples.

Why SwiftUI for Music Player UI?

SwiftUI offers several advantages when creating user interfaces for media-rich applications like music players:

  • Declarative Syntax: Simplifies UI development by describing what the UI should look like rather than specifying the steps to create it.
  • Live Preview: Provides real-time feedback on UI changes without needing to build and run the app.
  • Cross-Platform Compatibility: Allows sharing code across different Apple platforms.
  • Automatic Updates: Manages UI updates efficiently based on changes in the underlying data.

Prerequisites

Before you begin, make sure you have:

  • Xcode installed on your macOS.
  • Basic knowledge of Swift programming.
  • Familiarity with SwiftUI concepts like State, View, and Modifiers.

Setting Up the Project

Create a new Xcode project using the “App” template and select SwiftUI for the user interface.

Building the Music Player UI Step-by-Step

Step 1: Defining the Data Model

First, create a struct to represent a song with properties like title, artist, and cover image.

import SwiftUI

struct Song: Identifiable {
    let id = UUID()
    let title: String
    let artist: String
    let coverImage: String // Name of the asset image
}

let sampleSongs = [
    Song(title: "Adventure of a Lifetime", artist: "Coldplay", coverImage: "coldplay_adventure"),
    Song(title: "Starlight", artist: "Muse", coverImage: "muse_starlight"),
    Song(title: "High Hopes", artist: "Pink Floyd", coverImage: "pinkfloyd_highhopes"),
    // Add more sample songs here
]

Ensure you have image assets named according to coverImage values in your Assets.xcassets.

Step 2: Creating the Music Player View

Create the main Music Player View, displaying album art, song title, artist name, playback controls, and a progress slider.

import SwiftUI

struct MusicPlayerView: View {
    @State private var isPlaying = false
    @State private var currentTime: Double = 0.0
    let song: Song
    
    var body: some View {
        VStack {
            // Album Cover Image
            Image(song.coverImage)
                .resizable()
                .scaledToFit()
                .frame(width: 300, height: 300)
                .shadow(radius: 10)
            
            // Song Information
            Text(song.title)
                .font(.title)
                .fontWeight(.bold)
                .padding(.top)
            
            Text(song.artist)
                .font(.subheadline)
                .foregroundColor(.secondary)
            
            // Playback Progress Slider
            Slider(value: $currentTime, in: 0...100) // Dummy range, replace with actual duration
                .padding()
            
            // Playback Controls
            HStack {
                Button(action: {
                    // Previous track action
                }) {
                    Image(systemName: "backward.fill")
                        .font(.largeTitle)
                }
                .padding()
                
                Button(action: {
                    isPlaying.toggle()
                    // Add actual playback toggle functionality here
                }) {
                    Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
                        .font(.system(size: 60))
                        .padding()
                }
                
                Button(action: {
                    // Next track action
                }) {
                    Image(systemName: "forward.fill")
                        .font(.largeTitle)
                }
                .padding()
            }
        }
        .padding()
    }
}

struct MusicPlayerView_Previews: PreviewProvider {
    static var previews: some View {
        MusicPlayerView(song: sampleSongs[0]) // Provide a sample song for preview
    }
}

In this snippet:

  • The view displays the album cover, title, and artist.
  • A slider indicates playback progress.
  • Playback controls are managed via HStack.

Step 3: Implementing Playback Control

Update the play/pause button action and incorporate corresponding logic. This example simply toggles a state variable to reflect the play status; real implementation requires audio player integration.

Button(action: {
    isPlaying.toggle()
    // Placeholder for starting or stopping audio playback
    if isPlaying {
        print("Playing \(song.title)")
    } else {
        print("Paused \(song.title)")
    }
}) {
    Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
        .font(.system(size: 60))
        .padding()
}

Step 4: Creating a List of Songs

Create a SongListView to display a list of songs, and allow the user to select a song to play. Each song should be tappable to navigate to its music player view.

import SwiftUI

struct SongListView: View {
    let songs: [Song]

    var body: some View {
        NavigationView {
            List(songs) { song in
                NavigationLink(destination: MusicPlayerView(song: song)) {
                    HStack {
                        Image(song.coverImage)
                            .resizable()
                            .frame(width: 50, height: 50)
                            .cornerRadius(8)

                        VStack(alignment: .leading) {
                            Text(song.title)
                                .font(.headline)
                            Text(song.artist)
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                        }
                    }
                }
            }
            .navigationTitle("My Music")
        }
    }
}

struct SongListView_Previews: PreviewProvider {
    static var previews: some View {
        SongListView(songs: sampleSongs)
    }
}

Step 5: Integrating SongListView in the Main App View

In your main App file (e.g., YourAppNameApp.swift), replace the default content with SongListView:

import SwiftUI

@main
struct SwiftUIMusicApp: App {
    var body: some Scene {
        WindowGroup {
            SongListView(songs: sampleSongs)
        }
    }
}

Step 6: Enhance the Music Player View

Improve MusicPlayerView with additional functionalities such as track scrubbing using the slider, displaying current time and duration, and volume control.

import SwiftUI

struct MusicPlayerView: View {
    @State private var isPlaying = false
    @State private var currentTime: Double = 0.0
    @State private var volume: Double = 0.5 // Initial volume level
    let song: Song
    
    var body: some View {
        VStack {
            // Album Cover Image
            Image(song.coverImage)
                .resizable()
                .scaledToFit()
                .frame(width: 300, height: 300)
                .shadow(radius: 10)
            
            // Song Information
            Text(song.title)
                .font(.title)
                .fontWeight(.bold)
                .padding(.top)
            
            Text(song.artist)
                .font(.subheadline)
                .foregroundColor(.secondary)
            
            // Playback Progress Slider with Time Display
            HStack {
                Text(formatTime(currentTime))
                Slider(value: $currentTime, in: 0...100) // Replace 100 with song duration
                Text(formatTime(100)) // Replace 100 with song duration
            }
            .padding()
            
            // Playback Controls
            HStack {
                Button(action: {
                    // Previous track action
                }) {
                    Image(systemName: "backward.fill")
                        .font(.largeTitle)
                }
                .padding()
                
                Button(action: {
                    isPlaying.toggle()
                    // Add actual playback toggle functionality here
                    if isPlaying {
                        print("Playing \(song.title)")
                    } else {
                        print("Paused \(song.title)")
                    }
                }) {
                    Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
                        .font(.system(size: 60))
                        .padding()
                }
                
                Button(action: {
                    // Next track action
                }) {
                    Image(systemName: "forward.fill")
                        .font(.largeTitle)
                }
                .padding()
            }
            
            // Volume Control
            HStack {
                Image(systemName: "speaker.fill")
                Slider(value: $volume)
                Image(systemName: "speaker.wave.3.fill")
            }
            .padding()
        }
        .padding()
    }
    
    // Helper function to format time
    func formatTime(_ timeInSeconds: Double) -> String {
        let minutes = Int(timeInSeconds / 60)
        let seconds = Int(timeInSeconds) % 60
        return String(format: "%02d:%02d", minutes, seconds)
    }
}

Testing and Iterating

Use Xcode’s live preview and simulators to test your music player UI. Refine the design based on user feedback and usability testing.

Integrating with an Audio Player

To make the UI functional, integrate with an audio player using frameworks like AVFoundation. The following example is highly simplified and only covers playing from a local file. Playing from streaming services and managing buffering requires more advanced techniques.

import SwiftUI
import AVFoundation

class AudioPlayerManager: ObservableObject {
    var audioPlayer: AVAudioPlayer?
    @Published var isPlaying: Bool = false

    func startPlayback(songFileName: String) {
        guard let url = Bundle.main.url(forResource: songFileName, withExtension: "mp3") else {
            print("Resource not found")
            return
        }

        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url)
            audioPlayer?.prepareToPlay()
            audioPlayer?.play()
            isPlaying = true
        } catch {
            print("Playback error: \(error.localizedDescription)")
        }
    }

    func stopPlayback() {
        audioPlayer?.stop()
        isPlaying = false
    }
}

Use this class in your MusicPlayerView:

struct MusicPlayerView: View {
    @ObservedObject var audioPlayerManager = AudioPlayerManager()
    let song: Song
    //... (other parts of the view)

    var body: some View {
        //... (album art, song info, other UI elements)

        Button(action: {
            if audioPlayerManager.isPlaying {
                audioPlayerManager.stopPlayback()
            } else {
                audioPlayerManager.startPlayback(songFileName: song.coverImage) // Use song's identifier
            }
        }) {
            Image(systemName: audioPlayerManager.isPlaying ? "pause.circle.fill" : "play.circle.fill")
                .font(.system(size: 60))
                .padding()
        }
        //...
    }
}

Conclusion

SwiftUI offers an elegant way to design a music player UI that is both visually appealing and functionally rich. By following these steps, you can create a robust and user-friendly interface. Further improvements could include playlist management, search functionality, streaming support, and better error handling. Leveraging SwiftUI’s declarative approach and live preview allows for efficient development and quick iterations.