66

Let's say I have an NSURL? Whether or not it already has an empty query string, how do I add one or more parameters to the query of the NSURL? I.e., does anyone know of an implementation of this function?

- (NSURL *)URLByAppendingQueryString:(NSString *)queryString

So that it satisfies this NSURL+AdditionsSpec.h file:

#import "NSURL+Additions.h"
#import "Kiwi.h"

SPEC_BEGIN(NSURL_AdditionsSpec)

describe(@"NSURL+Additions", ^{
    __block NSURL *aURL;

    beforeEach(^{
        aURL = [[NSURL alloc] initWithString:@"http://www.example.com"];
        aURLWithQuery = [[NSURL alloc] initWithString:@"http://www.example.com?key=value"];
    });

    afterEach(^{
        [aURL release];
        [aURLWithQuery release];
    });

    describe(@"-URLByAppendingQueryString:", ^{
        it(@"adds to plain URL", ^{
            [[[[aURL URLByAppendingQueryString:@"key=value&key2=value2"] query] should]
             equal:@"key=value&key2=value2"];
        });

        it(@"appends to the existing query sting", ^{
            [[[[aURLWithQuery URLByAppendingQueryString:@"key2=value2&key3=value3"] query] should]
             equal:@"key=value&key2=value2&key3=value3"];
        });
    });
});

SPEC_END
ma11hew28
  • 121,420
  • 116
  • 450
  • 651

8 Answers8

76

Since iOS 7 you can use NSURLComponents that is very simple to use. Take a look on these examples:

Example 1

NSString *urlString = @"https://mail.google.com/mail/u/0/?shva=1#inbox";
NSURLComponents *components = [[NSURLComponents alloc] initWithString:urlString];

NSLog(@"%@ - %@ - %@ - %@", components.scheme, components.host, components.query, components.fragment);

Example 2

NSString *urlString = @"https://mail.google.com/mail/u/0/?shva=1#inbox";
NSURLComponents *components = [[NSURLComponents alloc] initWithString:urlString];

if (components) {
    //good URL
} else {
    //bad URL
}

Example 3

NSURLComponents *components = [NSURLComponents new];
[components setScheme:@"https"];
[components setHost:@"mail.google.com"];
[components setQuery:@"shva=1"];
[components setFragment:@"inbox"];
[components setPath:@"/mail/u/0/"];

[self.webview loadRequest:[[NSURLRequest alloc] initWithURL:[components URL]]];

But you can do many other things with NSURLComponents take a look on NSURLComponents class reference on Apple documentation or on this link: http://nshipster.com/nsurl/

Salmo
  • 1,788
  • 1
  • 18
  • 29
45

Here's an implementation that passes your specs:

@implementation NSURL (Additions)

- (NSURL *)URLByAppendingQueryString:(NSString *)queryString {
    if (![queryString length]) {
        return self;
    }

    NSString *URLString = [[NSString alloc] initWithFormat:@"%@%@%@", [self absoluteString],
                           [self query] ? @"&" : @"?", queryString];
    NSURL *theURL = [NSURL URLWithString:URLString];
    [URLString release];
    return theURL;
}

@end

And here is an implementation for NSString:

@implementation NSString (Additions)

- (NSURL *)URLByAppendingQueryString:(NSString *)queryString {
    if (![queryString length]) {
        return [NSURL URLWithString:self];
    }

    NSString *URLString = [[NSString alloc] initWithFormat:@"%@%@%@", self,
                           [self rangeOfString:@"?"].length > 0 ? @"&" : @"?", queryString];
    NSURL *theURL = [NSURL URLWithString:URLString];
    [URLString release];
    return theURL;
}

// Or:

- (NSString *)URLStringByAppendingQueryString:(NSString *)queryString {
    if (![queryString length]) {
        return self;
    }
    return [NSString stringWithFormat:@"%@%@%@", self,
            [self rangeOfString:@"?"].length > 0 ? @"&" : @"?", queryString];
}

@end
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • 5
    Excellent answer, but to be completely safe you should use [queryString stringByAddingPercentEscapesUsingEncoding:] when building URLString or you will get nil for the result URL in the case where the queryString is not already 'url friendly' (ex. when the parameter value has a space in it). – Christopher King Feb 16 '13 at 21:38
  • 1
    Doesn't work for urls with hashtag (#), where the new query string params should be appended before the hashtag. – Tomer Shiri Apr 29 '14 at 06:22
  • 1
    When using ARC [URLString release]; is not needed. – Mark Gaensicke Jun 03 '15 at 01:12
  • 1
    You should now really use `NSURLComponents` as suggested in Salmo's answer below. – Jarsen Aug 11 '15 at 17:05
  • 1
    I don't think this is really a good solution, RFC defines a complex format on `URL`, for example, this solution will break the `fragment` components of the source URL. – Itachi Oct 28 '21 at 09:22
  • Thanks for all of your comments. I've edited and accepted [this answer](https://stackoverflow.com/a/44041883/242933) that uses `NSURLComponents`. – ma11hew28 Oct 29 '21 at 13:44
32

The iOS8+ modern way

adding (or replacing 'ref' value if exists) ref=impm to url which is on min60.com

if ([[url host] hasSuffix:@"min60.com"]) {

    NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
    NSURLQueryItem * newQueryItem = [[NSURLQueryItem alloc] initWithName:@"ref" value:@"impm"];
    NSMutableArray * newQueryItems = [NSMutableArray arrayWithCapacity:[components.queryItems count] + 1];
    for (NSURLQueryItem * qi in components.queryItems) {
        if (![qi.name isEqual:newQueryItem.name]) {
            [newQueryItems addObject:qi];
        }
    }
    [newQueryItems addObject:newQueryItem];
    [components setQueryItems:newQueryItems];

    url = [components URL];
}
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
8

Just a friendly post for those who don't want to write boilerplate code while building NSURL with NSURLComponents.
Since iOS8 we have NSURLQueryItem that helps building URL request freaking fast.

I wrote a little handy category to ease the work, that you can grab here: URLQueryBuilder
Here is example of how easy it is to work with it:

NSString *baseURL = @"https://google.com/search";
NSDictionary *items = @{
    @"q"  : @"arsenkin.com",
    @"hl" : @"en_US",
    @"lr" : @"lang_en"
};

NSURL *URL = [NSURL ars_queryWithString:baseURL queryElements:items];  
// https://google.com/search?q=arsenkin.com&hl=en_US&lr=lang_en
Soberman
  • 2,556
  • 1
  • 19
  • 22
7

I have an extension to NSURLComponents that add query item, in swift:

extension NSURLComponents {

    func appendQueryItem(name name: String, value: String) {
        var queryItems: [NSURLQueryItem] = self.queryItems ?? [NSURLQueryItem]()
        queryItems.append(NSURLQueryItem(name: name, value: value))
        self.queryItems = queryItems
    }

}

To use,

let components = NSURLComponents(string: urlString)!
components.appendQueryItem(name: "key", value: "value")
samwize
  • 25,675
  • 15
  • 141
  • 186
5

If you're using RestKit it provides additions to NSString. One of which is:

- (NSString *)stringByAppendingQueryParameters:(NSDictionary *)queryParameters

So you could do:

NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:
                                @"limit",@"20", 
                                @"location",@"latitude,longitude",
                                nil];
NSString *pathWithQuery = [@"/api/v1/shops.json" stringByAppendingQueryParameters:shopParams]
drewish
  • 9,042
  • 9
  • 38
  • 51
3

As others have mentioned, you can use NSURLComponents to construct URLs.

@implementation NSURL (Additions)

- (NSURL *)URLByAppendingQueryParameters:(NSDictionary *)queryParameters
{        
    NSURLComponents *components = [[NSURLComponents alloc] initWithURL:self resolvingAgainstBaseURL:NO];
    NSMutableArray *queryItems = [NSMutableArray array:components.queryItems];
    for (NSString *key in [queryParameters allKeys]) {
        NSURLQueryItem *queryItem = [[NSURLQueryItem alloc] initWithName:key value:queryParameters[key]];
        [queryItems addObject:queryItem];
    }
    components.queryItems = queryItems;
    return [components URL];
}

@end
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
wangpeng
  • 56
  • 2
  • 1
    It work good, but need change part for iOS16: *queryItems = [[NSMutableArray alloc] initWithArray:components.queryItems] – Anonimys Feb 09 '23 at 11:40
1

NSURL is not mutable so you cannot implement this functionality directly based on NSURL. Instead you will have to obtain the string representation of the URL, append your parameters to that and then create a new NSURL.

This does not sound like a good solution. Unless there is a good reason, it is better to work with strings until the last moment and only create an NSURL when you have your fully formed request.

Rog
  • 17,070
  • 9
  • 50
  • 73