SPPlaylist items as SPTracks not loading (consistently) - ios

I've been struggling with this for a while now and was looking around for some advice from anyone who's worked with CocoaLibSpotify on iOS (iOS 7 to be exact).
I'm trying to load all the (SPTrack) items in a SPPlaylist. I've looked though the example code and the documentation so I know the best way to observe things is with KVO, however I can't see how that's the best way here.
Looking at the "Guess the Into" example and other sources on GitHub and the web in general, the precess I'm doing is as follows:
Get the SPPlaylist (this is either directly from a the users playlists or created from a saved URL),
Load the playlist with SPAsyncLoading,
Loop though the SPPlaylistItems returned in the SPPlaylist and get all the SPTrack objects,
Pass the array of SPTrack objects into another SPAsyncLoading call,
Display all items from the loadedItems array.
Step 5 is where things seem to go wrong. Sometimes it is fine and I get all the tracks in the playlist displaying. However most of the time it doesn't load them, or only load a few (normally very little).
I've noticed that restarting the phone can sometimes make it work. Though after running the app (or changing the playlist) a couple of time, it stops working again.
When I log each step I get something like the following:
-[ViewController sessionDidLoginSuccessfully:]
Load playlist with URL: spotify:user:XXXXX:playlist:XXXXXXXXXXXXXXXXXXXXXX
Created playlist: <SPPlaylist: 0x16775bc0>: (null) (0 items)
Loaded playlist: <SPPlaylist: 0x16775bc0>: Playlist Name (59 items)
Created SPTrack items: 59
Loaded SPTrack items: 1
Failed to load SPTrack items: 58
As you can see, the user is logged on, with a valid session. Each step works according to plan until the end. Sometimes even if it does load a track, it won't allow me to play it anyway as well returning SP_TRACK_AVAILABILITY_UNAVAILABLE.
I've also added a code snippet below if it helps. But any help would be grateful at this point with this.
Thanks.
- (void)loadPlaylist
{
__weak NSURL *spotifyURL = <URL from saved SPPlaylist>;
NSLog(#"Load playlist with URL: %#", spotifyURL);
[SPPlaylist playlistWithPlaylistURL:spotifyURL inSession:[SPSession sharedSession] callback:^(SPPlaylist *playlist) {
_spotifyPlaylist = playlist;
NSLog(#"Created playlist: %#", _spotifyPlaylist);
[SPAsyncLoading waitUntilLoaded: _spotifyPlaylist timeout:kSpotifyTimeout then:^(NSArray *loadedItems, NSArray *notLoadedItems) {
NSLog(#"Loaded playlist: %#", _spotifyPlaylist);
if ([loadedItems count] > 0)
[self loadSpotifyPlaylist];
}];
}];
}
- (void)loadSpotifyPlaylist
{
NSArray *tracksArray = [self tracksFromPlaylistItems:[_spotifyPlaylist items]];
NSLog(#"Created SPTrack items: %d", [tracksArray count]);
if (tracksArray && [tracksArray count] > 0)
{
[SPAsyncLoading waitUntilLoaded:tracksArray timeout:kSpotifyTimeout then:^(NSArray *loadedItems, NSArray *notLoadedItems) {
_playlist = loadedItems;
NSLog(#"Loaded SPTrack items: %d", [loadedItems count]);
NSLog(#"Failed to load SPTrack items: %d", [notLoadedItems count]);
[self finishReloadData];
}];
}
_spotifyPlaylist and _playlist are both strong instances and tracksFromPlaylistItems: is the same method used in the "Guess the Info" sample code.
Edit: The kSpotifyTimeout timeout is set to 10.0.

What's your value of kSpotifyTimeout? I think what you're seeing is simply the fact that metadata can take a long time to load (especially yesterday when the Spotify backend seemed to be having trouble, which can happen from time to time).
Generally, loading the entire contents of a playlist at once is bad practice, and you'll see that Guess the Intro doesn't actually care whether all the tracks loaded or not - it just grabs what happened to load and uses those tracks for the game.
A good approach to take is to paginate loading of playlist tracks to match your UI. That is, to only load the tracks your user can see, plus maybe a "screen" or two's worth up and down. As the user scrolls, you can start loading tracks - UIScrollViewDelegates -scrollViewWillEndDragging:withVelocity:targetContentOffset: is particularly good for this, as it lets you see where the user will "land" and start to load tracks for that point. There's some example code for doing this as well as a helper class called SPSparseArray for partially loading content like playlists on the dev branch of CococaLibSpotify (although be aware that dev branch = beta quality).

Related

Is there a way to get PHAssetResource for array of PHAsset?

I'm trying to get all PHAsset from the library. However, when getting them, we need to get the PHAssetResource to get the fileSize.
When we have a small number of images, the array is returned in less than 30 seconds; however, when we have around 12k~ image, it's taking too long.
Is there any way to load the resources for all the assets by one call instead of calling the API several times ?
Example:
NSArray<PHAssetResource *> *const assetResources = [PHAssetResource assetResourcesForAsset: asset];
if (![assetResources firstObject]) {
return;
}
[resource valueForKey:#"fileSize"]
PS: I don't mind a solution in Swift.
Unfortunately there is no way. You always have to first get the PHAsset then query the PHAssetResources for this asset.
In theory there would be a way (the iOS Photo Library is CoreData database), but Apple has not included any API to make such a query.

How to add "advertisements" in the UIScrollView like BBC News app?

I have created the news app which use the "UIScrollView + UIWebView" to let users to swipe through different news. However, I would like to ask whether it's possible for me to add the advertisements like the BBC News app or not, which users will see the advertisements while scrolling horizontally.
I was trying to use addSubView while scrolling (scrollViewDidScroll) but it replaces the existing content.
I realize BBC News app do the things like this,
Users swipe through the news --> After swiping a few times --> show the ads (in position 5, for instance) --> users swipe from start to the end again --> the ads appear again, but this time could be in position 3).
But I just don't know how to make it work for my app. Any clue? thanks in advance.
You probably already have a functionality, where You have stored news articles data in some array. Or You fetch data from database into some array.
Well - I think the easiest way would be just to add advert_data in this news array.
For example - You fetch from db in array:
NSArray *mArray = #[
"news1",
"news2",
"news3",
"news4",
"news5",
"news6"];
then You could simply - iterate it all through, and add advert data. For example
int counter = 0;
while (counter < [mArray count])
{
int mTmp = 3+(arc4random() % 2); // atleast 3, but up to 5?
counter +=mTmp;
[mArray insertObject:"advert" atIndex:counter];
counter +=1; //we added one object, so we need to adjust counter
}
Then simply - when You try to load corresponding data in webview, check if content is not "advert". If it is - load some random advert html from somewhere.
P.S. I guess, Your data arrays will have NSManagedObjects, instead of strings. Then You can check if [mArray objectAtIndex:i] (when You try to load it into webview)- is NSManagedObject or a string value.
I hope You understand this idea.

How do I determine if file exists in iCloud folder?

I have an iOS app that stores files in iCloud. When I start up the app I want to determine if a previous device has already uploaded any files. I start the first device and it adds the files to iCloud (I can see them in the Mobile Documents folder on my Mac). I then start the app on a second device and try to use the following NSMetadataQuery to see if any files have been uploaded, but it returns 0 results. If I keep running that query, after about 8-10 seconds it does return results.
iCloudQuery = [[NSMetadataQuery alloc] init];
iCloudQuery.searchScopes = #[NSMetadataQueryUbiquitousDataScope];
NSString *filePattern = [NSString stringWithFormat:#"*.%#", #"txt"];
iCloudQuery.predicate = [NSPredicate predicateWithFormat:#"%K LIKE %#", NSMetadataItemFSNameKey, filePattern];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(iCloudQueryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:iCloudQuery];
[iCloudQuery startQuery];
When I get the notification resultCount is 0 for the query
- (void)iCloudQueryDidFinishGathering:(NSNotification *)notification
{
NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];
NSLog(#"Found %d results from metadata query", query.resultCount);
}
Shouldn't NSMetadataQuery return a resultCount if the file exists in iCloud, even if it hasn't been downloaded? Is there any way to test if a file exists other than trying over an over and timing out after 15-30 seconds?
It might take some time for the query to retrieve the metadata from iCloud. The didFinishGathering may initially only hold the results that the device already knows about, not changes it hasn't had a chance to hear about from iCloud.
Rather than stopping and starting your NSMetadataQuery, it would be preferable to set one up and keep listening to it by also registering for:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(iCloudQueryDidUpdate:)
name:NSMetadataQueryDidUpdateNotification
object:iCloudQuery];
... and retrieve the updates when they come in. So you'll need to change your finishGathering method as well, not to stop the query, and to enableUpdates at the end.
You'll have to rethink your approach somewhat to allow for the fact that the first set of results won't necessarily know everything yet. More normally, NSMetadataQuery is used to keep watch on iCloud, with the expectation that changes generated by other devices can come along at any time - not just on app launch.
If you need to be sure you have the most up-to-date metadata for iCloud the only approach I've found reliable (on both iOS 5 and iOS 6) is to inject a small file into iCloud (usually with a distinct form of name, and named with a UUID so it is guaranteed to be unique), and then in the iCloudQueryDidUpdate: method, not considering the query results to be complete until that file is both returned by the query, and it's metadata is reporting that it is also uploaded to iCloud. Once you get this back, you can be fairly certain you have received the latest metadata from iCloud.
Check for upload in iCloudQueryDidUpdate: using:
int resultCount = [iCloudQuery resultCount];
for (int i = 0; i < resultCount; i++) {
NSMetadataItem *item = [iCloudQuery resultAtIndex:i];
BOOL isUploaded = [[item valueForAttribute:NSMetadataUbiquitousItemIsUploadedKey] boolValue];
BOOL isDownloaded = [[item valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey] boolValue];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
BOOL documentExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];
// You'll need to check isUploaded against the URL of the file you injected, rather than against any other files your query returns
}
And don't forget to delete the injected file when you are finished with it - otherwise these will mount up each time your app launches.
Edit:
The way I'd implemented these checks had an in-built delay, that once I took it out I found a case where the above wasn't entirely reliable.
I had deleted metadata items (deleted using Settings/iCloud/Storage & Backup/Manage Storage before the current run) being reported as uploaded and downloaded and existing on disk before the full metadata came back for my injected file. However, once the injected file was reported as uploaded, downloaded, and existing locally on disk, one of these deleted files was still listed in the metadata as uploaded and downloaded - BUT NOT existing on disk.
So what it looks like has been happening is the iCloud daemon hears about the pending deletion on the old data, and actually carries out the deletion BEFORE the metadata your app sees has been updated to reflect this. Crazy, huh? So, I have to update my recommendation above, to only consider query results complete if the item is reported as downloaded, uploaded AND it exists in the local folder by using the [NSFileManager fileExistsAtPath:] method. Code above edited to reflect this.
After this, all you can do is stick something like a 1 second delay in before acting on the results of the query, to be absolutely sure all metadata has had time to be received - though this is something I HATE having to do. Sticking spurious time delays into code to make it work feels a bit too close to black magic to me. And indicative that you don't really understand what is going on - though without more hooks into the processing behind iCloud, what else are we to do?

Implement my own a custom GKTurnBasedMatchmakerViewController

I'm sure there are questions similar to this, but I couldn't find an example. I'm trying to recreate the apple GKTurnBasedMatchmakerViewController for my turn based game with my own custom interface for my turn based iphone game. I'm having trouble getting all the options to display correctly so I was hoping someone had a working example. My current code is this:
-(void)getCurrentGamesFromServer {
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error) {
if(matches){
for (GKTurnBasedMatch *myMatch in matches) {
//this is, I believe, also where we need to consider invitations. available instance methods provided by GC are: acceptInviteWithCompletionHandler and declineInviteWithCompletionHandler
if(myMatch.status == 1){ //ongoing game
if([myMatch.currentParticipant.playerID isEqualToString:[GKLocalPlayer localPlayer].playerID]){ //if it's my turn
//here we need to populate the table by adding our matches to mcurrentgamesarray. after all the matches have been added, reload the tableview
NSLog(#"active game that is my turn");
[self.mCurrentGamesArray addObject:myMatch];
}
else{
NSLog(#"other turn or an invite from another player waiting to hear from you");
}
}
}
}
[_mCurrentGamesTableView reloadData];
}];
}
As you can see, the only games I'm grabbing right now are games where the status is 1 (ongoing game) and the current participant ID is my ID.
to get games that were not my turn, I tried doing games where it was not my ID, but it included invites with it and I couldn't figure out how to separate them out.
Basically, I'd like to have a section of games where it is my turn, a section where it is not my turn but games are still active, a section of completed old games, and a section for invites. does anyone have a working example, or a page they can send me to that explains the best practices for what I'm trying to do? Thanks for your help.

MGTwitterEngine + OAuth Load Users Tweets

I am using MGTwitterEngine + OAuth in my project. I would like to load all tweets from the currently logged in user into a NSMutableArray, however I am having difficulty doing so.
Upon successful login I call the following method:
[_engine getUserTimelineFor:_engine.username sinceID:0 startingAtPage:0 count:20];
And then in the delegate method I have the following:
- (void) statusesReceived:(NSArray *)statuses
forRequest:(NSString *) connectionIdentifier
{
for ( NSDictionary *dict in statuses )
{
NSLog(#"%#", [dict objectForKey:#"id" ]);
// tweets is a NSMutableArray previously allocated and initialised
[tweets addObject:[dict objectForKey:#"id" ]];
}
[_engine getUserTimelineFor:_engine.username sinceID:0 startingAtPage:0 count:20];
}
As you can see I am attempting to batch load the current users tweets, 20 at a time.
I believe the issue lies in the fact, I am not incrementing the page parameter. My issue is, I don't understand what a page actually represents, or how to get the total number of pages for any given user.
How can I load all of a users tweets in a simple, and efficient manner?
If you are using tableView to display tweet to user, you can use "Facebook's way". You can show first 20 tweets in the table view and when iPhone user scrolls down and and try to view last tweet (if you are rendering last row of your table view) then send request to get next 20 tweets and so on. So that you don't need to show page numbers and older tweets will get loaded when user wants to view them.
I hope this helps you.

Resources