Post

Replies

Boosts

Views

Activity

Reply to Run async tasks using Background Tasks
HI @eskimo, Thanks for chiming in and providing insight. Using the below, I set a breakpoint right when the app is moved to the background (I'm not using appDelegate). .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in       backgroundTaskManager.scheduleAppRefresh() // <= breakpoint here     } Once the app is paused, in Xcode, I run the following command in the Xcode terminal: e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.example.task"] That responds with: 2021-12-07 05:29:25.405222+0700 App[602:49934] Simulating launch for task with identifier com.example.task After that, I resume the app in Xcode and see (I believe "missing data" is not an error below): 2021-12-07 05:29:29.724492+0700 App[602:50205] Starting simulated task: <decode: missing data> Followed by the initial operation of the getUpdateAppData() function being called. The initial operation is a function that runs an Amplify (iOS library) graphQL query (here's an excerpt): func getMyFriends(currentUser: User) async throws -> [Friend] {     Logger.log(.fetch, "Fetching \(currentUser.username)'s friends...")     return try await withCheckedThrowingContinuation { continuation in       let friend = Friend.keys       let predicate = friend.owner == currentUser.username       Amplify.API.query(request: .list(Friend.self, where: predicate)) { event in         switch event {         case .success(let result): ... The app seems to just stop right after the logging of "Fetching (currentUser.username)'s friends..." No errors are thrown that I can see. Note this function works fine when it's called when the app is in the foreground. Also, when I return the app to the foreground, the getUpdateAppData() tries to resume, and rarely (if ever) completes as it should: 🐕 Fetching Elmer's friends... 2021-12-07 06:02:20.150514+0700 App[654:65556] Connection 1: encountered error(1:53) // <= upon return to foreground 2021-12-07 06:02:20.150650+0700 App[654:65556] Connection 1: received failure notification 2021-12-07 06:02:20.151257+0700 App[654:65556] [connection] nw_connection_copy_connected_local_endpoint [C1] Connection has no connected path 2021-12-07 06:02:20.151450+0700 App[654:65556] [connection] nw_connection_copy_connected_remote_endpoint [C1] Connection has no connected path 2021-12-07 06:02:20.153068+0700 App[654:65556] Connection 2: encountered error(1:53) 2021-12-07 06:02:20.153125+0700 App[654:65556] Connection 2: received failure notification 2021-12-07 06:02:20.153280+0700 App[654:65556] [connection] nw_connection_copy_connected_local_endpoint [C2] Connection has no connected path 2021-12-07 06:02:20.153891+0700 App[654:65556] [connection] nw_connection_copy_connected_remote_endpoint [C2] Connection has no connected path 2021-12-07 06:02:20.155082+0700 App[654:65556] Connection 3: encountered error(1:53) 2021-12-07 06:02:20.155293+0700 App[654:65556] Connection 3: received failure notification 2021-12-07 06:02:20.161795+0700 App[654:65556] [connection] nw_connection_copy_connected_local_endpoint [C3] Connection has no connected path 2021-12-07 06:02:20.161877+0700 App[654:65556] [connection] nw_connection_copy_connected_remote_endpoint [C3] Connection has no connected path 🦴 Successfully fetched Elmer's friends! ... Maybe Amplify doesn't work when in the background? I dunno. One thing that I have yet to try is testing with a simple, single API operation in lieu of the somewhat more involved getUpdateAppData() function that is currently running through completion. Any further thoughts and/or advice is appreciated. private func configureBackgroundTasks() { Logger.log(.info, "Registering background tasks...") let bgTaskIdentifier = "com.example.task" BGTaskScheduler.shared.register(forTaskWithIdentifier: bgTaskIdentifier, using: DispatchQueue.main) { (task) in Logger.log(.info, "Performing background task \(bgTaskIdentifier)") Task { if let updatedData = await appManager.getUpdateAppData() { DispatchQueue.main.async { appManager.data = updatedData task.setTaskCompleted(success: true) } } else { task.setTaskCompleted(success: false) } backgroundTaskManager.scheduleAppRefresh() } } }
Topic: App & System Services SubTopic: Core OS Tags:
Dec ’21
Reply to Run async tasks using Background Tasks
Hi @OOPer, Thanks much for taking the time to respond. I have tried several variations, in an attempt to get this working. The result from your suggested mods: using Task instead of Task.init and putting task.setTaskCompleted(success: true) in a DispatchQueue.main.async {}, results in: 2021-12-04 14:58:39.136393-0800 App[8341:2562502] [connection] nw_read_request_report [C7] Receive failed with error "Socket is not connected" 2021-12-04 14:58:39.139668-0800 App[8341:2562502] [connection] nw_read_request_report [C7] Receive failed with error "Socket is not connected" Doing the same, but using Task.init {... results in: 2021-12-04 15:03:53.128265-0800 App[8344:2563988] [Framework] Task <BGAppRefreshTask: com.example.task> dealloc'd without completing. This is a programmer error. 2021-12-04 15:03:53.128610-0800 App[8344:2563988] Marking simulated task complete: (null) I do like the very specific calling out the programmer error 😄 I've also tried using BGProcessingTaskRequest instead of BGAppRefreshTaskRequest just in case the update operation was timing out after 30 seconds, which I believe is the time window alloted for a refresh task.
Topic: App & System Services SubTopic: Core OS Tags:
Dec ’21
Reply to Deep link to Apple Music library playlist from Swift (iOS) app
It turns out you can open Apple Music to the user's library, using /library/ in the path, but it seems that's as deep as it will link. This works let urlString = "music://music.apple.com/library"                                         if let url = URL(string: urlString) {                                             await UIApplication.shared.open(url)                                         } This opens Apple Music to the user's library. This doesn't let urlString = "music://music.apple.com/library/playlists"                                         if let url = URL(string: urlString) {                                             await UIApplication.shared.open(url)                                         } This doesn't let urlString = "music://music.apple.com/library/playlist/\(playlistID)"                                         if let url = URL(string: urlString) {                                             await UIApplication.shared.open(url)                                         } The above two open Apple Music, which then displays an error: "An Error Occurred" with a Try Again button
Topic: Media Technologies SubTopic: General Tags:
Oct ’21
Reply to iOS 15 App Stops immediately after launch screen when using ipa
Okay, I tracked this down to an environment variable that was being used by the "dev" (i.e. debug) environment. As environment variables are not available in "production" (i.e. release), this one particular variable was causing the app to just stop. There was no message in the console log that reflected this that I could see, though there were guards that threw fatally if the environment variable was not present. Anyhow, once this was moved to a .plist and called from there, the app loaded as expected in both dev and prod.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Oct ’21
Reply to Retrieving a list of tracks from a library playlist
Thanks again, @JoeKun! I was able to retrieve the tracks using your code example above. Your mention of URLComponents saved me a lot of time, as appending the a query param wasn't working as I thought it would. That said, I'm a befuddled, as there seems to be a mismatch between track ids when retrieved via the above code example you provided, and those retrieved using MusicCatalogRequest and/or MusicCatalogSearchRequest. Again, what I'm trying to do is prevent the addition of tracks that already exist in a user's playlist, so I'm checking the playlist contents prior to adding the tracks. To do this: I create an array (Array 1) of ids from the tracks fetched in the above function (via MusicDataRequest), then I filter over an array (Array 2) of tracks that have been fetched via MusicCatalogRequest or MusicCatalogSearchRequest. In each filter loop, I only return if the Array 2 element's id is not in Array 1. Something like this: let tracksNotInPlaylist = Array2.filter{!array1.contains($0.id)} Then I only add the tracks that are in the tracksNotInPlaylist array to the playlist, preventing the undesired duplication. This isn't working, so I logged and compared the values of the ids. Apparently, the track IDs in Array 1 are strings, in the form of: i.RB4aoUZ5EAmd, and the ids of the tracks in Array2 are integers, such as: 1023383902. My assumption is that MusicItemCollection<Track> stores IDs differently than items of type Track. I'm at a loss of how to get these to align.
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to Retrieving a list of tracks from a library playlist
Hi @JoeKun, One thing I noticed in the error message is that the url is: https://api.music.apple.com/v1/catalog/us/playlists/p.7J5xs48VxVx?include=tracks&omit%5Bresource%5D=autos This url is missing the me/library, using instead catalog, which leads me to believe that MusicCatalogResourceRequest is not appropriate for fetching items in a user's library, and that this is where I would need to use a MusicDataRequest.
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to Retrieving a list of tracks from a library playlist
The playlistID is originally retrieved from the following function:     func getCurrentUsersAppleMusicPlaylists() async throws -> [AppleMusicPlaylist] {         print("Fetching AppleMusic Playlists...")         var playlists: [AppleMusicPlaylist] = []         do {             if let url = URL(string: "https://api.music.apple.com/v1/me/library/playlists?limit=100") {                 let dataRequest = MusicDataRequest(urlRequest: URLRequest(url: url))                 let dataResponse = try await dataRequest.response()                 let decoder = JSONDecoder()                 decoder.keyDecodingStrategy = .convertFromSnakeCase                 let response = try decoder.decode(AppleMusicPlaylistsResponse.self, from: dataResponse.data)                 playlists = response.data                 print("Playlists Fetched")             }         } catch {             print("Error Fetching Current User's Playlists", error)             throw error         }         return playlists     } These are stored as a @Published value in an ObservableObject. Later on in the app, the user selects a default playlist, using a picker, to save tracks to, and that is saved in a @Published variable in an ObservableObject (passed as an environment variable from higher up the chain), which is of type: struct DefaultSaveToPlaylist: Codable, Hashable {     let id: String     let name: String     let type: String } When getCurrentUsersAppleMusicPlaylists()is called, the id (type String) above is converted to an MusicItemID, via MusicItemId(playlistId) and passed as a parameter to the function call. The reasoning behind that final separation is because the detaulSaveToPlaylist is stored in a cloud database and shared across other instances of the app running different devices and operating systems.
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to Retrieving a list of tracks from a library playlist
Hi @JoeKun, All I have at the time of making the above function call is the id of the playlist. So at this time there is no instance of a playlist, unless I'm mistaken. To provide more insight as to what I'm doing: this function is run prior to adding tracks to an existing playlist it should get all of the tracks in the playlist, so that duplicate tracks are not added. the playlistId that is passed into the function is the id (MusicItemID) of the playlist. Not sure how I get from this point to where I have an instance of the playlist.
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to Retrieving a list of tracks from a library playlist
Hi @JoeKun, Thanks for the quick reply. I was using the pattern from here, which seemed appropriate, but again, I get lost in the docs, as there are no examples there. In your above response, you've put an ellipsis in, which would be helpful to know what's there. Is that supposed to be?: let playlistRequest = MusicCatalogResourceRequest<Playlist>(matching: \.id, equalTo: playlistId ) Shouldn't there be a .response() here?: let detailedPlaylist = try await playlist.with([.tracks]) Please provide a full example of the code, if you can, time permitting. For the record, I did see this code in the video you mentioned, but I've been using MusicCatalogRequest for so many things, it didn't click until you just said it.
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to Searching for a track with MusicKit Swift Beta
Hi @JoeKun, I got distracted for a while, but I wanted to come back to this to provide the following feedback. After a while of trying various ways to do this, I wound up re-writing the app around Track type instead of Song, as it just seemed to make more sense. I would not have come up with the below without your feedback to this question. I really wish the docs would include some code examples, as I still battle finding solutions when reading them in their current written form. In case it helps anyone, here's what I came up with: Create a custom type, AppleMusicTrack, that has the properties I want for the app, including track, where I put the Track (MusicKit type) info as well: struct AppleMusicTrack: Identifiable {     var id: String {         return track.id.rawValue // should probably use MusicItemID here.     }     let track: Track // MusicItemID is in here, when I need it.     let album: String     let addedAt: String     let addedBy: String     let trackType: String     let matchedBy: String } Use this function when I can't match a track via isrc     func getAppleMusicTrackBySearch(track: TrackToMatchType) async -> AppleMusicTrack? {         var matchedTrack: AppleMusicTrack? = nil         do {             let albumSearchRequest = MusicCatalogSearchRequest(term: track.track.album.name, types: [Album.self])             let response = try await albumSearchRequest.response()             if(response.albums.count > 0) {                 let matchedAlbums = response.albums.filter{album in                     track.track.album.artists.contains{$0.name == album.artistName}                 }                 if matchedAlbums.count > 0 {                     let scrapedTracks = await self.getAppleMusicTracksByAlbumId(albums: matchedAlbums)                     if(scrapedTracks != nil) {                         if let firstMatch = scrapedTracks!.first(                             where: {                                 $0.title == track.track.name                             }) {                             matchedTrack = AppleMusicTrack(                                 track: firstMatch,                                 album: firstMatch.title,                                 addedAt: track.addedAt,                                 addedBy: track.addedBy.id,                                 trackType: "matchedAppleMusicTrack",                                 matchedBy: "artist/title",                             )                         }                     }                 }             }         } catch {             print("Error", error) // handle error         }         if matchedTrack == nil {             print("No matched track!")         }         return matchedTrack     } The above calls this function to get tracks by album id:     func getAppleMusicTracksByAlbumId(albums: [Album]) async -> [Track]? {         var scrapedTracks: [Track]?         do {             var albumRequest = MusicCatalogResourceRequest<Album>(matching: \.id, memberOf: albums.map(\.id) )             albumRequest.properties = [.tracks]             let albumResponse = try await albumRequest.response()             let albumsWithTracks = albumResponse.items             let tracks = albumsWithTracks.flatMap { album -> MusicItemCollection<Track> in                 album.tracks ?? []             }             scrapedTracks = tracks.count > 1 ? tracks : nil         } catch {             print("Error", error)         }         return scrapedTracks     }
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to Searching for a track with MusicKit Swift Beta
Hi @JoeKun, You are right again! I would have never guessed [Album.self] by reading the docs, though I do see that in the MusicAlbums app, now that you mention it, thanks. Your patience and support are very much appreciated. What I still can't find in the docs (nor in the app) is how to do the above albumRequest and include the tracks (songs would be preferable) in the same call. Unfortunately MusicCatalogSearch request does not have a properties member, so I can't do this: albumSearchRequest.properties = [.tracks] At present the above albumResponse only returns the id, title, and artistName. I've currently worked it out where I do the following: Get a list of albums based on a search string use albums.filter{} to reduce this set to only albums by the desired artist, like this: let matchedAlbums = response.albums.filter{$0.artistName == artistSearchString}       For the record, this actually provides for album info, but not tracks. Do a MusicCatalogResourceRequest for each album in matchedAlbums to get the tracks This doable, but tedious for a few reasons, mainly the return is a Track not a Song, and converting these are a pain. Loop through these to find a track with a criteria matching the one being searched for, which is not hard once the above has been done.
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to How to get the album info for a song fetched by MusicDataRequest?
This is excellent info, @JoeKun. Thank you very much for being so super helpful! I gonna need to add you as a co-author for my app 😊 You know, I looked at MusicCatalogResourceRequest before, but the docs confused me. As I am used to working with the Apple Music API, MusicDataRequest seemed more familiar. Thanks for setting me straight. I'm not sure if this will ever happen, but if the docs had examples like yours in them, they'd be a lot easier to connect the dots.
Topic: Media Technologies SubTopic: General Tags:
Aug ’21
Reply to Adding tracks to User's Library Playlist
Hi @JoeKun, Your reply was spot on! As you can probably tell, I'm new to Swift. So thanks for your patience! In JS, I rely on Axios for https requests, and it handles the "data" somewhat automagically, so I completely glossed over it here. I want to thank you for your response and helping me get this resolved. Frankly, I've never had too much luck getting quality answers from this forum, and now that's changed, which is great! Below is my final code, in case it helps someone. struct AppleMusicPlaylistPostRequestBody: Codable {     let data: [AppleMusicPlaylistPostRequestItem] } struct AppleMusicPlaylistPostRequestItem: Codable {     let id: MusicItemID     let type: String } func addTracksToAppleMusicPlaylist(targetPlaylistId: String, tracksToAdd: [Song]) async throws -> Void {     let tracks = AppleMusicPlaylistPostRequestBody(data: tracksToAdd.compactMap {         AppleMusicPlaylistPostRequestItem(id: $0.id, type: "songs")     })     do {         if let url = URL(string: "https://api.music.apple.com/v1/me/library/playlists/\(targetPlaylistId)/tracks") {             var urlRequest = URLRequest(url: url)             urlRequest.httpMethod = "POST"             let encoder = JSONEncoder()             let data = try encoder.encode(tracks)             urlRequest.httpBody = data             let musicRequest = MusicDataRequest(urlRequest: urlRequest)             let musicRequestResponse = try await musicRequest.response()             print("Music Request Response", musicRequestResponse)             // Notify user of success!         } else {             print("Bad URL!")             throw AddTracksToPlaylistError.badUrl(message: "Bad URL!")         }     } catch {         print("Error Saving Tracks to Playlist", error)         throw error     } }
Topic: Media Technologies SubTopic: General Tags:
Jul ’21
Reply to Adding tracks to User's Library Playlist
Hi @JoeKun, thanks for the response. I did try something similar to the above with MusicDataRequest ; however, as I'm doing a POST, I needed to also send a body. And I was getting errors, which had me thinking that the problem was potentially with MusicDataRequest. Any further help to get this working, would be much appreciated. The below code throws the following error: MusicDataRequest.Error(  status: 400,  code: 40007,  title: "Invalid Request Body",  detailText: "Unable to parse request body",  id: "CKYZ347O6YJDXVPVSTZJOQ4R6Y",  originalResponse: MusicDataResponse(   data: 149 bytes,   urlResponse: <NSHTTPURLResponse: 0x0000000283e9ed00>  ) ) Please see comments within the below: func addTracksToAppleMusicPlaylist(targetPlaylistId: String, tracksToAdd: [Song]) async throws -> Void { struct AppleMusicPlaylistPostRequestItem: Codable {    let id: MusicItemID    let type: String } // This is what I have done using when updating a playlist in a JS app (but without the type) // My guess is that the problem is with the encoded data   let tracks = tracksToAdd.compactMap{     AppleMusicPlaylistPostRequestItem(id: $0.id, type: "songs")   }   do {     print("Saving matched tracks to Apple Music Playlist: \(targetPlaylistId)")     if let url = URL(string: "https://api.music.apple.com/v1/me/library/playlists/\(targetPlaylistId)/tracks") {       var urlRequest = URLRequest(url: url)       urlRequest.httpMethod = "POST"       let encoder = JSONEncoder()       let data = try encoder.encode(tracks)       print(String(data: data, encoding: .utf8)!) // <== See result below.       urlRequest.httpBody = data // <== This is most likey the problem!       let musicRequest = MusicDataRequest(urlRequest: urlRequest)       let musicRequestResponse = try await musicRequest.response()       // maybe do something with response once I get this working...     } else {       print("Bad URL!")       throw AddToPlaylistError.badUrl(message: "Bad URL!")     }   } catch {     print("Error Saving Tracks to Playlist", error)     throw error   } } The output from that print command, above, looks like this, which seems right: [{"id":"426656373","type":"songs"},{"id":"1559839880","type":"songs"},{"id":"1498420189","type":"songs"}]
Topic: Media Technologies SubTopic: General Tags:
Jul ’21