How to show MPMediaItem Artwork in a SwiftUI list?

1.4k Views Asked by At

I try to build a SwiftUI List of all local stored songs on my iPhone. Im using the MediaPlayer Framework of Apple to get the songs and storing them inside an EnvironmentObject for easy access in my SwiftUI view.

Inside my Cell im accessing the image via, but all i get is a white 50x50 block:

Image(uiImage: self.item.artwork!.image(at: CGSize(width: 50, height: 50))!)

Result: https://i.stack.imgur.com/eQXCN.png

// EnvironmentObject
class UserData: ObservableObject {

    @Published var allowMusicLibraryAccess: Bool = false
    @Published var songs: [MPMediaItem]

    init() {
        self.songs = [MPMediaItem]()

        self.initAllowMusicLibraryAccess()
    }

    private func initAllowMusicLibraryAccess() -> Void {
        MPMediaLibrary.requestAuthorization { status in
            if status == .authorized {
                DispatchQueue.main.async {
                    self.allowMusicLibraryAccess = true
                    self.songs = MPMediaQuery.songs().items!
                }
            }
        }
    }
}
// List
struct ContentView: View {

    @EnvironmentObject var userData: UserData

    var body: some View {
        ZStack() {
            if self.userData.allowMusicLibraryAccess {
                NavigationView {
                    List {
                        ForEach(self.userData.songs, id: \.persistentID) { song in
                            SongCell(song)
                        }
                    }
                    .navigationBarTitle("Songs")
                }
            } else {
                Text("Music Library Access needed")
            }
        }
    }
}
// Cell
struct SongCell: View {

    let item: MPMediaItem

    init(_ item: MPMediaItem) {
        self.item = item
    }

    var body: some View {

        Button(action: {
            print("clicked \(self.item)")
        }) {
            HStack() {
                if self.item.artwork != nil {
                    Image(uiImage: self.item.artwork!.image(at: CGSize(width: 50, height: 50))!)
                        .resizable()
                        .frame(width: 50, height: 50)
                        .cornerRadius(4)
                }

                VStack(alignment: .leading) {
                    Text(self.item.title ?? "---")

                    Text(self.item.artist ?? "---")
                        .font(.system(.footnote))
                        .opacity(0.7)
                }
            }
        }
        .frame(minWidth: nil, idealWidth: nil, maxWidth: .infinity, minHeight: 55, idealHeight: 55, maxHeight: 55, alignment: .leading)
    }
}
3

There are 3 best solutions below

0
KevinP On

I did figure it out, but it kinda seems like a bug ore some really interesting Initialization behavior I cant explain.

I figured out that moving the Image out of the Cell and directly into the List ForEach fixed that problem. Like I said, I cant explain why this is the case.

// Cell
struct SongCell: View {

    let item: MPMediaItem

    init(_ item: MPMediaItem) {
        self.item = item
    }

    var body: some View {

        Button(action: {
            print("clicked \(self.item)")
        }) {
            VStack(alignment: .leading) {
                Text(self.item.title ?? "---")

                Text(self.item.artist ?? "---")
                    .font(.system(.footnote))
                    .opacity(0.7)
            }
        }
        .frame(minWidth: nil, idealWidth: nil, maxWidth: .infinity, minHeight: 55, idealHeight: 55, maxHeight: 55, alignment: .leading)
    }
}
// List
struct ContentView: View {

    @EnvironmentObject var userData: UserData

    var body: some View {
        ZStack() {
            if self.userData.allowMusicLibraryAccess {
                NavigationView {
                    List {
                        ForEach(self.userData.songs, id: \.persistentID) { song in
                            HStack() {
                                Image(uiImage: song.artwork!.image(at: CGSize(width: 50, height: 50))!)
                                    .resizable()
                                    .frame(width: 50, height: 50)
                                    .cornerRadius(4)
                                SongCell(song)
                            }    
                        } 
                    }
                    .navigationBarTitle("Songs")
                }
            } else {
                Text("Music Library Access needed")
            }
        }
     }
}
0
Chorus Audio On

The solution is to use the .renderingMode() modifier:

Image(uiImage: self.item.artwork!.image(at: CGSize(width: 50, height: 50))!)
    .renderingMode(.original)
0
Nitish Kumar On

With iOS 15,

We can use

ArtworkImage(artwork: self.item.artwork, width: 250) // Specify the width