SafariViewController: How to grab OAuth token from URL?

iOS 12+

iOS 12 Beta already deprecates SFAuthenticationSession (see below) in favor of ASWebAuthenticationSession. It looks like it is used exactly the same way but requires the new AuthenticationServices framework.

iOS 11

iOS 11 introduced SFAuthenticationSession which is so much easier to handle. Given its nature this beta API may still change but there already are a couple of examples (1, 2) on the internet. First, you need a completion handler that is called with the result of the authentication request:

let completion : SFAuthenticationSession.CompletionHandler = { (callBack:URL?, error:Error?) in
    guard error == nil, let successURL = callBack else {
        return
    }

    let oauthToken = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "oauth_token"}).first

    // Do what you have to do...
}

Then you simply create a SFAuthenticationSession and start it.

let authURL = "https://the.service.you/want/toAuthorizeWith?..."
let scheme = "YOURSCHEME://"
let authSession = SFAuthenticationSession(url: authURL, callbackURLScheme: scheme, completionHandler: completion)
authSession.start()

iOS 10 and before

As some have noted in the comments, the accepted answer is incomplete and won't work on its own. When strolling through Strawberry Code's blog post one can find a link to the related GitHub project. That project's README.MD explains a crucial part of the setup, namely adding the redirect URI to Info.plist. So the whole thing goes down like this:

AppDelegate.swift

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {

   if let sourceApplication = options[.sourceApplication] {
       if (String(describing: sourceApplication) == "com.apple.SafariViewService") {
            NotificationCenter.default.post(name: Notification.Name("CallbackNotification"), object: url)
            return true
        }
    }

    return false
}

ViewController.swift

Register for the notification to call your handler in some sensible place. I'd recommend not doing it in viewDidLoad() but only before you are actually presenting the SFSafariViewController.

NotificationCenter.default.addObserver(self, selector: #selector(safariLogin(_:)), name: Notification.Name("CallbackNotification"), object: nil)
let safariVC = SFSafariViewController(URL: authURL)
safariVC.delegate = self
self.present(safariVC, animated: true, completion: nil)

And then remove the observance in the handler:

@objc func safariLogin(_ notification : Notification) {

    NotificationCenter.default.removeObserver(self, name: Notification.Name("CallbackNotification"), object: nil)

    guard let url = notification.object as? URL else {
        return
    }

    // Parse url ...

}

Remember that the user is able to dismiss the SFSafariViewController by tapping the Done button, so be sure to adopt the SFSafariViewControllerDelegate protocol and remove the observance like this as well:

func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
    NotificationCenter.default.removeObserver(self, name: Notification.Name("CallbackNotification"), object: nil)
}

Info.plist

To make it all work you need to add your redirect URI scheme to Info.plist:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>com.YOUR.BUNDLE.IDENTIFIER</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>YOURSCHEME</string>
        </array>
    </dict>
</array>

Of course YOURSCHEME has to match the scheme of the redirect URI you registered with the web service you're trying to oAuthorizing with.