Skip to Content
Course content

173: URL and URLComponents In Depth

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably used the URL(string:) initializer a thousand times. For a static link, it's perfect. But the moment you start building requests for a real-world API—where you have search terms, pagination, and API keys—trying to glue strings together is a recipe for a crash or a 400 Bad Request error. That's where URLComponents comes in.

Let's build a movie search URL generator for an API. We need to hit a specific endpoint, pass a search query, an API key, and an optional page number.

Starting with the basic URL

First, I'll define my base. I usually keep the base URL separate from the specific endpoint to keep things clean. I'm thinking about a standard REST structure here.

let baseURL = "https://api.themoviedb.org/3/search/movie"
let apiKey = "abc123xyz"
let searchTerm = "Guardians of the Galaxy"

The "Quick and Dirty" Mistake

When I first started out, I used to just interpolate everything into one big string. It feels faster, right? I'll show you what I mean, and why I stopped doing it.

// Don't do this in production
let badURLString = "\(baseURL)?api_key=\(apiKey)&query=\(searchTerm)"
if let url = URL(string: badURLString) {
    print(url)
}

Here is the problem: searchTerm contains spaces. If you run this, you'll notice that URL(string:) might return nil, or the server will reject it because URLs cannot contain raw spaces. I could manually call addingPercentEncoding(withAllowedCharacters:) on every single variable, but that's tedious and error-prone. I'm basically doing the work the framework is already designed to do for me.

Structuring with URLComponents

Instead of treating the URL as a string, I'll treat it as a collection of components. URLComponents allows me to set the scheme, host, path, and query items independently. It handles all the percent-encoding automatically.

var components = URLComponents(string: baseURL)! 
// I'm force-unwrapping here because I know my constant is valid.

components.queryItems = [
    URLQueryItem(name: "api_key", value: apiKey),
    URLQueryItem(name: "query", value: searchTerm)
]

if let finalURL = components.url {
    print(finalURL) 
    // Output: https://api.themoviedb.org/3/search/movie?api_key=abc123xyz&query=Guardians%20of%20the%20Galaxy
}

See that %20? URLComponents saw the spaces in "Guardians of the Galaxy" and encoded them correctly without me writing a single line of regex or encoding logic.

Handling Optional Parameters Dynamically

In a real app, you don't always have every parameter. For example, you might only want to include the page parameter if the user has actually scrolled past the first page. Since queryItems is just an array of URLQueryItem, we can build it dynamically.

func buildSearchURL(query: String, page: Int?) -> URL? {
    var components = URLComponents(string: "https://api.themoviedb.org/3/search/movie")!
    
    var queryItems = [
        URLQueryItem(name: "api_key", value: "abc123xyz"),
        URLQueryItem(name: "query", value: query)
    ]
    
    if let page = page {
        queryItems.append(URLQueryItem(name: "page", value: String(page)))
    }
    
    components.queryItems = queryItems
    return components.url
}

let urlWithPage = buildSearchURL(query: "Inception", page: 2)
let urlWithoutPage = buildSearchURL(query: "Inception", page: nil)

By separating the logic this way, I'm not fighting with & and ? symbols. I'm just managing a list of key-value pairs, and letting Swift handle the formatting. It's cleaner, it's safer, and it's how you should handle any dynamic URL construction.




📋 Practical Task

Build a Recipe Search URL Generator

You are integrating a recipe API that requires a base URL, a search term, a "cuisine" filter, and a "maxCalories" limit. However, the cuisine and calories filters are optional.

Write a function called createRecipeURL(searchTerm:cuisine:maxCalories:) that returns an optional URL. Use URLComponents and URLQueryItem to ensure the following:

  • The base URL is https://api.recipes.com/v1/search.
  • The searchTerm is always included.
  • The cuisine parameter is only added to the URL if the provided string is not nil.
  • The maxCalories parameter is only added if the provided integer is not nil.
  • All values are properly percent-encoded (handled automatically by your choice of components).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.