Related
Right now i have a dictionary like this, it's just a example, i got A to Z:
(
{
id = 13;
name = "Roll";
firstLetter = R;
},
{
id = 14;
name = "Scroll";
firstLetter = S;
},
{
id = 16;
name = "Rock";
firstLetter = R;
},
{
id = 17;
name = "Start";
firstLetter = S;
}
)
I want to extract the dict has the same firstLetter and combine these into a NSArray object. The expected results like this:
R array:
(
{
id = 13;
name = "Roll";
firstLetter = R;
},
{
id = 16;
name = "Rock";
firstLetter = R;
}
)
and S array:
(
{
id = 14;
name = "Scroll";
firstLetter = S;
},
{
id = 17;
name = "Start";
firstLetter = S;
}
)
How to do that?
I believe the better method would be the one suggested by Saohooou
But it can be optimised as
NSArray *array = #[#{#"id": #13,#"name":#"Roll",#"firstLetter":#"R"},
#{#"id": #14,#"name":#"Scroll",#"firstLetter":#"S"},
#{#"id": #15,#"name":#"Rock",#"firstLetter":#"R"},
#{#"id": #16,#"name":#"Start",#"firstLetter":#"S"}];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[array enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
NSString *key = dict[#"firstLetter"];
NSMutableArray *tempArray = dictionary[key];
if (!tempArray) {
tempArray = [NSMutableArray array];
}
[tempArray addObject:dict];
dictionary[key] = tempArray;
}];
NSLog(#"%#",dictionary);
NSMutableDictionay *dic = [NSMutableDictionay dictionay];
for ( YourObject *obj in yourDic.allValues )
{
NSMutableArray *dateArray = dic[obj.firstLetter];
if ( !dateArray )
{
dateArray = [NSMutableArray array];
[dic setObject:dateArray forKey:obj.firstLetter];
}
[dateArray addObject:obj];
}
so dic is what you want
I assume you organized the dict as an NSArray.
NSMutableDictionary* result = [NSMutableDictionary dictionary]; // NSDictionary of NSArray
for (id entry in dict) {
NSString* firstLetter = [entry firstLetter];
// Find the group of firstLetter
NSMutableArray* group = result[firstLetter];
if (group == nil) {
// No such group --> create new a new one and add it to the result
group = [NSMutableArray array];
result[firstLetter] = group;
}
// Either group has existed, or has been just created
// Add the entry to it
[group addObject: entry];
}
result holds what you want.
try this
NSString *currentStr;
//this int is to detect currentStr
NSInteger i;
NSMutableArray* R_Array = [[NSMutableArray alloc] init];
NSMutableArray* S_Array = [[NSMutableArray alloc] init];
for (NSDictionary *myDict in MyDictArray){
NSString *tempStr = [myDict objectForKey:#"firstLetter"];
if(currentStr = nil && [currentStr isEqualToString:""]){
currentStr = tempStr;
if([currentStr isEqualToString:"R"] ){
[R_Array addObject:myDict];
i = 0;
}else{
[S_Array addObject:myDict];
i = 1;
}
}else{
if([currentStr isEqualToString:tempStr]){
(i=0)?[R_Array addObject:myDict]:[S_Array addObject:myDict];
}else{
(i=0)?[R_Array addObject:myDict]:[S_Array addObject:myDict];
}
}
}
Base on your dictionaries. There are only two type, so i just created two array and use if-else for solving the problem. if there are multy values, you can try switch-case to do it.
Lets do this
NSMutaleDictionary * speDict = [[NSMutableDictionary alloc] init];
for(i=0;i<26;i++){
switch (i){
case 0:
[speDict setObject:[NSMutableArray alloc] init] forKey:#"A"];
break;
case 1:
[speDict setObject:[NSMutableArray alloc] init] forKey:#"B"];
break;
Case 2:
[speDict setObject:[NSMutableArray alloc] init] forKey:#"C"];
break;
...........
Case 25:
[speDict setObject:[NSMutableArray alloc] init] forKey:#"Z"];
break;
}
}
for (NSDictionary *myDict in MyDictArray){
NSString *tempStr = [myDict objectForKey:#"firstLetter"];
switch (tempStr)
case A:
[self addToMySpeDictArrayWithObject:myDict andStr:temStr];
break;
case B:
[self addToMySpeDictArrayWithObject:myDict andStr:temStr];
break;
Case C:
[self addToMySpeDictArrayWithObject:myDict andStr:temStr];
break;
...........
Case Z:
[self addToMySpeDictArrayWithObject:myDict andStr:temStr];
break;
}
-(void)addToMySpeDictArrayWithObject:(NSDictionary*)_dict andStr:(NString*)_str
{
NSMutableArray *tempArray = [speDict objectForKey:_str];
[tempArray addObject:_dict];
}
then the speDict is like
A:
//all firstletter is A
myDict
myDict
myDict
B:
//all firstletter is B
myDict
myDict
.......
First of all the sample you've provided is an array of dicts (not a dict as the question notes). Now, the easiest way to query this array is by using an NSPredicate. Something like this perhaps:
NSArray *objects = ...; // The array with dicts
NSString *letter = #"S"; // The letter we want to pull out
NSPredicate *p = [NSPredicate predicateWithFormat:#"firstLetter == %#", letter];
NSArray *s = [objects filteredArrayUsingPredicate:p]; // All the 'S' dicts
If for some reason you need to group all of your objects without having to ask for a specific letter each time, you could try something like this:
// Grab all available firstLetters
NSSet *letters = [NSSet setWithArray:[objects valueForKey:#"firstLetter"]];
for (NSString *letter in letters)
{
NSPredicate *p = [NSPredicate predicateWithFormat:#"firstLetter == %#", letter];
NSArray *x = [objects filteredArrayUsingPredicate:p];
// Do something with 'x'
// For example append it on a mutable array, or set it as the object
// for the key 'letter' on a mutable dict
}
And of course you could further optimize this approach by implementing a method for filtering the array based on a letter. I hope that this makes sense.
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 am parsing an itunes rss feed with JSON but I have run into a problem. The following code is running properly for one the movieName output but I still don't get the movieSummary output.
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
allDataDictionary = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
feed = [allDataDictionary objectForKey:#"feed"];
arrayOfEntry = [feed objectForKey:#"entry"];
for (NSDictionary *dictionTitle in arrayOfEntry) {
NSDictionary *title = [dictionTitle objectForKey:#"title"];
NSString *labelTitle = [title objectForKey:#"label"];
[arrayLable addObject:labelTitle];
NSDictionary *summary = [dictionTitle objectForKey:#"summary"];
NSString *labelSummary = [summary objectForKey:#"label"];
[arraySummary addObject:labelSummary];
}
movieName.text = [arrayLable objectAtIndex:0];
movieSummary.text = [arraySummary objectAtIndex:0]; //This is not displaying
}
Here is the link that I am parsing: http://itunes.apple.com/us/rss/topmovies/limit=300/json
I run into this situation a lot. I use something like this. Replace your code
NSString *labelTitle = [title objectForKey:#"label"];
[arrayLable addObject:labelTitle];
with
NSString * labelTitle = [ [ title objectForKey:#"label" ] ifNullThenNil ] ;
[ arrayLabel addObject:labelTitle ? labelTitle : #"" ] ; // you could also use #"<unknown>" or similar instead of #""
where -ifNullThenNil is provided via category:
#implementation NSObject (IfNullThenNil)
-(id)ifNullThenNil { return self ; }
#end
#implementation NSNull (IfNullThenNil)
-(id)ifNullThenNil { return nil ; }
#end
The problem was that when I was adding the strings to the Array that it sometimes contained NULL's thus the following code helped me out
if ([[arrayName objectAtIndex:0] isKindOfClass:[NSNull class]]) {
labelName.text = #"This is NULL";
} else {
[arrayName addObject:labelName];
}
if ([[arraySummary objectAtIndex:0] isKindOfClass:[NSNull class]]) {
labelSummary.text = #"This is NULL";
} else {
[arraySummary addObject:labelSummary];
}
I have one NSMutableArray called SubList that contains 262 element. Every object is a NSDictionary!
This code works like a charm!
for (NSDictionary *element in listSub){
[cell.textLabel setText:[element objectForKey:#"title"]];
}
But if I try to use use this code I get a SIGBART error!
NSDictionary * element = [listSub objectAtIndex:[indexPath row]];
[cell.textLabel setText:[element objectForKey:#"title"]];
So what's the problem?
*EDit the problem is at this line
NSDictionary * element = [listSub objectAtIndex:[indexPath row]];
and this is the description
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet objectAtIndex:]: unrecognized selector sent to instance 0x89a8d90'
Thanks
**Edit
This is the output of listSub!
listSub: {(
{
categories = (
{
id = "user/14761688561595773457/label/Web Programming";
label = "Web Programming";
}
);
firstitemmsec = 1265885595862;
id = "feed/http://feeds.feedburner.com/FamousBloggers";
sortid = A11327DE;
title = "Famous Bloggers";
},
{
categories = (
{
id = "user/14761688561595773457/label/Hacking";
label = Hacking;
}
);
firstitemmsec = 1241258413829;
htmlUrl = "http://backtrack-italia.blogspot.com/";
id = "feed/http://feeds2.feedburner.com/BackTrackItalia";
sortid = E8A04F76;
title = "Back Track Italia";
},
{
categories = (
{
id = "user/14761688561595773457/label/Hacking";
label = Hacking;
}
);
firstitemmsec = 1245376992188;
htmlUrl = "http://www.offensive-security.com";
id = "feed/http://www.offensive-security.com/blog/feed/";
sortid = 92F57555;
title = "BackTrack Information Security Distribution";
},
{
categories = (
{
id = "user/14761688561595773457/label/iOS Developer";
label = "iOS Developer";
}
);
firstitemmsec = 1296830392306;
htmlUrl = "http://amix.dk/Main/";
id = "feed/http://feeds.feedburner.com/amixdk";
sortid = 110C52C3;
title = "amix.dk blog";
},
{
categories = (
{
id = "user/14761688561595773457/label/Apple News";
label = "Apple News";
}
);
firstitemmsec = 1285261350202;
htmlUrl = "http://www.appletvhacks.net";
id = "feed/http://www.appletvhacks.net/feed/";
sortid = 81125D2E;
title = "Apple TV Hacks";
},
{
categories = (
{
id = "user/14761688561595773457/label/Apple News";
label = "Apple News";
}
);
firstitemmsec = 1293230300220;
htmlUrl = "http://www.appletvitalia.it";
id = "feed/http://www.appletvitalia.it/feed/";
sortid = 892FE61C;
title = "Apple Tv Italia";
},
{
categories = (
{
id = "user/14761688561595773457/label/Apple News";
label = "Apple News";
}
);
firstitemmsec = 1270115980935;
htmlUrl = "http://www.appleecious.com";
id = "feed/http://feeds2.feedburner.com/appleecious";
sortid = 00B5AFC2;
title = Appleecious;
},
{
categories = (
{
id = "user/14761688561595773457/label/Hacking";
label = Hacking;
}
);
firstitemmsec = 1258495136927;
htmlUrl = "http://www.rawseo.com/news";
id = "feed/http://www.rawseo.com/news/feed/";
sortid = D6766911;
title = "A blend of programming and seo";
},
{
categories = (
{
id = "user/14761688561595773457/label/Seo e Web Marketing";
label = "Seo e Web Marketing";
}
);
firstitemmsec = 1233684720758;
htmlUrl = "http://it-adsense.blogspot.com/";
id = "feed/http://it-adsense.blogspot.com/atom.xml";
sortid = 9FB570ED;
title = "AdSense Blog-Italiano";
},
{
categories = (
);
firstitemmsec = 1277627346000;
htmlUrl = "http://aext.net";
id = "feed/http://feeds.feedburner.com/aextnet";
sortid = 70800CFE;
title = "AEXT.NET NET MAGAZINE";
},
{
categories = (
);
firstitemmsec = 1217001547735;
htmlUrl = "http://www.alessandroscoscia.it";
id = "feed/http://feeds.feedburner.com/alessandroscoscia";
sortid = 51CB8E6E;
title = "Alessandro Scoscia";
},
{
categories = (
{
id = "user/14761688561595773457/label/iOS Developer";
label = "iOS Developer";
}
);
...
I'm sure that it isn't a NSSet.
Now I post the code that I'm using to populate my array.
NSError *error;
NSURLResponse *response;
NSData *dataReply = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&response error:&error];
NSString *string = [[NSString alloc] initWithData:dataReply encoding:NSASCIIStringEncoding];
NSLog(#"testing %#",string);
SBJsonParser *vol= [[SBJsonParser alloc]init];
if (string) {
NSArray *feeds = [vol objectWithString:string error:nil];
NSDictionary *results = [string JSONValue];
NSArray *subs = [results valueForKey:#"subscriptions"];
for (NSDictionary *iscrizione in subs){
[subscriptions addObject:iscrizione];
}
This is part of another class and also in this part of my code if i try to do something like this:
NSDictionary * element = [listSub objectAtIndex:0];
NSLog([element objectForKey:#"title"]);
I get the same error while if I try a loop like this
for (NSDictionary *element in listSub){
[cell.textLabel setText:[element objectForKey:#"title"]];
}
Everythings works fine without NSSet errors. This make me crazy!
The problem is probably that listSub was not properly retained and therefore released already. The memory is now re-used and the object at that address is now an __NSCFSet, which does not respond to objectAtIndex:.
Note that autoreleased objects returned by a convenience class method like [NSMutableArray arrayWithXXX] should be retained and only released when not needed anymore. The best way to be sure they are retained is to declare a #property(retain) NSArray *subList, #synthesize it and use self.subList in all cases except perhaps in dealloc.
Addition
It was suggested that the method that created subList returned a set and not an array. If that were the case, the simple loop at the begining of the answer would not function either. That is why I find it very unlikely that subList was originally an NSSet, and much more likely that the array was overreleased and dealloced and the memory re-used for the NSSet.
Please do what people who want to help you ask of you.
Add the line
NSLog(#"listSub: %#", listSub);
at the place where listSub is set and also right before the error line. Then look in the error log what it displays and tell us.
Have you tried:
[cell.textLabel setText:[element objectForKey:#"title"]];
What is that "lol"??
listSub isn't an array, it's an NSSet.
problem is here
[cell.textLabel setText:[NSString stringWithFormat:[element objectForKey:#"title"]]];
when you are call stringWithFormat then you need to give format in string literal form like this
[cell.textLabel setText:[NSString stringWithFormat:#"%#",[element objectForKey:#"title"]]];
or direct set the text with out calling stringWithFormat:
your array got released some where, so for solving the problem you need to call retain on tyour array at right place.
Just try this one.
NSDictionary * element = [listSub objectAtIndex:[indexPath row]];
[cell.textLabel setText:[NSString stringWithFormat:#"%#",[element objectForKey:#"title"]]];
Or try this as you have edit your question, Edit
NSDictionary *element = [[NSDictionary alloc] initWithDictionary:[listSub objectAtIndex:indexPath.row]];
[cell.textLabel setText:[element objectForKey:#"title"]];
[element release];
I have a URL like myApp://action/1?parameter=2&secondparameter=3
With the property query I get following part of my URL
parameter=2&secondparameter=3
Is there any way easy to put this in a NSDictionary or an Array?
Thx a lot
You can use queryItems in URLComponents.
When you get this property’s value, the NSURLComponents class parses the query string and returns an array of NSURLQueryItem objects, each of which represents a single key-value pair, in the order in which they appear in the original query string.
Swift
let url = "http://example.com?param1=value1¶m2=param2"
let queryItems = URLComponents(string: url)?.queryItems
let param1 = queryItems?.filter({$0.name == "param1"}).first
print(param1?.value)
Alternatively, you can add an extension on URL to make things easier.
extension URL {
var queryParameters: QueryParameters { return QueryParameters(url: self) }
}
class QueryParameters {
let queryItems: [URLQueryItem]
init(url: URL?) {
queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? []
print(queryItems)
}
subscript(name: String) -> String? {
return queryItems.first(where: { $0.name == name })?.value
}
}
You can then access the parameter by its name.
let url = "http://example.com?param1=value1¶m2=param2"
print(url.queryParameters["param1"])
I had reason to write some extensions for this behavior that might come in handy. First the header:
#import <Foundation/Foundation.h>
#interface NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat;
- (NSString *)stringByEncodingURLFormat;
- (NSMutableDictionary *)dictionaryFromQueryComponents;
#end
#interface NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents;
#end
#interface NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents;
#end
These methods extend NSString, NSURL, and NSDictionary, to allow you to convert to and from query components strings and dictionary objects containing the results.
Now the related .m code:
#import "XQueryComponents.h"
#implementation NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat
{
NSString *result = [self stringByReplacingOccurrencesOfString:#"+" withString:#" "];
result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
- (NSString *)stringByEncodingURLFormat
{
NSString *result = [self stringByReplacingOccurrencesOfString:#" " withString:#"+"];
result = [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
- (NSMutableDictionary *)dictionaryFromQueryComponents
{
NSMutableDictionary *queryComponents = [NSMutableDictionary dictionary];
for(NSString *keyValuePairString in [self componentsSeparatedByString:#"&"])
{
NSArray *keyValuePairArray = [keyValuePairString componentsSeparatedByString:#"="];
if ([keyValuePairArray count] < 2) continue; // Verify that there is at least one key, and at least one value. Ignore extra = signs
NSString *key = [[keyValuePairArray objectAtIndex:0] stringByDecodingURLFormat];
NSString *value = [[keyValuePairArray objectAtIndex:1] stringByDecodingURLFormat];
NSMutableArray *results = [queryComponents objectForKey:key]; // URL spec says that multiple values are allowed per key
if(!results) // First object
{
results = [NSMutableArray arrayWithCapacity:1];
[queryComponents setObject:results forKey:key];
}
[results addObject:value];
}
return queryComponents;
}
#end
#implementation NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents
{
return [[self query] dictionaryFromQueryComponents];
}
#end
#implementation NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents
{
NSString *result = nil;
for(__strong NSString *key in [self allKeys])
{
key = [key stringByEncodingURLFormat];
NSArray *allValues = [self objectForKey:key];
if([allValues isKindOfClass:[NSArray class]])
for(__strong NSString *value in allValues)
{
value = [[value description] stringByEncodingURLFormat];
if(!result)
result = [NSString stringWithFormat:#"%#=%#",key,value];
else
result = [result stringByAppendingFormat:#"&%#=%#",key,value];
}
else {
NSString *value = [[allValues description] stringByEncodingURLFormat];
if(!result)
result = [NSString stringWithFormat:#"%#=%#",key,value];
else
result = [result stringByAppendingFormat:#"&%#=%#",key,value];
}
}
return result;
}
#end
Something like that:
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [url componentsSeparatedByString:#"&"]) {
NSArray *elts = [param componentsSeparatedByString:#"="];
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
Note : This is sample code. All error cases are not managed.
Try this ;)!
NSString *query = #"parameter=2&secondparameter=3"; // replace this with [url query];
NSArray *components = [query componentsSeparatedByString:#"&"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
for (NSString *component in components) {
NSArray *subcomponents = [component componentsSeparatedByString:#"="];
[parameters setObject:[[subcomponents objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
forKey:[[subcomponents objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
All previous posts do not do the url encoding properly. I would suggest the following methods:
+(NSString*)concatenateQuery:(NSDictionary*)parameters {
if([parameters count]==0) return nil;
NSMutableString* query = [NSMutableString string];
for(NSString* parameter in [parameters allKeys])
[query appendFormat:#"&%#=%#",[parameter stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet],[[parameters objectForKey:parameter] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]];
return [[query substringFromIndex:1] copy];
}
+(NSDictionary*)splitQuery:(NSString*)query {
if([query length]==0) return nil;
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
for(NSString* parameter in [query componentsSeparatedByString:#"&"]) {
NSRange range = [parameter rangeOfString:#"="];
if(range.location!=NSNotFound)
[parameters setObject:[[parameter substringFromIndex:range.location+range.length] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:[[parameter substringToIndex:range.location] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
else [parameters setObject:[[NSString alloc] init] forKey:[parameter stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
return [parameters copy];
}
According to the already very clean answer of Onato I wrote an extension for NSURL in Swift where you can get a query param like this:
e.g. the URL contains the pair param=some_value
let queryItem = url.queryItemForKey("param")
let value = queryItem.value // would get String "someValue"
The extension looks like:
extension NSURL {
var allQueryItems: [NSURLQueryItem] {
get {
let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
let allQueryItems = components.queryItems!
return allQueryItems as [NSURLQueryItem]
}
}
func queryItemForKey(key: String) -> NSURLQueryItem? {
let predicate = NSPredicate(format: "name=%#", key)!
return (allQueryItems as NSArray).filteredArrayUsingPredicate(predicate).first as? NSURLQueryItem
}
}
Here is the extension in swift:
extension NSURL{
func queryParams() -> [String:AnyObject] {
var info : [String:AnyObject] = [String:AnyObject]()
if let queryString = self.query{
for parameter in queryString.componentsSeparatedByString("&"){
let parts = parameter.componentsSeparatedByString("=")
if parts.count > 1{
let key = (parts[0] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let value = (parts[1] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
if key != nil && value != nil{
info[key!] = value
}
}
}
}
return info
}
}
The preferred way to deal with URLs is now NSURLComponents. In particular the queryItems property which returns an NSArray of params.
If you want the params in a NSDictionary, here's a method:
+(NSDictionary<NSString *, NSString *>*)queryParamsFromURL:(NSURL*)url
{
NSURLComponents* urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSMutableDictionary<NSString *, NSString *>* queryParams = [NSMutableDictionary<NSString *, NSString *> new];
for (NSURLQueryItem* queryItem in [urlComponents queryItems])
{
if (queryItem.value == nil)
{
continue;
}
[queryParams setObject:queryItem.value forKey:queryItem.name];
}
return queryParams;
}
Caveat: URLs can have repeated params, but the dictionary will only contain the last value of any duplicated param. If that is undesirable, use the queryItems array directly.
For those using Bolts Framework you can use:
NSDictionary *parameters = [BFURL URLWithURL:yourURL].inputQueryParameters;
Remember to import:
#import <Bolts/BFURL.h>
If you happen to have Facebook SDK in your project, you also have Bolts. Facebook is using this framework as a dependency.
Swift 2.1
Oneliner:
"p1=v1&p2=v2".componentsSeparatedByString("&").map {
$0.componentsSeparatedByString("=")
}.reduce([:]) {
(var dict: [String:String], p) in
dict[p[0]] = p[1]
return dict
}
// ["p1": "v1", "p2": "v2"]
Used as an extension on NSURL:
extension NSURL {
/**
* URL query string as dictionary. Empty dictionary if query string is nil.
*/
public var queryValues : [String:String] {
get {
if let q = self.query {
return q.componentsSeparatedByString("&").map {
$0.componentsSeparatedByString("=")
}.reduce([:]) {
(var dict: [String:String], p) in
dict[p[0]] = p[1]
return dict
}
} else {
return [:]
}
}
}
}
Example:
let url = NSURL(string: "http://example.com?p1=v1&p2=v2")!
let queryDict = url.queryValues
// ["p1": "v1", "p2": "v2"]
Please note, if using OS X 10.10 or iOS 8 (or later), it's probably better to use NSURLComponents and the queryItems property and create the dictionary from the NSURLQueryItems directly.
Here's a NSURLComponents based NSURL extension solution:
extension NSURL {
/// URL query string as a dictionary. Empty dictionary if query string is nil.
public var queryValues : [String:String] {
get {
guard let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false) else {
return [:]
}
guard let queryItems = components.queryItems else {
return [:]
}
var result:[String:String] = [:]
for q in queryItems {
result[q.name] = q.value
}
return result
}
}
}
A footnote to the NSURL extension is that it's actually possible in Swift to give the property the same name as the existing string property—query. I didn't know until I tried it, but the polymorphism in Swift lets you differ only on the return type. So if the extended NSURL property is public var query: [String:String] it works. I didn't use this in the example as I find it a little bit crazy, but it does work ...
I published a simple class doing the job under MIT:
https://github.com/anegmawad/URLQueryToCocoa
With it you can have arrays and objects in the query, which are collected and glued together
For Example
users[0][firstName]=Amin&users[0][lastName]=Negm&name=Devs&users[1][lastName]=Kienle&users[1][firstName]=Christian
will become:
#{
name : #"Devs",
users :
#[
#{
firstName = #"Amin",
lastName = #"Negm"
},
#{
firstName = #"Christian",
lastName = #"Kienle"
}
]
}
You can think of it as a URL query counterpart of NSJSONSerializer.
It looks that you are using it to process incoming data from another iOS application. If so, this is what I use for the same purpose.
Initial call (e.g. in external application):
UIApplication *application = [UIApplication sharedApplication];
NSURL *url = [NSURL URLWithString:#"myApp://action/1?parameter=2&secondparameter=3"];
if ([application canOpenURL:url]) {
[application openURL:url];
NSLog(#"myApp is installed");
} else {
NSLog(#"myApp is not installed");
}
Method to extract QueryString data from NSURL and save as NSDictionary:
-(NSDictionary *) getNSDictionaryFromQueryString:(NSURL *)url {
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
NSRange needle = [url.absoluteString rangeOfString:#"?" options:NSCaseInsensitiveSearch];
NSString *data = nil;
if(needle.location != NSNotFound) {
NSUInteger start = needle.location + 1;
NSUInteger end = [url.absoluteString length] - start;
data = [url.absoluteString substringWithRange:NSMakeRange(start, end)];
}
for (NSString *param in [data componentsSeparatedByString:#"&"]) {
NSArray *keyvalue = [param componentsSeparatedByString:#"="];
if([keyvalue count] == 2){
[result setObject:[keyvalue objectAtIndex:1] forKey:[keyvalue objectAtIndex:0]];
}
}
return result;
}
Usage:
NSDictionary *result = [self getNSDictionaryFromQueryString:url];
This class is a nice solution for url parsing.
.h file
#interface URLParser : NSObject {
NSArray *variables;
}
#property (nonatomic, retain) NSArray *variables;
- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
.m file
#import "URLParser.h"
#implementation URLParser
#synthesize variables;
- (id) initWithURLString:(NSString *)url{
self = [super init];
if (self != nil) {
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
NSMutableArray *vars = [NSMutableArray new];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
[vars addObject:[tempString copy]];
}
self.variables = vars;
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
for (NSString *var in self.variables) {
if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:#"="]]) {
NSString *varValue = [var substringFromIndex:[varName length]+1];
return varValue;
}
}
return nil;
}
#end
Hendrik wrote a nice example for extension in this question, however I had to re-write it to not use any objective-c library methods. Using NSArray in swift is not the correct approach.
This is the result, all swift and a bit more safe. The usage example will be less lines of code with Swift 1.2.
public extension NSURL {
/*
Set an array with all the query items
*/
var allQueryItems: [NSURLQueryItem] {
get {
let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
if let allQueryItems = components.queryItems {
return allQueryItems as [NSURLQueryItem]
} else {
return []
}
}
}
/**
Get a query item form the URL query
:param: key The parameter to fetch from the URL query
:returns: `NSURLQueryItem` the query item
*/
public func queryItemForKey(key: String) -> NSURLQueryItem? {
let filteredArray = filter(allQueryItems) { $0.name == key }
if filteredArray.count > 0 {
return filteredArray.first
} else {
return nil
}
}
}
Usage:
let queryItem = url.queryItemForKey("myItem")
Or, more detailed usage:
if let url = NSURL(string: "http://www.domain.com/?myItem=something") {
if let queryItem = url.queryItemForKey("myItem") {
if let value = queryItem.value {
println("The value of 'myItem' is: \(value)")
}
}
}
try this:
-(NSDictionary *)getUrlParameters:(NSString *)url{
NSArray *justParamsArr = [url componentsSeparatedByString:#"?"];
url = [justParamsArr lastObject];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [url componentsSeparatedByString:#"&"]) {
NSArray *elts = [param componentsSeparatedByString:#"="];
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
return params;
}
Fairly compact approach:
func stringParamsToDict(query: String) -> [String: String] {
let params = query.components(separatedBy: "&").map {
$0.components(separatedBy: "=")
}.reduce(into: [String: String]()) { dict, pair in
if pair.count == 2 {
dict[pair[0]] = pair[1]
}
}
return params
}
Most robust solution if you are using a URL to pass data from the web app to the phone and you want to pass arrays, numbers, strings, ...
JSON encode your object in PHP
header("Location: myAppAction://".urlencode(json_encode($YOUROBJECT)));
And JSON decode the result in iOS
NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];