Having trouble getting the data from MusicKit Searches back to my SwiftUI view. Normally I would use a @State variable for the decodable structure, but with MusicKit you dont need to decode anything and I can't figure out how to best get the data back to my view. I call the MusicKit function using .onAppear in my SwiftUI view
func getAlbum() async throws -> MusicItemCollection<Album> {
var request = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: "1440881121")
let response = try await request.response()
print(response.debugDescription)
return response.items
printing debugDescription give these results:
MusicCatalogResourceResponse<Album>(
items: [
Album(
id: "1440881121",
title: "Colors",
artistName: "Beck",
contentRating: "explicit",
copyright: Fonograf Records/Capitol Records; ℗ 2017 Fonograf Records, under exclusive license to UMG Recordings, Inc.,
genreNames: [
"Alternative",
"Music"
],
isCompilation: false,
isComplete: true,
isDigitalMaster: true,
isSingle: false,
releaseDate: "2017-10-13",
trackCount: 11,
upc: "00602557176827"
Hoe would you access all of the data in the debugDescription and use it in your SwiftUI view? I can only access the standard name and id. Is anyone using Codable to move the data through a data model? The documentation is not making sense to me even though I am usually ok using it.
Also, should I be using `func getAlbum() async throws -> MusicCatalogResourceResponse {' instead of using MusicItemCollection?
Thanks
Hello @AnimalOnDrums,
There are a couple of things that can be greatly simplified in your code.
First of all, for the List, you don't have to pass the album as the data, since the items of the list are static anyway.
Second, you don't have to store the tracks separately.
Lastly, I would urge you to be a lot more careful with your usage of the force-unwrap operator, as it can easily cause your app to crash if you forget to check that an optional contains a non-nil
value.
To that end, let me offer an alternate implementation of your view that uses MusicKit for Swift as it was intended.
Let's start with a convenience extension on Optional to display values that might be missing:
extension Optional {
var displayableOptionalValue: String {
let displayableOptionalValue: String
if let value = self {
displayableOptionalValue = "\(value)"
} else {
displayableOptionalValue = "not available"
}
return displayableOptionalValue
}
}
Then, let's factor out the code for the items of your list into a reusable view, to reduce the amount of duplicated code:
struct MyMusicAttributeView<V>: View {
let label: String
let value: V?
var body: some View {
Text("\(label): \(value.displayableOptionalValue)")
.foregroundColor(.secondary)
.padding(2)
}
}
With this, you can implement your ContentView
more simply like this:
struct MyView: View {
@State private var album: Album?
@State private var selection: Set<Track.ID> = []
@State private var sortOrder = [
KeyPathComparator<Track>(\.trackNumber, order: .forward)
]
var body: some View {
content
.frame(minWidth: 1000, minHeight: 800)
.task {
let authorizationStatus = await MusicAuthorization.request()
if authorizationStatus == .authorized {
var request = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: "1440881121")
request.properties = [.tracks]
let response = try? await request.response()
self.album = response?.items.first
}
}
}
@ViewBuilder
private var content: some View {
if let album = self.album {
details(for: album)
} else {
Text("Loading…")
}
}
private func details(for album: Album) -> some View {
VStack(alignment: .leading) {
HStack {
if let artwork = album.artwork {
ArtworkImage(artwork, width: 400)
}
List {
MyMusicAttributeView(label: "album id", value: album.id)
MyMusicAttributeView(label: "title", value: album.title)
MyMusicAttributeView(label: "artist", value: album.artistName)
MyMusicAttributeView(label: "composer", value: album.artistName)
MyMusicAttributeView(label: "total tracks", value: album.trackCount)
MyMusicAttributeView(label: "genres", value: album.genreNames.joined(separator: ", "))
MyMusicAttributeView(label: "release date", value: album.releaseDate?.formatted(date: .abbreviated, time: .omitted))
MyMusicAttributeView(label: "record label", value: album.recordLabelName)
MyMusicAttributeView(label: "copyright", value: album.copyright)
MyMusicAttributeView(label: "upc", value: album.upc)
}
}
if let standardEditorialNotes = album.editorialNotes?.standard {
MyMusicAttributeView(label: "editorialNotes.standard", value: standardEditorialNotes)
}
if let tracks = album.tracks {
Table(tracks, selection: $selection, sortOrder: $sortOrder) {
TableColumn("track", value: \.trackNumber.displayableOptionalValue)
TableColumn("title", value: \.title)
TableColumn("artist", value: \.artistName)
TableColumn("release date") { track in
Text((track.releaseDate?.formatted(date: .abbreviated, time: .omitted)).displayableOptionalValue)
}
TableColumn("duration", value: \.duration.displayableOptionalValue)
TableColumn("isrc", value: \.isrc.displayableOptionalValue)
}
}
}
}
}
As shown here, you rarely need to type out MusicItemCollection in your app-level code. There is often a simpler and more elegant solution, such as just storing the top level music item you're trying to display, such as the Album, in this case.
I hope this helps.
Best regards,