The music we share with friends.
It was a dark and stormy stardate 57162.3
The girl slumped down in her office chair. The meeting was over and once again it had been one of those meetings. But that’s okay, she knew how to deal with them and how to regain control of her emotions afterwards. One of the group chats she was in served as a great outlet stress and they were all her chosen family. It was a safe place for all of them to vent about what’s going on in life. The second thing she needed was some music.
She queued something up on her HomePod’s and closed her eyes waiting for the bass to drop and the stress to start slowly leaving her body as the music took hold. Opening her eyes again, she moved to the next step in the ritual. She wanted to share the music she was listening to with the group as it was relevant to her outpouring of emotions and regaining control.
This time though, she was confronted by issues. She had to go looking for the song in music.app and then navigate to the share sheet so that she could simply get the URL for the track she wanted to share. It was all too much and she threw up her arms in frustration, letting out a high pitched wail of frustration. There had to be a better way of handling this situation.
File -> New Project
Once the inevitable break down had subsided, the girl was spurred into action. She had a problem to solve and would not be satisfied until a situation had presented itself. There had to be an easier way to share the music with her friends. Her mental health depended on it. Well, maybe not depend on but it was a distraction.
Breaking apart the task, there were a few things that needed to be accomplished so she could start sharing the music she listened to with her friends.
- Obtaining permission
- Finding out the playback history
- Creating a UI
- Sharing the track
- Access from anywhere
A plan set out, it was now time for the girl to get to work.
Setting up the project
Surprisingly this turned out to be the most complex of the issues to address. The girl had to set up a complete project in the Apple Developer portal in order to get access to MediaKit from within her application. While impressed by the structure of the MusicKit API, she was caught out by how it interacted with lower level parts of apples platforms. Like WeatherKit and ShazamKit the MusicKit framework made use of a system daemon that handled things such as caching and authentication for all apps making use of the framework. She let a relaxed smile appear on her face having realised this. The extra steps involved were there for a purpose and meant that the task of having her app remain a good citizen when using the MusicKit API was handled by the platform. She noted down the documentation link in her notebook that constantly sat open by her side (what girl doesn’t have a stack of notebooks just sitting around with random things contained therein?)
Obtaining permission
A grin crept across the girls face. She had the project set up. She was now ready to get going with the fun part of the project. She would be able to write some code. There would be unit tests, there would be comments. The structure would flow like a well choreographed dance number. Grace, elegance, fluidity. All the things that reflect the girls nature and attitude towards life. Everything about her gets poured out into the work she does.
First up, she needed to ask the for the users permission to access the music library on behalf of the user. This is always an important step as accessing user data without permission is an invasion of the user privacy. She remembered the stories about peoples data being harvested and sold and she didn’t want anything about that. Thankfully, there was a type in MusicKit helpfully labeled as MusicAuthorization that contained exactly what she needed.
While reading the documentation, she noticed that there needed to be a property list value added to the info.plist
file labeled NSAppleMusicUsageDescription. When requesting authorisation, the text associated with this value is presented to the user. It makes clear what the consent is being requested and why it is being requested.
Well, she thought to herself, that’s another point for relying on the platform to take care of the heavy lifting. She doesn’t need to make her own implementation. Let’s test this out she sang to herself and proceeded to place some code into the view model (structure is important and all business logic gets separated out from the view).
let status = switch await MusicAuthorization.currentStatus {
case .authorized:
.authorized
case .restricted:
.restricted
case .denied, .notDetermined:
MusicAuthorization.request()
@unknown default:
break
}
if case .authorized = status || case .restricted = status {
// Handle request here
}
On the first run, she let out a cheer as the prompt appeared to ask for consent. Forward momentum is always a good thing.
Finding out the playback history
Knowing that she could access the music library she could now ask questions of it. These questions were done using a request / response handshake. Thankfully she could make use of the generic types to create a specific query and get the response crafted to her liking. This was a powerful side of the swift programming language that she always enjoyed using. MusicRecentlyPlayedRequest was the type that she needed for the request and looking at it, she could see that it was generic over the type of MusicItem that included things like Album, Playlist, Song and Track that were perfect for her needs. She could easily customise the apps behaviour to show any of these or even more as long as it conformed to the correct protocol. Another point to well designed API’s.
With the request created, she then needed to get a response. There was a corresponding response to the request available to her that she could make use of. This was MusicRecentlyPlayedResponse. So it was now time for the girl to get to writing some more code.
var request = MusicRecentlyPlayedRequest<Track>()
request.limit = 1
let response = try await request.response()
guard let track = response.items.first else {
throw Errors.noRecentlyPlayed
}
// Do something with the track data returned
Creating a UI
With the data now flowing to her app, it was time to create the UI. As the girl had selected a multiplatform app when creating the project, there was only one approach to take for the UI and that was to use SwiftUI as it meant she could be expressive in how she built the UI and know that the platform running the app would happily translate the UI to something appropriate for its conventions and behaviours. Gone were the days having to splatter #if os(macOS)
all throughout the codebase and have multiple targets each with their own quirks and conventions.
To get started, the girl envisioned a simple UI. One that started by showing information over delight. She would move onto that at some stage, but for now she was content on getting things on the screen first.
VStack(alignment: .center, spacing: 12) {
AsyncImage(url: song.imageURL) { image in
image
.resizable()
.frame(width: 400, height: 400)
} placeholder: {
ProgressView()
.progressViewStyle(.circular)
}
Text(song.title)
Text(song.artist)
HStack {
if let shareURL = song.shareURL {
ShareLink(item: shareURL)
}
Button("Copy", systemImage: "doc.on.doc") {
viewModel.copyToPasteboard()
}
}
}
Sharing the track
It was here that the girl was faced with a choice. Did she want to implement the standard platform behaviour of a Share Sheet that provided the most extensible way of getting the information about the album / track / song / playlist to others? Did she want to just copy the URL to the clipboard so that she could paste it where she wanted? A lot of her current frustration for building the new app stemmed from the lack of discoverability around the share sheet in Apple’s Music app and how it introduced many extra steps to completing the task at hand. She also understood that a URL often lacks the extra information required when sharing. In the end, she needed to provide both options to the users. The first option, using a share sheet, could be achieved by using the ShareLink type in SwiftUI and letting the platform take care of the nuance around presentation.
The second option required a bit more nuance as NSPasteboard and UIPasteboard had their own quirks and the girl needed to resort to having different behaviour between. Thankfully the differences weren’t substantial. The girl also took note that the pasteboard was for more than just string and binary values. She could be expressive with the data she provided and that would let other apps determine if and how they responded to a paste event. For her app, specifying that the pasteboard was handling a URL was sufficient.
#if os(macOS)
NSPasteboard.general.setString(url.absoluteString, forType: .URL)
#else
UIPasteboard.general.url = url
#endif
Access from anywhere
At last the girl could get the song she was listening to could be shared easily with her friends. But there were quirks. Apple’s platforms were great for multitasking, but they still needed switching between apps. You could scatter multiple windows over the desktop on macOS but you still had to switch between them. On iOS and iPadOS there was a need to switch between them as well. Thankfully on macOS the problem could be addressed by the use of MenuBarExtra as a scene within the app. On iOS and iPadOS she would need to look for other solutions.
MenuBarExtra("Your App Name", systemImage: "music.note.list") {
// UI goes here
}
.menuBarExtraStyle(.window)
Taking it further
The girls voice rang out in jubilation. She had a solution to her problem and she was keen to share it with the world at large. Her friends first as they received the test flight link and then she put her mind to releasing the app once feedback had been received. This is where her passion lies, her delight is found. Her voice. Her song.
narrators note app is still being finalised though will be released at some point in the future.