Simple, Swift deeplinking and notification handling
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
LinksAndStuff
Stargate.xcodeproj
Stargate
StargateTests
.gitignore
.travis.yml
README.md

README.md

Stargate

Platform: iOS 8+

<br>
<br>

Attempting to make deeplinking and handling push notifications on iOS, simpler. Work in progress.

Usage

In your App Delegate:

DeepLink:

func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
    let deepLink = DeepLink(url: url, sourceApplication: sourceApplication, annotation: annotation)
    return Router.handleDeepLink(deepLink)
}

PushNotification:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
  // Called when foreground or background but not suspended
  Router.handlePushNotification(userInfo)
  completionHandler(.NoData)
}

### Registering for DeepLink:
class MyViewController: UIViewController, DeepLinkCatcher {
	static let deepLinkRegex = "myapp:\\/\\/cool\\/ass\\/stuff" // Matches myapp://cool/ass/stuff

	override func viewDidLoad() {
		super.viewDidLoad()
        Router.setDeepLinkCatcher(self, forRegex: self.dynamicType.deepLinkRegex, referenceStrength: .Weak)
	}

	func catchDeepLink(deepLink: DeepLink) -> Bool {
		if self.view.window != nil {
    		// View is on screen
    		// Do some rad crap
    		return true
  		}

		// Returning false will make DeepLinkRouter call delegate (if there is one, of course)
		return false
    }
}

DeepLink:

@objc public class DeepLink: NSObject, Regexable {
    let url: NSURL
    let sourceApplication: String?
    let annotation: AnyObject

    public init(url: NSURL, sourceApplication: String?, annotation: AnyObject) {
        self.url = url
        self.sourceApplication = sourceApplication
        self.annotation = annotation
    }

    public func matchesRegex(regex: Regex) -> Bool {
        let link = self.url.absoluteString
        let regex = try? NSRegularExpression(pattern: regex, options: .CaseInsensitive)
        let numberOfMatches = regex?.numberOfMatchesInString(link, options: [], range: NSMakeRange(0, link.characters.count))
        return numberOfMatches > 0
    }
}

### Registering for a Push Notification:

Pretty much the same as DeepLink.

class MyViewController: UIViewController, PushNotificationCatcher {
	static let notificationRegex = "myapp:\\/\\/^cool_ass_stuff$" // Matches myapp://cool/ass/stuff

	override func viewDidLoad() {
		super.viewDidLoad()
		Router.setPushNotificationCatcher(self, forRegex: self.dynamicType.notificationRegex, referenceStrength: .Weak)
	}

	func catchPushNotification(pushNotification: PushNotification) {
		// Do stuff
    }
}

This would be called for this notification:

{
  "aps": {
    "alert": "message",
    "sound": "default",
    "badge": "1",
    "notification_id": "cool_ass_stuff"
  }
}

The regex is compared on a string in the notification payload. By default the key for this string is "notification_id". You can change this by:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  PushNotification.setDefaultPushNotificationKey("your_key")
}

PushNotification:

public typealias PushNotificationPayload = [NSObject : AnyObject]
@objc public class PushNotification: NSObject, Regexable {
    public let payload: PushNotificationPayload

    public init(payload: PushNotificationPayload) {
        self.payload = payload
    }

    public static func setDefaultPushNotificationKey(key: String) { defaultPushNotificationKey = key }

    public func matchesRegex(regex: Regex) -> Bool {
        if let payload = self.payload["aps"] as? [NSObject : AnyObject], notification = payload[defaultPushNotificationKey] as? String {
            let regex = try? NSRegularExpression(pattern: regex, options: .CaseInsensitive)
            let numberOfMatches = regex?.numberOfMatchesInString(notification, options: [], range: NSMakeRange(0, notification.characters.count))
            return numberOfMatches > 0
        }
        return false
    }
}

Closure handlers

You can also just provide a closure if you'd prefer, just make sure you manage memory properly (e.g. use [weak self]).

let deepLinkClosure = DeepLinkClosureCatcher(callback: { deepLink -> Bool in [weak self]
    // do some stuff
    return true
})
Router.setDeepLinkCatcher(deepLinkClosure, forRegex: "^cool_ass_stuff$", referenceStrength: .Strong)

let notificationClosure = PushNotificationClosureCatcher(callback: { notification in [weak self]
    // do some stuff
})
Router.setPushNotificationCatcher(notificationClosure, forRegex: "^cool_ass_stuff$", referenceStrength: .Strong)

## ⚠️ Memory management ⚠️

You can decide whether Stargate keeps a .Strong or a .Weak reference to your Catcher.

With closures you likely want to choose .Strong. Make sure you use [weak self] where appropriate.

Stargate achieves its memory management functionality by using NSMapTable.


## The delegate

If your ViewController or object that you'd like to handle the DeepLink and/or PushNotification does not exist at the time of the DeepLink or PushNotification being triggered, you can assign a delegate that will definitely be around (AppDelegate for example) to catch it and do any setup, like, for example, instantiate a ViewController and re-setup its views then call it's catchDeepLink or catchPushNotification method.

With DeepLinks you can also use the delegate to setup the view hierarchy when a ViewController exists but its view is not on screen. To do this just return false in the callback, like in the example above, which will cause the Router to call its delegate. The delegate gets passed the DeepLink, therefore it knows which ViewController to get on screen.

Example:

func catchDeepLink(deepLink: DeepLink) -> Bool {
    if deepLink.matchesRegex("^some/rad/shit$") {
        // Show a view controller or something
        return true
    }
    return false
}

Todo

  • Some kind of regex thing for router string
  • Fix delegate pointer strength
  • Replace NSMapTable with Swift implementation and then
  • Refactor code duplication in Router for setting and removing catchers

Authors