• Stars
    star
    415
  • Rank 100,469 (Top 3 %)
  • Language
    Swift
  • License
    MIT License
  • Created almost 6 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🚍 Route URL schemes easily

Crossroad

Build Status Language SwiftPM compatible Carthage compatible CocoaPods Compatible Platform 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 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.

import Crossroad

let 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.

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:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let context = URLContexts.first else {
        return
    }
    router.openIfPossible(context.url, options: context.options)
}

Using NSApplicationDelegate (for macOS)

If you develop macOS applications:

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.

// actual URL: pokedex://search/Pikachu
let keyword: String = try context.arguments(named: "keyword") // Pikachu

QueryParameter

And more, you can get query parameters if exist.

// 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.

// 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.

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.

// 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.

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:

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.

struct UserInfo {
    let userID: Int64
}
let router = try Router<UserInfo>(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.

let parser = ContextParser()
let context = parser.parse(URL(string: "pokedex:/pokemons/25")!, 
                           with: "pokedex://pokemons/:id")

Installation

Swift Package Manager

CocoaPods

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 license. Original design by @Arslanshn.

More Repositories

1

Scipio

A new build tool to generate XCFramework
Swift
388
star
2

xcprofiler

📈 CLI to profile compilation time of Swift project
Ruby
331
star
3

RxSpriteKit

👾 Reactive Extensions for SpriteKit
Swift
132
star
4

Toybox

🧸 Xcode Playground management made easy
Swift
130
star
5

NESEmulator-watchOS

👾 ⌚ NES Emulator on Apple Watch
Swift
122
star
6

Peafowl

🀄 Play Japanese Mahjong in Swift
Swift
91
star
7

SimRecorder

GIF Recorder for iOS Simulator
Swift
74
star
8

MinSwift-workshop

♻️ minimum Swift compiler written in Swift
Swift
55
star
9

danger-xcprofiler

🚫 danger plugin for asserting Swift compilation time
Ruby
52
star
10

Wormhole

🐛 Wormhole invites you to the fastest trip to AppStore Connect. (NOT STABLE)
Swift
38
star
11

Milepost

🚥 SwiftPM Build Plugin to show Git info on your app
Swift
34
star
12

KawazBuster

かわずたんたたき! for iPhone
Objective-C
25
star
13

FCEUX-watchOS

👾 ⌚ Play NES on watchOS
C++
21
star
14

CustomKeyboardTextField

Provides easy way to make type safety TextField with custom keyboards
Swift
20
star
15

NibLoaderKit

Tiny utility to load UIView/NSView from nibs
Swift
15
star
16

castle

🏰 All my dotfile are belong to us. BUT MY CREDENTIALS ARE IN ANOTHER CASTLE.
Vim Script
15
star
17

JSONMatcher

A JSON matcher extension in Swift for Nimble
Swift
14
star
18

xcode-opener

Open Xcode project by context
Shell
13
star
19

alfred-autojump-workflow

Alfred 2 workflow to provide you a faster way to navigate your filesystem via autojump
Python
12
star
20

KawazCatch

C++
10
star
21

aws-lambda-swift-runtime

Swift
10
star
22

CCSocialShare

Ultimate SNS adapter for cocos2d-x
Java
10
star
23

RubyJang

るびじゃん!〜Rubyで麻雀実装してみた〜
Ruby
10
star
24

beddit-python

API Client for Beddit Sleep Tracker 😴
Python
10
star
25

KawazJet

C++
9
star
26

django-debug-toolbar-vcs-info

Add VCS info onto your django-debug-toolbar
Python
8
star
27

dqx_helper

useful tool collection for DragonQuest X
Python
8
star
28

alfred-ghq-workflow

Search and get repository on GHQ via Alfred
Python
7
star
29

Jubiol

Jubiol is new type shooting game written in CoffeeScript.
CoffeeScript
6
star
30

RikoBuster

Swift
5
star
31

swift-evolution-gpt

Python
5
star
32

peco-anyenv

Switch interpreter versions interactively with fooenv(rbenv, pyenv and others) + peco
Shell
5
star
33

django-slack-invitation

Automatic invitation to Slack team
Python
5
star
34

Kaleidscope-cpp

C++
4
star
35

fastlane-plugin-influxdb

fastlane plugin to post to InfluxDB
Ruby
4
star
36

jquery-3dsmenu

jQuery plugin for listing like Nintendo 3DS home menu.
JavaScript
4
star
37

Heqet

Heqet is an useful utility collection for game development on Kobold2D.
Objective-C
4
star
38

MachiMatch

新感覚アクションパズルゲー!
Python
4
star
39

modern-gb

C
3
star
40

KawazCrash

C++
3
star
41

BrushRush

Rush the doodle by your Brush
Objective-C
3
star
42

Hatena-Bookmark-for-Safari

Safari Extention
JavaScript
3
star
43

scipio-s3-storage

Scipio cache storage backend for AWS S3
Swift
2
star
44

pokedex-legacy

Python
2
star
45

XI

XI[sai] Clone on Unity3D in Boo
C#
2
star
46

docker-gbdk

👾 🐳 Docker image to build Gameboy ROMs with GBDK.
Dockerfile
2
star
47

denops-deckset.vim

Vim/Neovim plugin which enpowers editing Deckset slides
TypeScript
2
star
48

Sockwaz

Sockwaz is live tweet viewer written in CoffeeScript.
CoffeeScript
2
star
49

SantaRochka

Sample Game for GGJ12 on Kobold2D
Objective-C
2
star
50

ribbit

ribbit is the perfect collaboration tool for all creators.
Python
2
star
51

gb-sprite-generator

My first Rust execise.
Rust
2
star
52

UDON-for-iPhone

iPhone習作。UDONを移植してみた。
Objective-C
2
star
53

LoopyLooper

Objective-C
2
star
54

libjwt-swift

Swift modulemaps for libjwt
Swift
2
star
55

django-thumbnailfield

An ImageField with auto thumbnail generation for Django.
2
star
56

RentalDayNotifier

レンタルの返却日を通知します
Objective-C
2
star
57

AutomationTestDemo

This is a demo project for ui testing with TuneupJS
JavaScript
2
star
58

ComplexNetwork

情報工学実験Ⅱ第2回課題
Java
1
star
59

Tekitirs

An old-style shooting game on Unity
Boo
1
star
60

django-star

django plugin
Python
1
star
61

SoukobanSolver

倉庫番解くよ!
Java
1
star
62

Fortress

スタイリッシュ落城ストラテジー!
Objective-C
1
star
63

CCADX2Manager

C++
1
star
64

iGEM-Wiki

JavaScript
1
star
65

MyList

はてな課題2
Perl
1
star
66

experiments

課題用
C++
1
star
67

alfred-irkit-workflow

Alfred workflow to send signal for IRKit.
Python
1
star
68

PatternMatch

情報工学実験Ⅰの課題です
Python
1
star
69

CCMessageWindow

Message Window node for cocos2d-x
C++
1
star
70

kwing.coffee

useful utils for game developing via Kawaz. It extends enchant.js
CoffeeScript
1
star
71

9leapShooting

9leapハッカソン用
JavaScript
1
star
72

Cockatrice

新感覚パズルゲーム
Python
1
star
73

NazoShooting

2010年4月の春のゲーム祭りで開発したシューティングゲーです
Ruby
1
star
74

BeatleFlick

A beat'll flick the beetles.
Objective-C
1
star
75

expeiment3

実験その3
R
1
star
76

LeDA

Level Design Assistant System via Crowdsourcing
Python
1
star
77

gbdev-tools

Python
1
star
78

TwitterClone

はてな課題3
Perl
1
star
79

jquery.pingboard.js

生存戦略しましょうか
CoffeeScript
1
star
80

cocos2d-html5-demo

JavaScript
1
star
81

VirusSimulator

情報工学演習Ⅱの課題
Java
1
star
82

Kwing4Py

Kwing is not Geek!
1
star
83

GCControllerWrapper

GameController.framework for C++
C
1
star
84

gl.enchant.js

CoffeeScript
1
star
85

Kukuri

Kukuri is a PatternMatching Engine for iPad via OpenCV.
C++
1
star
86

RailDesigner

C++
1
star
87

Barca

⚡Barca hastens Carthage packages like lightning.
Swift
1
star
88

ElmsAutoLoginSafariExtension

auto login to ELMS
JavaScript
1
star
89

Flamesc

けしからんゲームです
Python
1
star
90

DanketsuTrokko-kun

This is a demo project of P2P amount multiple devices via GKSession on Kobold2D
Objective-C
1
star
91

FormationPuzzle

Python
1
star