Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/giginet/Crossroad
:oncoming_bus: Route URL schemes easily
https://github.com/giginet/Crossroad
Last synced: 3 months ago
JSON representation
:oncoming_bus: Route URL schemes easily
- Host: GitHub
- URL: https://github.com/giginet/Crossroad
- Owner: giginet
- License: mit
- Created: 2018-05-18T08:54:11.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-02-19T09:53:14.000Z (9 months ago)
- Last Synced: 2024-07-19T11:46:15.774Z (4 months ago)
- Language: Swift
- Homepage:
- Size: 381 KB
- Stars: 417
- Watchers: 5
- Forks: 23
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-ios - Crossroad - Crossroad is an URL router focused on handling Custom URL Schemes. Using this, you can route multiple URL schemes and fetch arguments and parameters easily. (App Routing)
- awesome-swift - Crossroad - :oncoming_bus: Crossroad is an URL router focused on handling Custom URL Schemes. (Libs / App Routing)
- awesome-swift - Crossroad - :oncoming_bus: Crossroad is an URL router focused on handling Custom URL Schemes. (Libs / App Routing)
- fucking-awesome-swift - Crossroad - :oncoming_bus: Crossroad is an URL router focused on handling Custom URL Schemes. (Libs / App Routing)
- awesome-ios-star - Crossroad - Crossroad is an URL router focused on handling Custom URL Schemes. Using this, you can route multiple URL schemes and fetch arguments and parameters easily. (App Routing)
- fucking-awesome-ios - Crossroad - Crossroad is an URL router focused on handling Custom URL Schemes. Using this, you can route multiple URL schemes and fetch arguments and parameters easily. (App Routing)
- awesome-swift - Crossroad - Route URL schemes easily ` 📝 13 days ago ` (App Routing [🔝](#readme))
README
[![Crossroad](Documentation/logo.png)](https://github.com/giginet/Crossroad)
[![Build Status](https://img.shields.io/github/workflow/status/giginet/Crossroad/Crossroad?style=flat-square)](https://github.com/giginet/Crossroad/actions?query=workflow%3ACrossroad)
[![Language](https://img.shields.io/static/v1.svg?label=language&message=Swift%205.4&color=FA7343&logo=swift&style=flat-square)](https://swift.org)
[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-4BC51D.svg?style=flat-square)](https://swift.org/package-manager/)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat-square)](https://github.com/Carthage/Carthage)
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Crossroad.svg?style=flat-square)](http://cocoapods.org/pods/Crossroad)
[![Platform](https://img.shields.io/static/v1.svg?label=platform&message=iOS|macOS|tvOS&color=grey&logo=apple&style=flat-square)](http://cocoapods.org/pods/Crossroad)
[![License](https://img.shields.io/cocoapods/l/Crossroad.svg?style=flat-square)](https://github.com/giginet/Crossroad/blob/master/LICENSE)Route URL schemes easily.
Crossroad is an URL router focused on handling Custom URL Schemes or Universal Links.
Of cource, you can also use for [Firebase Dynamic Link](https://firebase.google.com/docs/dynamic-links) or other similar services.Using this, you can route multiple URL schemes and fetch arguments and parameters easily.
This library is developed in working time for Cookpad.
## Basic Usage
You can use `DefaultRouter` to define route definitions.
Imagine to implement Pokédex on iOS. You can access somewhere via URL scheme.
```swift
import Crossroadlet customURLScheme: LinkSource = .customURLScheme("pokedex")
let universalLink: LinkSource = .universalLink(URL(string: "https://my-awesome-pokedex.com")!)do {
let router = try DefaultRouter(accepting: [customURLScheme, universalLink]) { registry in
registry.route("/pokemons/:pokedexID") { context in
let pokedexID: Int = try context.argument(named: "pokedexID") // Parse 'pokedexID' from URL
if !Pokedex.isExist(pokedexID) { // Find the Pokémon by ID
throw PokedexError.pokemonIsNotExist(pokedexID) // If Pokémon is not found. Try next route definition.
}
presentPokedexDetailViewController(of: pokedexID)
}
registry.route("/pokemons") { context in
let type: Type? = context.queryParameters.type // If URL contains &type=fire, you can get Fire type.
presentPokedexListViewController(for: type)
}// ...
}
} catch {
// If route definitions have some problems, routers fail initialization and raise reasons.
fatalError(error.localizedDescription)
}// Pikachu(No. 25) is exist! so you can open Pikachu's page.
let canRespond25 = router.responds(to: URL(string: "pokedex://pokemons/25")!) // true
// No. 9999 is missing. so you can't open this page.
let canRespond9999 = router.responds(to: URL(string: "pokedex://pokemons/9999")!) // false
// You can also open the pages via universal links.
let canRespondUniversalLink = router.responds(to: URL(string: "https://my-awesome-pokedex.com/pokemons/25")!) // true// Open Pikachu page
router.openIfPossible(URL(string: "pokedex://pokemons/25")!)
// Open list of fire Pokémons page
router.openIfPossible(URL(string: "pokedex://pokemons?type=fire")!)
```### Using AppDelegate
In common use case, you should call `router.openIfPossible` on `UIApplicationDelegate` method.
```swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
if router.responds(to: url, options: options) {
return router.openIfPossible(url, options: options)
}
return false
}
```### Using SceneDelegate
Or, if you are using `SceneDelegate` with a modern app:
```swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
guard let context = URLContexts.first else {
return
}
router.openIfPossible(context.url, options: context.options)
}
```### Using NSApplicationDelegate (for macOS)
If you develop macOS applications:
```swift
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let appleEventManager = NSAppleEventManager.shared()
appleEventManager.setEventHandler(self,
andSelector: #selector(handleURLEvent(event:replyEvent:)),
forEventClass: AEEventClass(kInternetEventClass),
andEventID: AEEventID(kAEGetURL))
}@objc func handleURLEvent(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
guard let urlString = event?.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))?.stringValue else { return }
guard let url = URL(string: urlString) else { return }
router.openIfPossible(context.url, options: [:])
}
}
```## Argument and Parameter
### Argument
`:` prefixed components on passed URL pattern mean **argument**.
For example, if passed URL matches `pokedex://search/:keyword`, you can get `keyword` from `Context`.
```swift
// actual URL: pokedex://search/Pikachu
let keyword: String = try context.arguments(named: "keyword") // Pikachu
```### QueryParameter
And more, you can get query parameters if exist.
```swift
// actual URL: pokedex://search/Pikachu?generation=1
let generation: Int? = context.queryParameters["generation"] // 1
// or you can also get value using DynamicMemberLookup
let generation: Int? = context.queryParameters.generation // 1
```You can cast arguments/query parameters as any type. Crossroad attempt to cast each String values to the type.
```swift
// expected pattern: pokedex://search/:pokedexID
// actual URL: pokedex://search/25
let pokedexID: Int = try context.arguments(named: "keyword") // 25
```Currently supported types are `Int`, `Int64`, `Float`, `Double`, `Bool`, `String` and `URL`.
### Enum arguments
You can use enums as arguments by conforming to `Parsable`.
```swift
enum Type: String, Parsable {
case normal
case fire
case water
case grass
// ....
}// matches: pokedex://pokemons?type=fire
let type: Type? = context.queryParameters.type // .fire
```### Comma-separated list
You can treat comma-separated query strings as `Array` or `Set`.
```swift
// matches: pokedex://pokemons?types=water,grass
let types: [Type]? = context.queryParameters.types // [.water, .grass]
```### Custom argument
You can also define own arguments by implementing `Parsable`.
This is an example to parse custom struct.```swift
struct User {
let name: String
}
extension User: Parsable {
init?(from string: String) {
self.name = string
}
}
```## Multiple link sources support
You can define complex routing definitions like following:
```swift
let customURLScheme: LinkSource = .customURLScheme("pokedex")
let pokedexWeb: LinkSource = .universalLink(URL(string: "https://my-awesome-pokedex.com")!)
let anotherWeb: LinkSource = .universalLink(URL(string: "https://kanto.my-awesome-pokedex.com")!)let router = try DefaultRouter(accepting: [customURLScheme, pokedexWeb, anotherWeb]) { registry in
// Pokémon detail pages can be opened from all sources.
registry.route("/pokemons/:pokedexID") { context in
let pokedexID: Int = try context.argument(named: "pokedexID") // Parse 'pokedexID' from URL
if !Pokedex.isExist(pokedexID) { // Find the Pokémon by ID
throw PokedexError.pokemonIsNotExist(pokedexID)
}
presentPokedexDetailViewController(of: pokedexID)
}// Move related pages can be opened only from Custom URL Schemes
registry.group(accepting: [customURLScheme]) { group in
group.route("/moves/:move_name") { context in
let moveName: String = try context.argument(named: "move_name")
presentMoveViewController(for: moveName)
}
group.route("/pokemons/:pokedexID/move") { context in
let pokedexID: Int = try context.argument(named: "pokedexID")
presentPokemonMoveViewController(for: pokedexID)
}
}// You can pass acceptPolicy for a specific page.
registry.route("/regions", accepting: .only(for: pokedexWeb)) { context in
presentRegionListViewController()
}
}
```This router can treat three link sources.
## Custom Router
You can add any payload to `Router`.
```swift
struct UserInfo {
let userID: Int64
}
let router = try Router(accepting: customURLScheme) { registry in
registry.route("pokedex://pokemons") { context in
let userInfo: UserInfo = context.userInfo
let userID = userInfo.userID
}
// ...
])
let userInfo = UserInfo(userID: User.current.id)
router.openIfPossible(url, userInfo: userInfo)
```## Parse URL patterns
If you maintain a complex application and you want to use independent URL pattern parsers without Router.
You can use `ContextParser`.```swift
let parser = ContextParser()
let context = parser.parse(URL(string: "pokedex:/pokemons/25")!,
with: "pokedex://pokemons/:id")
```## Installation
### Swift Package Manager
- File > Swift Packages > Add Package Dependency
- Add https://github.com/giginet/Crossroad.git
Select "Up to Next Major" with "4.0.0"### CocoaPods
```ruby
use_frameworks!pod 'Crossroad'
```### Carthage
```
github "giginet/Crossroad"
```## Demo
1. Open `Demo/Demo.xcodeproj` on Xcode.
2. Build `Demo` schema.## Supported version
Latest version of Crossroad requires Swift 5.2 or above.
Use 1.x instead on Swift 4.1 or lower.
|Crossroad Version|Swift Version|Xcode Version|
|-----------------|-------------|-------------|
|4.x |5.4 |Xcode 13.0 |
|3.x |5.0 |Xcode 10.3 |
|2.x |5.0 |Xcode 10.2 |
|1.x |4.0 ~ 4.2 |~ Xcode 10.1 |## License
Crossroad is released under the MIT License.
Header logo is released under the [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license. Original design by [@Arslanshn](https://github.com/Arslanshn).