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,