I'm trying to simply search for videos using a query, which is working perfectly using the below code.
// Create a service object for executing queries
GTLServiceYouTube *service = [[GTLServiceYouTube alloc] init];
// Services which do not require sign-in may need an API key from the
// API Console
service.APIKey = #"AIzaSy...";
// Create a query
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"id,snippet"];
query.maxResults = 10;
query.q = searchBar.text;
query.videoEmbeddable = #"true";
query.type = #"video";
//query.country = #"US";
// Execute the query
GTLServiceTicket *ticket = [service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
// This callback block is run when the fetch completes
if (error == nil) {
GTLYouTubeSearchListResponse *products = object;
[videoArray removeAllObjects];
// iteration of items and subscript access to items.
for (GTLYouTubeSearchResult *item in products) {
NSMutableDictionary *dictionary = [item JSONValueForKey:#"id"];
NSLog(#"%#", [dictionary objectForKey:#"videoId"]);
YoutubeVideo *video = [[YoutubeVideo alloc]init];
[video setLblTitle:item.snippet.title];
//Get youtube video image
[video setImgIconURL:[NSURL URLWithString:item.snippet.thumbnails.defaultProperty.url]];
[video setLblVideoURL:[dictionary objectForKey:#"videoId"]];
[video setLblChannelTitle:item.snippet.channelTitle];
[videoArray addObject:video];
}
reloadData = YES;
[tableView reloadData];
//Download images asynchronously
[NSThread detachNewThreadSelector:#selector(downloadImages)
toTarget:self
withObject:nil];
}else{
NSLog(#"Error: %#", error.description);
}
}];
However, now I'd like to display certain information about the video. Some of this information I can get out of
item.snippet
But I also need to get the video duration, and number of views. How can I get them using Youtube API 3.0?? I also had an idea to try using GData just for this, but it literally triples the load time to use
NSString *JSONString = [NSString stringWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://gdata.youtube.com/feeds/api/videos/%#?v=2&alt=json", [video lblVideoURL]]] encoding:NSUTF8StringEncoding error:nil ];
How do I get the duration of the video, plus the number of views the video has?
Search query only accept ID and Snippet as parts. If you change to Video List Query you can include other parts, but you have to use one of the filters.
I think you'll have to get the video ID with the search query and do another query (Now a video query) filtering by ID (the Id you got), than you can get all other information of the videos you searched.
The problem is i'm having trouble getting the video ID, i think the API use the word "identifier" instead of "id" because it's a reserved word of objective-c.
Edit: Yeah, it was just a matter of time, just request my GTLYoutubeSearchResponse.JSON, an manipulated it as i wanted.
FIRST QUERY:
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"id,snippet"];
query.maxResults = 10;
query.q = #"iphone";
query.fields = #"items(id,snippet)";
query.order = #"viewCount";
//query.channelId = #"UCsnbNwitAF9BzjjdMfRyK2g";//Kavaco
[appDelegate.service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
id object,
NSError *error) {
if (error == nil) {
appDelegate.videos = object;
[self performSegueWithIdentifier:#"videoList" sender:self];
}
else {
NSLog(#"%#", error.description);
}
}];
SECOND QUERY: In my TableViewController, inside my cellForRowAtIndexPath i do another query for each video i found. Be sure to request only the variables you need to avoid spending your credits, in my case i requested only viewCount.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell" forIndexPath:indexPath];
GTLYouTubeVideo *video = appDelegate.videos[indexPath.row];
NSMutableDictionary *videoIdJson = [video.JSON objectForKey:#"id"];
NSString *videoId = [videoIdJson objectForKey:#"videoId"];
cell.textLabel.text = video.snippet.title;
GTLQueryYouTube *query = [GTLQueryYouTube queryForVideosListWithPart:#"statistics"];
query.identifier = videoId;
query.maxResults = 1;
query.fields = #"items/statistics(viewCount)";
[appDelegate.service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
id object,
NSError *error) {
if (error == nil) {
GTLYouTubeVideoListResponse *detalhe = object;
NSMutableDictionary *responseJSON = detalhe.JSON;
NSArray *tempArray = [responseJSON objectForKey:#"items"];
NSMutableDictionary *items = tempArray[0];
NSMutableDictionary *statistics = [items objectForKey:#"statistics"];
_views = [[NSString alloc] initWithFormat:#"Views: %#",[statistics objectForKey:#"viewCount"]];
cell.detailTextLabel.text = _views;
}
else {
NSLog(#"%#", error.description);
}
}];
cell.detailTextLabel.text = _views;
return cell;
}
Hope it helps.
Collect the id from search API and do another video list API call is the proper way to do what you want to achieve. The video list API call can put multiple video ids separate by comma in the same call. The extra call shouldn't consider exhausting because this is expected behavior on API v3:
Project Member #1 je...#google.com
That's the expected behavior, and not likely to change. Since the
search.list() method can return channels, videos, and playlists, only
properties that make sense for all of those resource types are
returned in the search responses. If you need to obtain any other
properties, making a follow-up request to, e.g., videos.list() is
required. Note that you can pass in up to 50 video ids to
videos.list(), so you can effectively look up an entire page's worth
of search.list() results in a single video.list() call.
If you try https://developers.google.com/youtube/v3/docs/videos/list#try-it , you set contentDetails,statistics as the part, you should able to get the following result:
"contentDetails": {
"duration": "PT20M38S",
"dimension": "2d",
"definition": "hd",
"caption": "false",
"licensedContent": false
},
"statistics": {
"viewCount": "191",
"likeCount": "7",
"dislikeCount": "0",
"favoriteCount": "0",
"commentCount": "0"
}
PT20M38S means 20 minutes and 38 seconds, based on ISO 8601(http://en.wikipedia.org/wiki/ISO_8601)
The best way for make this is:
if (!service) {
service = [[GTLServiceYouTube alloc] init];
service.shouldFetchNextPages = YES;
service.shouldFetchInBackground = YES;
service.retryEnabled = YES;
service.APIKey = #"AIzaSyDSO2JPnM_r9VcDrDJJs_d_7Li2Ttk2AuU";
}
[youtubeList removeAllObjects];
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"id"];
query.maxResults = 50;
query.q = withText;
query.fields = #"items(id)";
query.order = #"viewCount";
query.type = #"video";
// query.videoDuration = #"long";//any-long-medium-short
__block NSInteger incrementRequest = 0;
[service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
if (error) {
NSLog(#"Error is!! = %#", error.localizedDescription);
return;
}
GTLYouTubeVideoListResponse *idsResponse = object;
for (GTLYouTubeVideoListResponse *videoInfo in object) {
[youtubeList addObject:videoInfo.JSON];
GTLQueryYouTube *query2 = [GTLQueryYouTube queryForVideosListWithIdentifier:[[videoInfo.JSON valueForKey:#"id"] valueForKey:#"videoId"] part:#"id,contentDetails,snippet,statistics"];
query2.maxResults = 1;
query2.fields = #"items(id,contentDetails,snippet,statistics)";
query2.order = #"viewCount";
[service executeQuery:query2 completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
if (error) {
NSLog(#"Error is!! = %#", error.localizedDescription);
return;
}
GTLYouTubeVideoListResponse *detalhe = object;
for (NSMutableDictionary *tmpDict in youtubeList) {
if ([[[tmpDict valueForKey:#"id"] valueForKey:#"videoId"] isEqualToString:[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"id"]]) {
[tmpDict removeObjectForKey:#"id"];
//condition personal
if (![Utils parseISO8601TimeIsGrater30:[[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"contentDetails"] valueForKey:#"duration"]]) {
BOOL isBlockedInUs = NO;
for (NSString *countryRestric in [[[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"contentDetails"] valueForKey:#"regionRestriction"] valueForKey:#"blocked"]) {
if ([countryRestric isEqualToString:#"US"]) {
isBlockedInUs = YES;
break;
}
}
if (!isBlockedInUs) {
[tmpDict addEntriesFromDictionary:detalhe.JSON];
[tmpDict setValue:[[[[detalhe.JSON valueForKey:#"items"] objectAtIndex:0] valueForKey:#"snippet"] valueForKey:#"publishedAt"] forKey:#"publishedAt"];
} else {
[youtubeList removeObject:tmpDict];
}
} else {
[youtubeList removeObject:tmpDict];
}
break;
}
}
incrementRequest ++;
if ([idsResponse.items count] == incrementRequest) {
//Finish
[self.tableView reloadData];
}
}];
}
}];
I've been working on adding Core Data into my iPhone app, and I've been running into some very frustrating issues. When I call save on my context, the save returns successfully, however no data is getting added to my database(I am running this on the simulator and looking at the SQLITE file to check).
I am using the MYDocumentHandler class from this post to use a single UIManagedDocument across multiple classes. I run the code in my AppDelegate as follows:
if (!self.document) {
[[MYDocumentHandler sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.document = document;
self.context = document.managedObjectContext;
[self loadView];
}];
}
The loadView method setups up my view controllers once the document has been returned. In my view controllers that use the Core Data I again use something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
if(!self.document){
[[MYDocumentHandler sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.document = document;
self.context = document.managedObjectContext;
[self loadAll];
}];
}
}
Where the loadAll method setups everything for the view. When I try to save my data, I use the following:
for (int i = 0; i < [jsonArray count]; i++) {
NSDictionary *dictionary = [jsonArray objectAtIndex:i];
ProjectObject *tempProject = [[ProjectObject alloc] initWithDict:dictionary andETag:etag];
[tempAllProjects addObject:[Projects newProject:tempProject withContext:self.context]];
[tempProject release];
}
[self saveWithContext:self.context];
My saveWithContext method looks like this:
- (BOOL) saveWithContext:(NSManagedObjectContext *)context{
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
DLog(#"Unresolved error %#, %#", error, [error userInfo]);
} else{
DLog(#"save was successful");
return YES;
}
}else{
DLog(#"context is nil");
return NO;
}
}
I always get the save was successful message, and I get the proper messages from the MYDocumentHandler file. Unfortunately, the data is simply not making it to the database. The data is definitely stored in the context, but its not going to the database. Any ideas?
Edit:
Here is the code where I create the Entities:
+ (Projects *) newProject:(ProjectObject *)project withContext:(NSManagedObjectContext *)context
{
Projects *newProject = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Projects"];
request.predicate = [NSPredicate predicateWithFormat:#"project_id = %#", project.project_id];
NSArray *results = [context executeFetchRequest:request error:nil];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([results count] > 0){
newProject = [results objectAtIndex:0];
newProject.account_id = [NSString stringWithFormat:#"%#", [defaults objectForKey:#"account_id"]];
} else {
newProject = [NSEntityDescription insertNewObjectForEntityForName:#"Projects" inManagedObjectContext:context];
}
newProject.account_id = [NSString stringWithFormat:#"%#", [defaults objectForKey:#"account_id"]];
newProject.project_id = project.project_id;
newProject.name = project.name;
newProject.project_description = project.description;
newProject.updated_at = project.updated_at;
newProject.starred = project.starred;
newProject.etag = project.etag;
newProject.synced = [self hasConnection] ? [NSNumber numberWithInt:1] : [NSNumber numberWithInt:0];
return newProject;
}
I found the solution in this post. Not sure if this is the best way to handle it. If there is a better option, please let me know.
You are not saving Manage object context after changing it. I have added the lines into your code.
Please check if it works now.
I am implementing a search bar controller to search a table view. The below method code which performs the search is crashing with the error "-[__NSArrayM rangeOfString:options:]: unrecognized selector sent to instance 0x65558e0'
The locationInfo array is an array containing 26 arrays, each of which contains a number of objects made up of strings.
Can anyone suggest why the code is crashing ?
Thank you.
- (void)handleSearchForTerm:(NSString *)searchTerm
{
[self setSavedSearchTerm:searchTerm];
if ([self searchResults] == nil)
{
NSMutableArray *array = [[NSMutableArray alloc] init];
[self setSearchResults:array];
[array release], array = nil;
}
[[self searchResults] removeAllObjects];
if ([[self savedSearchTerm] length] != 0)
{
for (NSString *currentString in [self locationInfo])
{
if ([currentString rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[[self searchResults] addObject:currentString];
}
}
}
}
As you stated in question that "locationInfo" is array containing 26 arrays,
so,
currentString in [self locationInfo] will return an array only so try to write something like below :
for (NSArray *currentArray in [self locationInfo])
{
for (NSString *currentString in currentArray)
{
if ([currentString rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[[self searchResults] addObject:currentString];
}
}
}
or something like this
Based on the error you're getting, it seems [self locationInfo] returns an array (NSArray) and not a string (NSString) as you're expecting.
I am developing an application which upload number of photos and xml file in one attempt. I have two core data table with one-many(jobs & photos) relation. One job may contain number of photos. Once all the photos has uploaded I need to upload xml file which contain photos details. I need to keep track on which photo has upload successfully and update the jobs table's status field as well as photo status. Following code illustrate that.
This works some time. Some time this is not updating jobs table. I do appreciate is anyone can let me know what is wrong with following code.
NSMutableArray *photosForJob=[[NSMutableArray alloc] initWithArray:[fetchedJob.photos allObjects]];
self.manageObjectForJobs = fetchedJob;
__block int count = 0;
dispatch_group_async(group, queue, ^{
for (int i = 0; i < [photosForJob count]; i++)
{
Photos *ph = [photosForJob objectAtIndex:i];
if ([ph.status compare:[NSNumber numberWithBool:NO]] == NSOrderedSame)
{
NSMutableArray *responseArray = [self filePosting:ph.photoName];
self.manageObjectForPhotos = ph;
if ([[responseArray objectAtIndex:0] isEqual:#"200"] && [[responseArray objectAtIndex:1] isEqualToString:ph.photoName])
{
[self.manageObjectForPhotos setValue:[NSNumber numberWithBool:YES] forKey:#"status"];
count++;
}
}
else{
count++;
}
}
if (count == [photosForJob count])
{
if ([status compare:[NSNumber numberWithBool:NO]] == NSOrderedSame)
{
NSMutableArray *responseArray = [self filePosting:xmlFile];
if ([[responseArray objectAtIndex:0] isEqual:#"200"] && [[responseArray objectAtIndex:1] isEqualToString:xmlFile]){
[self.manageObjectForJobs setValue:[NSNumber numberWithBool:YES] forKey:#"status"];
}
}
}
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Job status did updat.... : %#", [error description]);
}
else{
[UIApplication sharedApplication].applicationIconBadgeNumber = [self fetchJobsForBadge];
[photosForJob removeAllObjects];
count = 0;
}
});
Many Thanks
I am attempting to implement searching in a UITableView. When searching, it appears that the correct number of results are returned, but I am receiving entries from the original stories array in the results, rather than searchResults. I can see that the searchResults array should be the data source, but haven't been able to figure out after tons of searching quite how to pull it off with an array of NSDictionaries. Any help is appreciated.
- (void)handleSearchForTerm:(NSString *)searchTerm {
[self setSavedSearchTerm:searchTerm];
if ([self searchResults] == nil)
{
NSMutableArray *array = [[NSMutableArray alloc] init];
[self setSearchResults:array];
[array release], array = nil;
}
[[self searchResults] removeAllObjects];
if ([[self savedSearchTerm] length] != 0)
{
for (NSDictionary *currentItem in [self stories])
{
if ([[currentItem objectForKey:#"title"] rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[[self searchResults] addObject:currentItem];
}
}
}
}
[tableView isEqual:self.searchDisplayController.searchResultsTableView] is also another alternative to making and managing your own BOOL isFiltering; variable
use NSPredicate for filtering
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"self.title MATCHES %#",searchTerm];
Suppose that your original array is "originalArray" so to get the filtered array use this make two more global variables
NSArray* filteredArray;
BOOL isFiltering;
Now in search bar delegate method do following
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"self.title MATCHES %#",searchTerm];
filteredArray = [[originalArray filteredArrayUsingPredicate:predicate] retain];
}
Now you need to change l'll bit your table view delegate and data source, .... for all the places where you are using
NSDictionary *currentString = [originalArray objectAtIndex:indexPath.row];
use following
NSDictionary *currentString;
if(isFiltering)
currentString = [originalArray objectAtIndex:indexPath.row];
else
currentString = [filteredArray objectAtIndex:indexPath.row];