3

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.

4
  • 3
    The # symbol represents the URL fragment Commented Nov 18, 2020 at 19:05
  • You mean that if I call url.fragment, it returns everything after #? Commented Nov 18, 2020 at 19:18
  • 1
    The problem is that url.fragment returns String and basically you cannot use convenient things like pathComponents and URLComponents Commented Nov 18, 2020 at 19:35
  • Are you dealing with a situation where room/#/order and room/order can be used more-or-less interchangeably? If so something like, .replacingOccurrences(of: "/#/", with: "/") could be a reasonable first-step in transformation. Otherwise, you could do something like let fragmentUrl = URL(string: url.fragment!, relativeTo: url)! to get just the stuff after # Commented Nov 18, 2020 at 20:01

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

Comments

1
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 ?.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.