neo
neo

Reputation: 1404

How to parse URL with # in Swift?

Suppose, I have following URL: https://something.com/room/order/12345555/product/543333?is_correct=true. It is kind of deeplink and I should parse its parameters and show some ViewController. I am interested in values as 12345555, 543333 and true. Actually, it is easy to get those parameters.

In order to get 12345555 or 543333, we can use pathComponents of URL which returns ["/", "room", "order", "12345555", "product", "543333"]. To get query items (is_correct: true), we can use URLComponents. Everything is clear and simple.

But suppose my link contains # as path https://something.com/room/#/order/12345555/product/543333?is_correct=true. Now, for this link, pathComponents returns just ["/", "room"] ignoring everything else. Of course, there are also problems with query parameters.

Why does # symbol affect so? How can I solve problem? Should I just replace # with something or URL from Swift contains some helper methods? Thanks.

Upvotes: 3

Views: 1573

Answers (2)

Fattie
Fattie

Reputation: 12272

In the typical case of the app being called-back at a custom URL...

As of 2023, I really don't know a more elegant way to do it, than just

  • convert it to a string,
  • replace the hash with a query so that the string is a normal url
  • convert the string back to a url

Hence,

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, openURLContexts: Set<UIOpenURLContext>) {
        if let urlContext = openURLContexts.first {
            process(urlContext.url)
        }
    }
    ...
    ...

and then ...

///KISS approach, change # to ? then proceed normally.
func process(_ u: URL) {

    let clean = u.absoluteString.replacingOccurrences(of: "#", with: "?")

    if let c = URLComponents(string: clean), let q = c.queryItems {

        let dict = q.reduce(into: [:]) { $0[$1.name] = $1.value }
        print(dict)
    }
}

And that's it.

Unfortunately if you use .fragment there's no way to make Foundation of the work of parsing it, which is a shame.

I suppose you could say clean = "z://?\(urlstring.fragment)" (where the z is just an irrelevant character) but there's no advantage at all and it seems more straightforward and readable to simply swap the # to a ?.

Upvotes: 1

Brian Nickel
Brian Nickel

Reputation: 27560

The problem you're running into is that # isn't part of the path but introducing a new component of the URL, stored in url.fragment. It's similar to if you had https://example.com/foo/?test=/bar. ?test= isn't a path component but the beginning of the query.

You have two approaches you can take.

If https://something.com/room/order/12345555/product/543333?is_correct=true and https://something.com/room/#/order/12345555/product/543333?is_correct=true can be used interchangeably, as in viewing either page in the browser will land you on the same page, you could have a sanitizing step in your process:

var rawUrl = ...
var sanitizedUrl = url.replacingOccurrences(of: "/#/", with: "/")
var url = URL(string: url)

How much sanitization you do depends on your application. It could be that you only want to do (of: "/room/#/", with: "/room/")

Another option, if you know your fragment will always look like a partial URL would be to pass the fragment into URL:

let url = URL(string: rawUrl)!
let fragmentUrl = URL(string: url.fragment!, relativeTo: url)!

let fullPathComponents = url.pathComponents + fragmentUrl.pathComponents[1...];
var query = fragmentUrl.query

The above approach yields: ["/", "room", "order", "12345555", "product", "543333"] for the joined URL.

Which approach and how much sanitization you do will depend on your use-case.

Upvotes: 4

Related Questions