Creating and Modifying a URL in your Swift App

In many types of apps, you have to access files. They can be in your app bundle, on the file system, or even somewhere on the web. You need to have someway to refer to where they are in your code. On Apple platforms, you basically have 2 choices, as a String or aURL.

Given the address bar right above you, or any use of a terminal, a Swift String would be a very understandable choice, I mean, that’s all that text is in the address bar, right? Many of the older APIs in the Cocoa and Cocoa Touch SDKs take URLs and Strings (referred to as a “path” in those APIs usually), but things are moving more and more towards just using URL objects everywhere. URL objects have many advantages over String paths, most notably that you can access different parts of the URL from properties, instead of having to write your own code to parse those components from the path String.

Stay tuned as we learn a thing or two about creating and using URL objects in your Swift app.

Creating a URL in Swift

There are several initializers and factory methods to create a URL in Swift, but I’m going to cover some of the more useful ones.

init?(string URLString: String)

This one is the most plain, and probably the most used. This takes your Swift String version of a URL, and changes it into a URL object. It is a failable initializer, because not all strings can make valid URLS. There are some characters that cannot be used in URLs, and thus are percent encoded, meaning a code that CAN be sent in a URL is used in its place. The one I personally have seen the most is %20, the “space” character. This initializer expects only valid characters, it will not percent encode for you. If the string does have characters or anything that can’t be converted into a valid URL, this initializer will return nil.

1
2
let NSHipster = URL(string: "http://nshipster.com/") //returns a valid URL
let invalidURL = URL(string: "www.example.com/This is a sentence") //Returns nil

This is actually a convenience initializer which defers to the initializer below.

init?(string: String, relativeTo: URL?)

This is the dedicated initializer. Similar to the previous initializer, it is a failable one, and takes a similar URL Swift String, but it also takes an optional baseURL object, which is a URL object itself. If the baseURL is nil, it just makes the URL entirely from the URL String, which is probably what the first initializer does under the hood.

1
2
let NSHipsterTwo = URL(string: "http://nshipster.com/", relativeTo: nil) //Returns valid NSHipster URL
let article = URL(string: "ios9/", relativeTo: NSHipster) //Returns "http://nshipster.com/ios9/" URL

init(fileURLWithPath: String, isDirectory: Bool)

This is similar to the above initializer, but is meant to point at a local file or directory. I’m not certain why there is a special version for local files, but I presume some optimizations are made (at least to start it with the file scheme, as opposed to http, or other ones). There is a version without the isDirectory parameter, but the header file suggests using this one if you do know whether it is a directory or not. Presumably, the other one will need to perform the check itself, while this one has you providing the answer, saving it from having to check.

init(fileURLWithPath: String, isDirectory: Bool, relativeTo: URL?)

Newly added in iOS 9, this is similar to the previous one, but with the relativeToURL parameter. Like the previous initializers, this will return a URL object that appends the path to the baseURL. This could be used, if you had a directory of several files, where you were iterating through them to use for something. You could supply the directory the files were in as the baseURL, and then just create the URLs with the file name as the Swift String path.

Converting URL back to a Swift String

Sometimes, especially when dealing with older API, or when showing it to the user, you need to convert a URL object back to a Swift String. Thankfully, URL provides a simple read-only property to get that out: absoluteString. Just call that property on your URL object and you’ve got it:

1
2
let articleString = article?.absoluteString
//ArticleString now contains: "http://nshipster.com/ios9/"

In this case it took our article constant that we defined using the relativeToURL version of the initializer, resolved it into a complete URL from scheme until the end (which in this case is a path). If we did have file extensions, queries, and fragments on this URL, it would resolve them as well. The original article object was returned from the failable initializer, so that is why we still have that question mark there for Swift’s optional chaining.

Modifying a URL Object

Each of these functions return a new URL object based on the one it was called from, but with the requested modification done. They do NOT change the URL they are called from though, it remains the same.

func appendingPathComponent(String, isDirectory: Bool) -> URL

This one adds more path components to the URL, say if you were adding a file to the directory you’re in (which is stored in the URL this is called from). There is a version without the isDirectory parameter like some of the initializers, but if you know whether it is a directory or not, it is recommended to use this one to save the metadata check to determine whether it is a directory or not.

func deletingLastPathComponent() -> URL

This method will return a new URL with the last path component deleted. This works on the path part of a URL, so other areas of the URL are unaffected, like the domain. So we can do this:

1
2
3
4
5
let articleTwo = NSHipster?.URLByAppendingPathComponent("ios9", isDirectory: true)
//articleTwo now contains "http://nshipster.com/ios9/"
let deletePathComp = articleTwo?.URLByDeletingLastPathComponent
//deletePathComp now contains "http://nshipster.com/"

If there is no path information, things can get a bit weird. For fun, I chained a few URLByDeletingLastPathComponent afterwards, and it ended up just appending “../” afterwards, analogous to going up a directory in the command line (cd ..).

There are several more modification methods and properties, but these are probably the most commonly used.

Conclusion

If you are curious about specifics of URL formatting specifications, you can checkout the RFC documents that Apple’s URL Class Reference mentions in how it handles URLs. The strings used when initializing URLs must conform to RFC 2396, and the URLs themselves are parsed according to RFC 1738 and RFC 1808. The are pretty long but probably have anything you would want to know about URLs, URIs, etc.

There are many other properties in URL if you want a fully resolved URL, the baseURL, host, query, fragment, etc, and you can check them out in Apple’s URL Class Reference, but I personally primarily use absoluteString, and occasionally pathExtension.

I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!

Sources

Thx F Sup