URLs are everywhere in an app we built. We reference local files using bundle paths, we fetch data from a path pointing to our API, and we fetch images to display visuals. While working with URLs we often need to verify things like making sure that it’s pointing to a file or that certain query property is set.
Some of those operations might be easy to remember and don’t require an intensive guide like this. However, I find myself often searching for that one stack overflow question that explains how to build a URL with a given set of query parameters. As a blogger, this is often the moment where I decide to write a manual for myself and share it with you!
Basic URL Operations
It all starts with basic operations to create and work with links. For example, you can convert a String value into a URL as follows:
let url = URL(string: "https://www.avanderlee.com")!
The downside of this default initializer is the force unwrap we likely have to copy over throughout our codebase. A simple extension helps us to prevent this by using a custom initializer:
extension URL {
init(_ string: StaticString) {
self.init(string: "\(string)")!
}
}
var unwrappedURL = URL("https://www.avanderlee.com")
That looks a lot more swiftly and allows us to keep our implementation code clean from unwraps!
Converting a URL into a String
A URL can also be converted into a String
by using the absoluteString
property:
print(unwrappedURL.absoluteString) // Prints: https://www.avanderlee.com
Making a URL relative to a base URL
A scenario that’s often used when building an API in Swift is to construct a link by using a base URL. For example, we could build up a blog post category page as follows:
let category = "swift"
let baseURL = URL(string: "https://www.avanderlee.com")!
let blogURL = URL(string: category, relativeTo: baseURL)!
print(blogURL) // Prints: swift -- https://www.avanderlee.com
print(blogURL.absoluteString) // Prints: https://www.avanderlee.com/swift
At the same time, you can get the base for a certain link if the URL itself is not absolute:
let absoluteURL = URL(string: "https://www.avanderlee.com")!
let relativeURL = URL(string: "swift", relativeTo: baseURL)!
print(absoluteURL.baseURL) // Prints: nil
print(relativeURL.baseURL!) // Prints: https://www.avanderlee.com
A base URL is often confused with getting the host of a link that prints out the domain without its scheme. You can get the scheme by using the scheme
parameter:
let swiftLeeURL = URL(string: "https://www.avanderlee.com")!
print(absoluteURL.host!) // Prints: www.avanderlee.com
print(absoluteURL.scheme!) // Prints: https
Working with URL Components
A link is basically built up out of several components. We’ve seen a few already in the print statements before but there are a lot more components to cover:
let twitterAvatarURL = URL(string: "https://twitter.com/twannl/photo.png?width=200&height=200")!
print(twitterAvatarURL.path) // Prints: /twannl/photo.png
print(twitterAvatarURL.pathComponents) // Prints: ["/", "twannl", "photo.png"]
print(twitterAvatarURL.pathExtension) // Prints: png
print(twitterAvatarURL.lastPathComponent) // Prints: photo.png
These properties are great for a lot of cases like getting the file name or extension but they don’t give us the query items. This is where URLComponents
come into place.
Get the value of query parameters
The above Twitter avatar link contains two query parameters that describe the width and the height of the image. This is important information to fetch so we make sure that the image is displayed in the correct ratio. Therefore, we need to be able to get those query parameters and their values.
We can do this by using a URLComponents
instance initialized with the link. It gives us access to a new collection of properties. You can basically see it as a URL parser that gives us access to, for example, the query items:
let components = URLComponents(string: "https://twitter.com/twannl/photo.png?width=200&height=200")!
print(components.query!) // width=200&height=200
print(components.queryItems!) // [width=200, height=200]
Note that the URLComponents structure parses and constructs URLs according to RFC 3986. Its behavior differs subtly from that of the URL structure, which conforms to older RFCs. However, you can easily obtain a URL value based on the contents of a URLComponents value or vice versa.
This allows us to directly access the values by going over the query items array:
let width = components.queryItems!.first(where: { queryItem -> Bool in
queryItem.name == "width"
})!.value!
let height = components.queryItems!.first(where: { queryItem -> Bool in
queryItem.name == "height"
})!.value!
let imageSize = CGSize(width: Int(width)!, height: Int(height)!)
print(imageSize) // Prints: (200.0, 200.0)
However, this is still not the most beautiful piece of code. We use a lot of unwraps and the code is not really readable as well. Therefore, we can make use of a custom subscript that allows us to make this code look a lot nicer:
extension Collection where Element == URLQueryItem {
subscript(_ name: String) -> String? {
first(where: { $0.name == name })?.value
}
}
let imageWidth = Int(components.queryItems!["width"]!)!
let imageHeight = Int(components.queryItems!["height"]!)!
let size = CGSize(width: imageWidth, height: imageHeight)
New to custom subscripts? Check out my blog post Custom subscripts in Swift explained with code examples.
Yes, these are still a lot of unwraps. I’ll leave it up to you to make this look nicer by using guards for unwrapping. At least, we can access the query items a lot easier.
Constructing a URL with parameters
The other way around is building a URL with query parameters from a dictionary. This is often used when building an API framework.
For example, if we want to fetch an avatar with a certain width and height, we can build it up as follows:
let parameters = [
"width": 500,
"height": 500
]
var avatarURLComponents = URLComponents(string: "https://twitter.com/twannl/photo.png")!
avatarURLComponents.queryItems = parameters.map({ (key, value) -> URLQueryItem in
URLQueryItem(name: key, value: String(value))
})
print(avatarURLComponents.url!) // Prints: https://twitter.com/twannl/photo.png?width=500&height=500
It might be valuable to write an extension here as well to make it easier to convert a dictionary into an array of query items:
extension Array where Element == URLQueryItem {
init<T: LosslessStringConvertible>(_ dictionary: [String: T]) {
self = dictionary.map({ (key, value) -> Element in
URLQueryItem(name: key, value: String(value))
})
}
}
let pagingParameters = [
"offset": 2,
"limit": 50
]
var twitterFeedURLComponents = URLComponents(string: "https://twitter.com/feed")!
twitterFeedURLComponents.queryItems = .init(pagingParameters)
print(twitterFeedURLComponents.url!) // Prints: https://twitter.com/feed?offset=2&limit=50
Working with file URLs
When building apps we often reference local or remote files. You can download a certain image from a remote location or you can fetch an image from your local bundle.
It’s good to understand the difference between a remote URL and a local one. Only the latter is considered a file URL when using the isFileURL
parameter:
var remoteFileURL = URL(string: "https://www.twitter.com/avatar.jpg")!
var fileURL = URL(string: "file:///users/antoine/avatar.jpg")!
print(remoteFileURL.isFileURL) // Prints: false
print(fileURL.isFileURL) // Prints: true
This property basically only returns true when the URL scheme is file:
.
Getting the file extensions from a file path
When working with files you often want to know the file extension to determine the file type. You can do this by using the path extension parameter:
print(fileURL.pathExtension) // Prints: jpg
How to get the filename from a file path in Swift
To get the file name from a file URL we can use the lastPathComponent
and extract the filename by removing the file extension:
print(fileURL.deletingPathExtension().lastPathComponent) // Prints: avatar
Isn’t that simple? This is great if you want to display the file name in your UI.
Conclusion
URLs in Swift are used in a lot of ways. We fetch data using an API, images to visualize our app, and we often work with local files from our bundle. The Foundation framework allows us to access a lot of URL components easily with default parameters. If we need access to URL components like query items, we can make use of the URLComponents
type.
Do you think that I missed an essential part of working with URLs? Please, let me know!
If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!