{"id":16868937,"url":"https://github.com/ilyapuchka/deeper","last_synced_at":"2026-03-10T06:03:21.805Z","repository":{"id":148362877,"uuid":"106619921","full_name":"ilyapuchka/Deeper","owner":"ilyapuchka","description":"Put some order in your deep links handling","archived":false,"fork":false,"pushed_at":"2018-01-11T10:07:10.000Z","size":102,"stargazers_count":40,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-24T13:37:25.214Z","etag":null,"topics":["deeplinks","ios","swift"],"latest_commit_sha":null,"homepage":"http://ilya.puchka.me/deeplinks-no-brainer/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ilyapuchka.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-11T23:26:23.000Z","updated_at":"2023-01-19T22:27:43.000Z","dependencies_parsed_at":"2023-05-19T21:15:08.115Z","dependency_job_id":null,"html_url":"https://github.com/ilyapuchka/Deeper","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ilyapuchka/Deeper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilyapuchka%2FDeeper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilyapuchka%2FDeeper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilyapuchka%2FDeeper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilyapuchka%2FDeeper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ilyapuchka","download_url":"https://codeload.github.com/ilyapuchka/Deeper/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilyapuchka%2FDeeper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30326878,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T05:25:20.737Z","status":"ssl_error","status_checked_at":"2026-03-10T05:25:17.430Z","response_time":106,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deeplinks","ios","swift"],"created_at":"2024-10-13T14:59:56.864Z","updated_at":"2026-03-10T06:03:21.773Z","avatar_url":"https://github.com/ilyapuchka.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Deeper\n\n*Deeper* is a small framework that aims to help to put some structure in deeplink handling in your iOS app. Read [this article](http://ilya.puchka.me/deeplinks-no-brainer/) to get an overview of an idea behind it.\n\n## Usage\n\n### Define intents\n\nFirst you start by describing *intents* of your deeplinks. It might be just opening some screen or performing an action on some object.\n\n```swift\nenum MyDeepLinkIntent {\n  case showProfile(userId: String)\n  case follow(userId: String)\n  case retweet(tweetId: String)\n}\n```\n\nYou are not limited to using `enum` for that, but it will make it easier to keep track of number of intents and to have exhaustive handling.\n\n### Register url patterns (aka routes)\n\nNext you create a router object and define the \"routes\" for your deeplinks. You can use any router you want along with Deeper, you'll just need to write some extensions to bridge their APIs and implement `DeepLinkRouter` protocol. In the [article](http://ilya.puchka.me/deeplinks-no-brainer/) you can see an example of using [JLRoutes](https://github.com/joeldev/JLRoutes), which was an inspiration for Deeper's own router.\n\nThis project comes with to variants of routers, `Deeper.Router` and `DeeperFunc.Router`. You can use either of them which better suits your taste.\n\n#### Deeper.Router\n\nWith `Deeper.Router` you register routes by registering handler closures which return spicific intent that should be performed when this deeplink is handeled or `nil` if it can't be properly handled. In this handler you have access to the full url, as well as to parsed parameters, extracted from the paths, based on the pattern that you are defining.\n\n\u003e Note: order of registration matters and defines priority of the route, first registered will be tried first when handling deeplink will happen.\n\n```swift\nimport Deeper\n\nlet router = Router\u003cIntent\u003e(scheme: \"myapp\", rootDeepLinkHandler: appDelegate)\n\nrouter.add(routes: [\"profile\" / \":userId\" ]) { url, params in\n  guard let userId = params[.init(\"userId\")] else { return nil }\n  return .showProfile(userId: userId)\n}\n```\n\n#### DeeperFunc.Router\n\nWith `DeeperFunc.Router` your register routes by associating your intent cases (or any other constructor) with url patterns.\n\n```swift\nimport DeeperFunc\n\nlet router = Router\u003cIntent\u003e(scheme: \"myapp\", rootDeepLinkHandler: appDelegate)\n\nrouter.add(Intent.showProfile, route: \"profile\" /\u003e string )\n```\n\nYou will also need to make your intent type conform to `Route` protocol. For that you need to implement `Equatable` protocol and `deconstruct` function:\n\n```swift\nextension Intent: Route {\n\n  func deconstruct\u003cA\u003e(_ constructor: (A) -\u003e Intent) -\u003e A? {\n    switch self {\n    case .showProfile(let values): return extract(constructor, values)\n    case .follow(let values):      return extract(constructor, values)\n    case .retweet(let values):     return extract(constructor, values)\n    }\n  } \n\n}\n```\n\nAs you can see this is just a boilerplate. You can generate it with Sourcery as well as `Equatable` conformance.\n\n\n### Implement handlers\n\nNext you implement `DeepLinkHandler` protocol on some view controller or a separate object that you'll use for that if you want to extract this responsibility. You can also subclass `AnyDeepLinkHandler` class.\n\n```swift\nclass ProfileScreen: UIViewController, DeepLinkHandler {\n\n  var deeplinkHandling: DeepLinkHandling\u003cMyDeepLinkIntent\u003e?\n\t\n  func open(deeplink: DeepLink\u003cMyDeepLinkIntent\u003e, animated: Bool) -\u003e DeepLinkHandling\u003cMyDeepLinkIntent\u003e {\n    // handle deeplink here and return one of the states based on the state of the app\n    switch deeplink.intent {\n    case .shopProfile(let userId):\n      return .opened(deeplink) { [unowned self] animated in \n\t//perform some side-effect that i.e. triggers loading of profile data\n\tself.userService.getProfile(forUserWithId: userId, completion: { result in self.updateView(result) })\n      }\n    default:\n      // fail on any other deeplinks as they are not supported by this screen\n      // you can also use assertions here if you like to catch this earlier\n      return .rejected(deeplink, nil)\n    }\n  }\n  \n}\n```\n\nIt's adviced to also implement this protocol on app delegate or any other \"root\" object that will be always an entry point for deeplink handling. This will ensure predictable control flow both in \"cold\" and \"warm\" start.\n\n```swift\nextension AppDelegate: AnyDeepLinkHandler\u003cMyDeepLinkIntent\u003e {\n\n  func open(deeplink: DeepLink\u003cMyDeepLinkIntent\u003e, animated: Bool) -\u003e DeepLinkHandling\u003cMyDeepLinkIntent\u003e {\n    // start handling deeplink here by deciding i.e. to present destination screen modally\n    // or passing the control flow to some other handler, i.e. root tab bar controller\n    // that will decided to what tab to switch and so on\n    switch deeplink.intent {\n    case .showProfile:\n      return .passedThrough(deeplink) { [unowned self] animated in \n\t// this method returns some other handler that will be invoked right after this closure returns\n\treturn self.showProfileScreen(toOpen: deeplink, animanted: animated)\n      }\n    case .follow(let userId):\n      return .opened(deeplink) { [unowned self] in\n\tself.userService.follow(userWithId: userId, completion: { result in self.showUserMessage(result) })\n      }\n    case .retweet(let tweetId):\n      return .opened(deeplink) { [unowned self] in\n\tself.tweetService.retweet(tweetWithId: userId, completion: { result in self.showUserMessage(result) })\n      }\n    }\n  }\n  \n}\n\nlet router = DeepLinkRouter(scheme: \"myapp\", rootDeepLinkHandler: appDelegate)\n```\n\n### Trigger deeplink handling\n\nThat boils down to just calling `router.open(url: url)` in the app delegate:\n\n```swift\nfunc application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -\u003e Bool {\n  return router.open(url: url)\n}\n```\n\nWhen `open(url:)` is called router will search for the pattern that matches this url and will invoke corresponding handler to get the intent from it. If some handler returns `nil` it will continue to match other handlers until it tries all of them. When matching pattern is found router will create `DeepLink` from url and intent returned by handler closure and will pass it to `open(deeplink:animated:) -\u003e DeepLinkHandling\u003cIntent\u003e` method of a `rootDeepLinkHandler`.\n\nTo handle deeplinks you can follow two different scenarios:\n\n- present destination screen from what ever screen user is currently on\n- perform all navigation steps to get to the destination screen like if user would do all the navigation manually \n\nWhat approach to choose is up to you, but *Deeper* allows you to implement any of them. For that you are provided with a set of `DeepLinkHandling` options which represent different kind of possible states that you may be in while handling deeplink. You can provide optional side effects that will be executed right after `open(deeplink:animated:) -\u003e DeepLinkHandling\u003cIntent\u003e` returns. This can help you to simplify unit tests.\n\n```swift\npublic enum DeepLinkHandling\u003cIntent\u003e {\n    \n  /// Return this state if deeplink successfully handled\n  case opened(DeepLink\u003cIntent\u003e, ((Bool) -\u003e Void)?)\n    \n  /// Return this state if deeplink was rejected because it can't be handeled, with optional error\n  case rejected(DeepLink\u003cIntent\u003e, Error?)\n    \n  /// Return this state if deeplink handling delayed because more data is needed\n  case delayed(DeepLink\u003cIntent\u003e, Bool, ((Bool) -\u003e Void)?)\n    \n  /// Return this state if deeplink was passed through to some other handler\n  case passedThrough(DeepLink\u003cIntent\u003e, ((Bool) -\u003e AnyDeepLinkHandler\u003cIntent\u003e)?)\n    \n}\n\n```\n\nIn general that's it, the rest depends on your imagination and how your application is built. In the [article](http://ilya.puchka.me/deeplinks-no-brainer/) you can find more extensive code examples which are closer to real life project.\n\n### TODO:\n\n- example application\n- improve documentation\n- CocoaPods and SPM support\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filyapuchka%2Fdeeper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Filyapuchka%2Fdeeper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filyapuchka%2Fdeeper/lists"}