How to recreate Apple Music mini player transition in SwiftUI

Hello,

I am building an audio player app in SwiftUI and trying to recreate the behavior of Apple Music's mini player and full player.

I'm struggling to get the animation to seamlessly transition between the mini player and the full player. Currently, it feels disconnected and doesn't resemble the smooth animation seen in Apple Music.

What I want to achieve:

  • Full player that expands/collapses from/to the mini player
  • Smooth artwork transition between both states
  • Drag down to collapse the full player
  • Support both newer APIs like tabViewBottomAccessory and older iOS versions

Questions:

  • What is the best way to build this transition in SwiftUI?
  • Should I use matchedGeometryEffect or something else?
  • Should this be a custom container instead of fullScreenCover?
  • How would you support both new and older iOS versions?
  • What is the best way to implement drag to dismiss?

Thanks for any help!

Example code:

struct ContentView: View {
    @State private var isFullPlayerPresented = false

    var body: some View {
        TabView {
            Tab("Home", systemImage: "house") {
                Text("Home")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(.green)
            }
            Tab("Library", systemImage: "rectangle.stack.fill") {
                Text("Library")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(.brown)
            }
        }
        .tabViewBottomAccessory(isEnabled: !isFullPlayerPresented) {
            MiniPlayerView(isFullPlayerPresented: $isFullPlayerPresented)
        }
        .fullScreenCover(isPresented: $isFullPlayerPresented) {
            // Maybe it's not a full screen cover presentation in Apple Music?
            FullPlayerView(isFullPlayerPresented: $isFullPlayerPresented)
        }
    }
}

Mini player:

struct MiniPlayerView: View {
    @Binding var isFullPlayerPresented: Bool

    var body: some View {
        Button {
            isFullPlayerPresented = true
        } label: {
            HStack {
                Image(systemName: "photo")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30, height: 30)
                    .clipShape(.rect(cornerRadius: 8))

                Spacer()
                Text("Tap to open full player")
                Spacer()

                Button("", systemImage: "play.fill", action: {})
            }
            .padding(.horizontal)
            .padding(.vertical, 4)
        }
        .foregroundStyle(.white)
    }
}

Full player:

struct FullPlayerView: View {
    @Binding var isFullPlayerPresented: Bool

    var body: some View {
        // This art work needs to snaps to the artwork in mini player
        Image(systemName: "photo")
            .resizable()
            .scaledToFit()
            .frame(width: 250, height: 250)
            .clipShape(.rect(cornerRadius: 20))
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.red)
            .overlay(alignment: .topTrailing) {
                Button(role: .close) {
                    isFullPlayerPresented = false
                }
                .foregroundStyle(.white)
                .padding()
            }
    }
}

Hello @sheikhbayazid

This is a great start!

While we are unable to share exactly how Apple achieved this in the Music app, you might still find the following helpful:

Should this be a custom container instead of fullScreenCover?

You might be looking for a sheet. Users can swipe down to dismiss a sheet interactively by default. .fullScreenCover has no built-in-swipe-to-dismiss, you must provide an explicit mechanism, like a dismiss button.

For more information on Sheets see https://developer.apple.com/design/human-interface-guidelines/sheets

  • How would you support both new and older iOS versions?

Configure the Xcode project to support the intended lowest OS version, then use conditionals to handle differences between iOS versions with availability checks such as #available

if #available(iOS 18.0, *) {
    // Content in iOS 18.0 and later
} else {
    // Content in iOS 17.x and earlier
}

As for the transition effect, the sample code you provided doesn't demonstrate one. Share a code snippet of what you tried here and what goal you are trying to achieve and maybe others could chime in.

 Travis

How to recreate Apple Music mini player transition in SwiftUI
 
 
Q