Setting tokens in Spotify iOS app disables login callback - ios

I am trying to set up the login for my iOS app using Spotify's SDK. I have the login working, but only without tokens. Once I add these two lines of code:
SPTAuth.defaultInstance().tokenSwapURL = NSURL(string: kTokenSwapURL)
SPTAuth.defaultInstance().tokenRefreshURL = NSURL(string: kTokenRefreshServiceURL)
The login does not work. This is my code for the login.
AppDelegate.swift
let kClientID = "my-client-id"
let kCallbackURL = "my-callback-url"
let kTokenSwapURL = "my-token-swap-url"
let kTokenRefreshServiceURL = "my-token-refresh-url"
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
// Override point for customization after application launch.
SPTAuth.defaultInstance().clientID = kClientID
SPTAuth.defaultInstance().redirectURL = NSURL(string: kCallbackURL)
SPTAuth.defaultInstance().requestedScopes = [SPTAuthStreamingScope, SPTAuthUserReadPrivateScope, SPTAuthPlaylistReadPrivateScope]
SPTAuth.defaultInstance().sessionUserDefaultsKey = "SpotifySession"
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let loginViewController = LoginViewController(nibName: "LogInViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: loginViewController)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
return true
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
let authCallback : SPTAuthCallback = { error, session in
// This is the callback that'll be triggered when auth is completed (or fails).
if (error != nil) {
print(error);
return;
}
let userDefaults = NSUserDefaults.standardUserDefaults()
let sessionData = NSKeyedArchiver.archivedDataWithRootObject(session)
userDefaults.setObject(sessionData, forKey: SPTAuth.defaultInstance().sessionUserDefaultsKey)
userDefaults.synchronize()
AuthHandler.sharedHandler.loginWithSession(session)
};
if SPTAuth.defaultInstance().canHandleURL(url) {
SPTAuth.defaultInstance().handleAuthCallbackWithTriggeredAuthURL(url, callback:authCallback)
return true
}
return false;
}
LoginViewController.swift
class LoginViewController: UIViewController {
let kClientID = "my-client-id"
let kCallbackURL = "my-callback-url"
let kTokenSwapURL = "my-token-swap-url"
let kTokenRefreshServiceURL = "my-token-refresh-url"
var session: SPTSession!
var logIn: UIButton!
var auth : SPTAuthViewController?
override func viewWillAppear(animated: Bool) {
// set login callback for what happens when session is got
AuthHandler.sharedHandler.setLoginCallback({ success in
if (success) {
self.transitionToPlaylistScreen()
}
})
// if session is still valid, login
let userDefaults = NSUserDefaults.standardUserDefaults()
if let sessionObj:AnyObject = userDefaults.objectForKey("SpotifySession") { // session available
let sessionDataObj = sessionObj as! NSData
let session = NSKeyedUnarchiver.unarchiveObjectWithData(sessionDataObj) as! SPTSession
if !session.isValid() {
SPTAuth.defaultInstance().renewSession(session, callback: { (error:NSError!, renewdSession:SPTSession!) -> Void in
if error == nil {
let sessionData = NSKeyedArchiver.archivedDataWithRootObject(session)
userDefaults.setObject(sessionData, forKey: SPTAuth.defaultInstance().sessionUserDefaultsKey)
userDefaults.synchronize()
self.session = renewdSession
AuthHandler.sharedHandler.loginWithSession(self.session!)
} else {
print(error.localizedDescription)
}
})
} else {
self.session = session
AuthHandler.sharedHandler.loginWithSession(self.session!)
}
}
}
override func viewDidLoad() {
// add observer for login success
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("transitionToPlaylistScreen"), name: "loginSuccess", object: nil)
// code to set up the login button
}
func transitionToPlaylistScreen() {
if (self.auth != nil) {
self.dismissViewControllerAnimated(true, completion: nil)
self.auth = nil
}
let playlistScreen = PlaylistViewController()
let navigation = UINavigationController(rootViewController: playlistScreen)
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(navigation, animated: true, completion: nil)
})
}
func loginToSpotify() {
// if session isn't valid, login within app
dispatch_async(dispatch_get_main_queue(), {
self.auth = SPTAuthViewController.authenticationViewController()
self.auth?.delegate = AuthHandler.sharedHandler
self.auth!.modalPresentationStyle = .OverCurrentContext
self.auth!.modalTransitionStyle = .CrossDissolve
self.modalPresentationStyle = .CurrentContext
self.definesPresentationContext = true
self.auth!.clearCookies({
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(self.auth!, animated: false, completion: nil)
})
})
})
}
}
AuthHandler.swift
class AuthHandler: NSObject, SPTAuthViewDelegate {
static let sharedHandler = AuthHandler()
var session: SPTSession?
var callback: (Bool -> Void)?
func setLoginCallback(callback: (Bool -> Void)) {
self.callback = callback
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didFailToLogin error: NSError!) {
if let function = callback {
function(false)
}
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didLoginWithSession session: SPTSession!) {
self.loginWithSession(session)
}
func authenticationViewControllerDidCancelLogin(authenticationViewController: SPTAuthViewController!) {
if let function = callback {
function(false)
}
}
func loginWithSession(session: SPTSession) {
self.session = session
SPTAuth.defaultInstance().session = session
if let function = callback {
function(true)
}
}
}

I guess that your backend (swap/refresh) server is not set up properly, because a not working server will cause log in to fail.
I recommend this repository, which you can set up a simple server on heroku.

What I would recomed you to try several things:
Could you please post what is in yours URL Types Identifier and URL Schemes?
I do not know is it important, but in my case Redirect URI and URL Schemes is the same. Redirect URI can be found here under My Applications.
Try to write second app which is opening your app using URL Scheme. If it will not work then here is problem in URL schemas.
As code snapshot is here:
let kCallbackURL = "myWhosampled://"
let url = NSURL(string: kCallbackURL)
UIApplication.sharedApplication().openURL(url!)
When generating files for tokens using spotify_token_swap.rb file you would need to set correct values for this:
CLIENT_ID = "e6695c6d22214e0f832006889566df9c"
CLIENT_SECRET = "29eb02041ba646179a1189dccac112c7"
ENCRYPTION_SECRET = "cFJLyifeUJUBFWdHzVbykfDmPHtLKLGzViHW9aHGmyTLD8hGXC"
CLIENT_CALLBACK_URL = "spotifyiossdkexample://"
AUTH_HEADER = "Basic " + Base64.strict_encode64(CLIENT_ID + ":" + CLIENT_SECRET)
SPOTIFY_ACCOUNTS_ENDPOINT = URI.parse("https://accounts.spotify.com")
set :port, 1234 # The port to bind to.
set :bind, '0.0.0.0' # IP address of the interface to listen on (all)

Related

How to open a link from Smooch chat at webView instead Safari browser Swift

I'm using Smooch library for integration chat into the app. In addition, I've implemented Universal Links.
Programming language - Swift / iOS version - 12.4+ / Smooch SDK version 7.12
Problem:
When I am tapping on the link from Smooch chat it opens me Safari browser instead webView!!!
Universal link works correctly, I can be redirected by tapping on the link from other Apps (WhatsApp, Safari, telegram ...)
But just taping in the link from the smooch chat redirects me to the Safari browser. How I can stay in the App?
Here main VC where I'm loading webView and button with Smooch chat:
override func viewDidLoad() {
super.viewDidLoad()
if let data = userDefaults.dictionary(forKey: "data"){
responseData = data
}
loadWebKit()
checkToken()
initSmooch()
setupChatBtnView()
}
//showing smooch chat view
#IBAction func chatBtnDidPress(_ sender: UIButton) {
Smooch.show()
}
//chat button design
func setupChatBtnView(){
chatBtnOutlet.layer.cornerRadius = chatBtnOutlet.bounds.self.width / 2.0
chatBtnOutlet.clipsToBounds = true
chatBtnOutlet.layer.shadowRadius = 1
chatBtnOutlet.layer.shadowColor = UIColor(red: 255/255, green: 170/255, blue: 0/255, alpha: 0.5).cgColor
chatBtnOutlet.layer.shadowOpacity = 0.5
chatBtnOutlet.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
chatBtnOutlet.layer.masksToBounds = false
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
Smooch.close()
}
//webkit view url load and adding js event listeners
func loadWebKit(){
webView.navigationDelegate = self
webView.configuration.userContentController.add(self, name: "finishLoading")
webView.configuration.userContentController.add(self, name: "logout")
webView.isHidden = true
activityIndicator.isHidden = false
if appDelegate.externalUrl == nil {
webView.load(URLRequest(url: URL(string: "https://example.com")!))
} else {
deepLinkURL = self.appDelegate.externalUrl!
let urlRequest = URLRequest(url: deepLinkURL!)
self.webView.load(urlRequest)
isFromDeepLing = true
webView.isHidden = true
activityIndicator.isHidden = false
print(urlRequest)
}
}
//when loading url finish call a js functions
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard let dataForJS = userDefaults.string(forKey: "dataJs") else {
self.webView.isHidden = false
self.activityIndicator.isHidden = true
return
}
if !isFromDeepLing {
webView.evaluateJavaScript("handleNativeLogin('\(dataForJS)')") { (result, error) in
self.webView.isHidden = false
self.activityIndicator.isHidden = true
}
} else {
if !isUsedDeepLink {
webView.evaluateJavaScript("handleNativeLogin('\(dataForJS)')") { (result, error) in
}
webView.isHidden = true
activityIndicator.isHidden = false
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
let urlRequest = URLRequest(url: self.deepLinkURL!)
self.webView.load(urlRequest)
self.isUsedDeepLink = true
}
} else {
self.webView.isHidden = false
self.activityIndicator.isHidden = true
}
}
}
//segue to login screen and logout from smooch
func goToLoginScreen(){
Smooch.logout {(error:Error?, userInfo:[AnyHashable : Any]?) in
}
userDefaults.removeObject(forKey: "data")
performSegue(withIdentifier: "loginViewController", sender: self);
}
// check if token is valid . if not, go to login screen
func checkToken(){
if let token = userDefaults.dictionary(forKey: "data")?["access_token"]{
serverManager.checkToken(token: token as! String) { (code) in
if(code == "200"){
print("token is valid")
}else if(code == "401"){
print("token expierd")
self.goToLoginScreen()
}
}
}
}
// connect to smooch service , init and login
func initSmooch(){
guard let appId = responseData["smoochAppId"] ,let userId = responseData["smoochUserId"], let smoochToken = responseData["smoochJWTToken"] else {
chatBtnOutlet.isHidden = true
return
}
Smooch.initWith(SKTSettings(appId: appId as! String)) { (error:Error?, userInfo:[AnyHashable : Any]?) in
if(error == nil){
print("smooch init success")
Smooch.login(userId as! String , jwt: smoochToken as! String, completionHandler: { (error:Error?, userInfo: [AnyHashable : Any]?) in
if(error == nil){
print("smooch user login success")
}else{
print("smooch user login failed")
}
})
}else{
print("smooch init failed")
}
}
}
// listen to js event
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "finishLoading"){
print("finishLoading")
}else if (message.name == "logout"){
userDefaults.set(false, forKey: "isLoggedIn")
self.goToLoginScreen()
}
}
And here appDelegate where I'm using Universal link:
// MARK: - Deep Linking
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
var viewController = UIViewController()
let userDefaults = UserDefaults.standard
let isLoggedIn = userDefaults.bool(forKey: "isLoggedIn")
if url.path == "/login" || isLoggedIn == false {
viewController = self.getDestinationLogin(for: url)
} else {
viewController = self.getDestinationMain(for: url)
}
externalUrl = url
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return (parseAppLinks(from: url) != nil)
}
func getDestinationLogin(for url: URL) -> UIViewController {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let loginVC = storyboard.instantiateViewController(withIdentifier: "loginViewController") as? LoginViewController
return loginVC!
}
func getDestinationMain(for url: URL) -> UIViewController {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let mainVC = storyboard.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
let userDefaults = UserDefaults.standard
userDefaults.set(true, forKey: "isLoggedIn")
return mainVC!
}
private func parseAppLinks(from url: URL) -> String? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return .none }
guard let urlString = components.queryItems?.first?.value else { return .none }
return urlString
}

Thread Error in Xcode w/ Swift When Launching New Screen

Once the user logs into my app, I set the window, set the rootViewController and makeKeyAndVisible and when I do I get a Thread 9: signal SIGABRT error. FWIW, I get a purple warning -[UIWindow initWithFrame:] must be used from main thread only when setting self.window = UIWindow(frame: UIScreen.main.bounds). See code below to see my setup.
The code dies with that error right here - self.window!.makeKeyAndVisible() in the launchWindow(aWindow: UIWindow?) function below in AppController.swift.
AppDelegate.swift
//
// AppDelegate.swift
import UIKit
import AWSCognitoAuth
import AWSSNS
import AWSCognitoIdentityProvider
import UserNotifications
import ESTabBarController_swift
import AWSMobileClient
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
var navigationController: UINavigationController?
var storyboard: UIStoryboard?
var loginViewController: LoginViewController?
var pool = AWSCognitoIdentityUserPool.init(forKey: "UserPool")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppController.sharedInstance.enableCognitoClientWithAuthentication()
// setup logging
// pool.delegate = self
// AWSDDLog.sharedInstance.logLevel = .verbose
// let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1,
// identityPoolId: Constants.APIKeys.AWSIdentityPoolID)
//
// // setup service configuration
// let serviceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
//
// // create pool configuration
// let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.APIKeys.AWSClientID,
// clientSecret: Constants.APIKeys.AWSSecret,
// poolId: Constants.APIKeys.AWSPoolID)
// AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
// // initialize user pool client
// AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: "UserPool")
//
// pool.currentUser()?.getSession()
// fetch the user pool client we initialized in above step
//let pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
let signedIn = AWSMobileClient.sharedInstance().isSignedIn
self.navigationController = UINavigationController()
if !signedIn {
navigationInit()
}
// } else {
// AppController.sharedInstance.showLoggedInStateAndReturn(true)
// }
//pool.delegate = self
self.window = UIWindow(frame: UIScreen.main.bounds)
AppController.sharedInstance.launchInWindow(aWindow: self.window)
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
navigationInit()
return true
}
func navigationInit() {
let loginViewController = LoginViewController()
self.navigationController!.pushViewController(loginViewController, animated: false)
}
//MARK: Push Notification
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
/// Attach the device token to the user defaults
var token = ""
for i in 0..<deviceToken.count {
token = token + String(format: "%02.2hhx", arguments: [deviceToken[i]])
}
print(token)
UserDefaults.standard.set(token, forKey: "deviceTokenForSNS")
/// Create a platform endpoint. In this case, the endpoint is a
/// device endpoint ARN
let sns = AWSSNS.default()
let request = AWSSNSCreatePlatformEndpointInput()
request?.token = token
request?.platformApplicationArn = Constants.APIKeys.AWSSSNSARN
sns.createPlatformEndpoint(request!).continueWith(executor: AWSExecutor.mainThread(), block: { (task: AWSTask!) -> AnyObject? in
if task.error != nil {
print("Error: \(String(describing: task.error))")
} else {
let createEndpointResponse = task.result! as AWSSNSCreateEndpointResponse
if let endpointArnForSNS = createEndpointResponse.endpointArn {
print("endpointArn: \(endpointArnForSNS)")
Settings.setPushArn(endpointArnForSNS)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "RegisteredForPush"), object: nil)
}
}
return nil
})
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let visible = window?.visibleViewController()
if let data = userInfo["aps"] as? [AnyHashable: Any] {
if let route = data["route"] as? String {
switch route {
case "matchRequested":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
var matchId = ""
if let match = data["matchId"] as? String {
matchId = match
}
let projectMatches = MatchRequestedViewController(withNotificationPayload: projectId, matchId: matchId)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
case "projectDetails":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
let projectMatches = ProjectDetailsViewController(withProject: TERMyProject(), orProjectId: projectId)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
case "matched":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
var matchId = ""
if let match = data["matchId"] as? String {
matchId = match
}
var originProject: TERMyProject = TERMyProject()
var matchedProject: TERMatchedProject = TERMatchedProject()
AppController.sharedInstance.AWSClient?.projectsGet(id:projectId).continueWith(block: { (task: AWSTask) -> Any? in
if let error = task.error {
print("Error: \(error)")
} else if let result = task.result {
if result is NSDictionary {
DispatchQueue.main.async {
let array = [result]
let parsedProject: [TERMyProject] = TERMyProject.parseFromAPI(array:array as! [NSDictionary])
for project in parsedProject {
originProject = project
}
// self.initialSetup()
}
AppController.sharedInstance.AWSClient?.projectsGet(id:matchId).continueWith(block: { (task: AWSTask) -> Any? in
if let error = task.error {
print("Error: \(error)")
} else if let result = task.result {
if result is NSDictionary {
DispatchQueue.main.async {
let array = [result]
let parsedProject: [TERMatchedProject] = TERMatchedProject.parseFromAPI(array:array as! [NSDictionary])
for project in parsedProject {
matchedProject = project
}
let projectMatches = MatchedViewController(withProject: originProject, match: matchedProject, isComplete: false)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
}
}
}
return nil
})
}
}
return nil
})
default:
break
}
}
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error.localizedDescription)
}
// Called when a notification is delivered to a foreground app.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("User Info = ",notification.request.content.userInfo)
completionHandler([.alert, .badge, .sound])
}
//MARK: Boiler-plate methods
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
// func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
// if (self.navigationController == nil) {
//
// self.navigationController = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? UINavigationController
// }
//
// if (self.loginViewController == nil) {
// self.loginViewController = self.navigationController?.viewControllers[0] as? LoginViewController
// }
//
// DispatchQueue.main.async {
// self.navigationController!.popToRootViewController(animated: true)
// if (!self.navigationController!.isViewLoaded
// || self.navigationController!.view.window == nil) {
// self.window?.rootViewController?.present(self.navigationController!,
// animated: true,
// completion: nil)
// }
//
// }
// return self.loginViewController!
// }
}
// MARK:- AWSCognitoIdentityRememberDevice protocol delegate
extension AppDelegate: AWSCognitoIdentityRememberDevice {
func didCompleteStepWithError(_ error: Error?) {
}
func getRememberDevice(_ rememberDeviceCompletionSource: AWSTaskCompletionSource<NSNumber>) {
}
}
extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
switch(vc){
case is UINavigationController:
let navigationController = vc as! UINavigationController
return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)
break;
case is UITabBarController:
let tabBarController = vc as! UITabBarController
return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)
break;
default:
if let presentedViewController = vc.presentedViewController {
//print(presentedViewController)
if let presentedViewController2 = presentedViewController.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController2)
}
else{
return vc;
}
}
else{
return vc;
}
break;
}
}
}
AppController.swift
func launchInWindow(aWindow: UIWindow?){
self.window = aWindow
self.initializeSDKs()
self.globalCustomization()
self.AWSUnAuthedClient.apiKey = Constants.APIKeys.AWSAPIKey
self.window!.rootViewController = self.showLoggedInStateAndReturn(true)
self.window!.makeKeyAndVisible()
}
func initializeSDKs() {
// Google places
GMSPlacesClient.provideAPIKey(Constants.APIKeys.GooglePlaces)
}
func globalCustomization() {
self.styleNavigationBar()
}
#discardableResult func showLoggedInStateAndReturn(_ shouldReturn: Bool) -> UIViewController? {
//AppController.sharedInstance.enableCognitoClientWithAuthentication()
//self.registerForPush()
self.tabBarController = ESTabBarController()
//tabBarController.delegate = delegate
self.tabBarController?.title = "Irregularity"
self.tabBarController?.tabBar.shadowImage = UIImage.image(with: UIColor("FFFFFF", alpha: 0.0)!)
self.tabBarController?.tabBar.backgroundImage = UIImage.image(with: UIColor("2A2A27")!)
self.tabBarController?.shouldHijackHandler = {
tabbarController, viewController, index in
if index == 1 {
return true
}
return false
}
self.tabBarController?.didHijackHandler = {
[weak tabBarController] tabbarController, viewController, index in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
let newProjectNavCon = UINavigationController(rootViewController: NewProjectViewController())
newProjectNavCon.hero.isEnabled = true
newProjectNavCon.setNavigationBarHidden(true, animated: false)
newProjectNavCon.hero.navigationAnimationType = .fade
tabBarController?.present(newProjectNavCon, animated: true, completion: nil)
}
}
let centerVC = UINavigationController(rootViewController: HomeViewController())
let v1 = centerVC
let v2 = BaseViewController()
let v3 = UINavigationController(rootViewController: ProfileViewController())
v1.tabBarItem = ESTabBarItem.init(TabBarBouncesContentView(), title: "Projects", image: UIImage(named: "tabBar"), selectedImage: UIImage(named: "tabBar"))
v2.tabBarItem = ESTabBarItem.init(TabBarIrregularityContentView(), title: nil, image: UIImage(named: "tabBarPlusButton"), selectedImage: UIImage(named: "tabBarPlusButton"))
v3.tabBarItem = ESTabBarItem.init(TabBarBouncesContentView(), title: "Profile", image: UIImage(named: "tabBarProfile"), selectedImage: UIImage(named: "tabBarProfile"))
self.tabBarController?.setViewControllers([v1, v2, v3], animated: true)
if shouldReturn {
return self.tabBarController
} else {
self.window?.rootViewController = self.tabBarController
return nil
}
}
You should try to execute the piece of code in the main thread:
func launchInWindow(aWindow: UIWindow?){
self.window = aWindow
self.initializeSDKs()
self.globalCustomization()
self.AWSUnAuthedClient.apiKey = Constants.APIKeys.AWSAPIKey
DispatchQueue.main.async {
self.window!.rootViewController = self.showLoggedInStateAndReturn(true)
self.window!.makeKeyAndVisible()
}
}
Also, your question codebase looks a little bit weird in this part in AppDelegate.swift:
self.window = UIWindow(frame: UIScreen.main.bounds) //-- Purple warning here
AppController.sharedInstance.launchInWindow(aWindow: self.window)
return true
Looks like those three lines are not in a function but in class scope, which is weird and you shouldn't be able to compile it. Probably you pasted your code wrong?

I can't get this delegate to fire and I'm not sure what I'm missing

I've got a login screen that authenticates with Amazon Cognito using the SDK. Once complete, it's supposed to call a delegate (or extension in iOS Swift). It is supposed to call the didCompleteWithError or the getDetails methods.
I've tried taking the examples I've been trying to follow this example but I haven't had luck (https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift). Any ideas? See my code for just the log in screen and AppDelegate.swift below. What am I doing wrong?
// LoginViewController.swift
import UIKit
import AWSCognitoIdentityProvider
import AWSCognitoAuth
import AWSMobileClient
import AWSUserPoolsSignIn
import AWSAuthUI
class LoginViewController: BaseViewController {
#IBOutlet var password: UITextField!
#IBOutlet var email: UITextField!
var pool: AWSCognitoIdentityUserPool?
var passwordAuthenticationCompletion: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>?
var usernameText: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.password.text = nil
self.email.text = usernameText
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
#IBAction func login_Tap(_ sender: Any) {
if (self.email.text != nil && self.password.text != nil) {
let authDetails = AWSCognitoIdentityPasswordAuthenticationDetails(username: self.email.text!, password: self.password.text! )
self.passwordAuthenticationCompletion?.set(result: authDetails)
} else {
let alertController = UIAlertController(title: "Missing information",
message: "Please enter a valid user name and password",
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
}
}
}
extension LoginViewController: AWSCognitoIdentityPasswordAuthentication {
public func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
print(passwordAuthenticationCompletionSource)
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
DispatchQueue.main.async {
if (self.usernameText == nil) {
self.usernameText = authenticationInput.lastKnownUsername
}
}
}
public func didCompleteStepWithError(_ error: Error?) {
print(error)
DispatchQueue.main.async {
if let error = error as NSError? {
let alertController = UIAlertController(title: error.userInfo["__type"] as? String,
message: error.userInfo["message"] as? String,
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
} else {
self.email.text = nil
self.dismiss(animated: true, completion: nil)
}
}
}
}
AppDelegate.swift I have this
//
// AppDelegate.swift
import UIKit
import AWSCognitoAuth
import AWSSNS
import AWSCognitoIdentityProvider
import UserNotifications
import ESTabBarController_swift
import AWSMobileClient
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
var navigationController: UINavigationController?
var storyboard: UIStoryboard?
var loginViewController: LoginViewController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// setup logging
AWSDDLog.sharedInstance.logLevel = .verbose
// setup service configuration
let serviceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: nil)
// create pool configuration
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.APIKeys.AWSClientID,
clientSecret: Constants.APIKeys.AWSSecret,
poolId: Constants.APIKeys.AWSPoolID)
// initialize user pool client
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: "UserPool")
// fetch the user pool client we initialized in above step
let pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
self.window = UIWindow(frame: UIScreen.main.bounds)
self.storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
pool.delegate = self
AppController.sharedInstance.launchInWindow(aWindow: self.window)
return true
}
//MARK: Push Notification
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
/// Attach the device token to the user defaults
var token = ""
for i in 0..<deviceToken.count {
token = token + String(format: "%02.2hhx", arguments: [deviceToken[i]])
}
print(token)
UserDefaults.standard.set(token, forKey: "deviceTokenForSNS")
/// Create a platform endpoint. In this case, the endpoint is a
/// device endpoint ARN
let sns = AWSSNS.default()
let request = AWSSNSCreatePlatformEndpointInput()
request?.token = token
request?.platformApplicationArn = Constants.APIKeys.AWSSSNSARN
sns.createPlatformEndpoint(request!).continueWith(executor: AWSExecutor.mainThread(), block: { (task: AWSTask!) -> AnyObject? in
if task.error != nil {
print("Error: \(String(describing: task.error))")
} else {
let createEndpointResponse = task.result! as AWSSNSCreateEndpointResponse
if let endpointArnForSNS = createEndpointResponse.endpointArn {
print("endpointArn: \(endpointArnForSNS)")
Settings.setPushArn(endpointArnForSNS)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "RegisteredForPush"), object: nil)
}
}
return nil
})
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let visible = window?.visibleViewController()
if let data = userInfo["aps"] as? [AnyHashable: Any] {
if let route = data["route"] as? String {
switch route {
case "matchRequested":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
var matchId = ""
if let match = data["matchId"] as? String {
matchId = match
}
let projectMatches = MatchRequestedViewController(withNotificationPayload: projectId, matchId: matchId)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
case "projectDetails":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
let projectMatches = ProjectDetailsViewController(withProject: TERMyProject(), orProjectId: projectId)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
case "matched":
var projectId = ""
if let project = data["projectId"] as? String {
projectId = project
}
var matchId = ""
if let match = data["matchId"] as? String {
matchId = match
}
var originProject: TERMyProject = TERMyProject()
var matchedProject: TERMatchedProject = TERMatchedProject()
AppController.sharedInstance.AWSClient?.projectsGet(id:projectId).continueWith(block: { (task: AWSTask) -> Any? in
if let error = task.error {
print("Error: \(error)")
} else if let result = task.result {
if result is NSDictionary {
DispatchQueue.main.async {
let array = [result]
let parsedProject: [TERMyProject] = TERMyProject.parseFromAPI(array:array as! [NSDictionary])
for project in parsedProject {
originProject = project
}
// self.initialSetup()
}
AppController.sharedInstance.AWSClient?.projectsGet(id:matchId).continueWith(block: { (task: AWSTask) -> Any? in
if let error = task.error {
print("Error: \(error)")
} else if let result = task.result {
if result is NSDictionary {
DispatchQueue.main.async {
let array = [result]
let parsedProject: [TERMatchedProject] = TERMatchedProject.parseFromAPI(array:array as! [NSDictionary])
for project in parsedProject {
matchedProject = project
}
let projectMatches = MatchedViewController(withProject: originProject, match: matchedProject, isComplete: false)
visible?.navigationController?.pushViewController(projectMatches, animated: true)
}
}
}
return nil
})
}
}
return nil
})
default:
break
}
}
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error.localizedDescription)
}
// Called when a notification is delivered to a foreground app.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("User Info = ",notification.request.content.userInfo)
completionHandler([.alert, .badge, .sound])
}
//MARK: Boiler-plate methods
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
if (self.navigationController == nil) {
self.navigationController = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? UINavigationController
}
if (self.loginViewController == nil) {
self.loginViewController = self.navigationController?.viewControllers[0] as? LoginViewController
}
DispatchQueue.main.async {
self.navigationController!.popToRootViewController(animated: true)
if (!self.navigationController!.isViewLoaded
|| self.navigationController!.view.window == nil) {
self.window?.rootViewController?.present(self.navigationController!,
animated: true,
completion: nil)
}
}
return self.loginViewController!
}
}
// MARK:- AWSCognitoIdentityRememberDevice protocol delegate
extension AppDelegate: AWSCognitoIdentityRememberDevice {
func didCompleteStepWithError(_ error: Error?) {
}
func getRememberDevice(_ rememberDeviceCompletionSource: AWSTaskCompletionSource<NSNumber>) {
}
}
extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
switch(vc){
case is UINavigationController:
let navigationController = vc as! UINavigationController
return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)
break;
case is UITabBarController:
let tabBarController = vc as! UITabBarController
return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)
break;
default:
if let presentedViewController = vc.presentedViewController {
//print(presentedViewController)
if let presentedViewController2 = presentedViewController.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController2)
}
else{
return vc;
}
}
else{
return vc;
}
break;
}
}
}

I can't play music with Spotify iOS SDK with Swift 4.0

I have a struggle with Spotify SDK, I followed every step correctly, but I can't play music with my premium account on my project. There is no error or crash, my app directs me to the Spotify login page and after facebook login, It brings me back to my app again. Yesterday I'd get "logged in" print but today I can't. I'm trying to play a song after login and also manually as you see below. I'm wondering, am I lucky to find an answer?
override func viewDidLoad() {
super.viewDidLoad()
self.spotify()
NotificationCenter.default.addObserver(self, selector: #selector(GeneralNewsViewController.updateAfterFirstLogin), name: NSNotification.Name(rawValue: "SpotifySession"), object: nil)
}
func spotify() {
// insert redirect your url and client ID below
auth.redirectURL = URL(string: "correctCallbackURl")
auth.clientID = "correctClientID"
auth.requestedScopes = [SPTAuthStreamingScope, SPTAuthPlaylistReadPrivateScope, SPTAuthPlaylistModifyPublicScope, SPTAuthPlaylistModifyPrivateScope]
loginUrl = auth.spotifyWebAuthenticationURL()
}
func initializaPlayer(authSession:SPTSession){
if self.player == nil {
self.player = SPTAudioStreamingController.sharedInstance()
self.player!.playbackDelegate = self
self.player!.delegate = self
try! player?.start(withClientId: auth.clientID)
self.player!.login(withAccessToken: authSession.accessToken)
}
}
#objc func updateAfterFirstLogin () {
loginButton.isHidden = true
let userDefaults = UserDefaults.standard
if let sessionObj:AnyObject = userDefaults.object(forKey: "SpotifySession") as AnyObject? {
let sessionDataObj = sessionObj as! Data
let firstTimeSession = NSKeyedUnarchiver.unarchiveObject(with: sessionDataObj) as! SPTSession
self.session = firstTimeSession
initializaPlayer(authSession: session)
self.loginButton.isHidden = true
// self.loadingLabel.isHidden = false
}
}
func audioStreamingDidLogin(_ audioStreaming: SPTAudioStreamingController!) {
// after a user authenticates a session, the SPTAudioStreamingController is then initialized and this method called
print("logged in")
self.player?.playSpotifyURI("spotify:track:4aDLPXlzHZm26GppvRwms8", startingWith: 0, startingWithPosition: 0, callback: { (error) in
if (error != nil) {
print("playing!")
} else {
print(error?.localizedDescription)
}
})
}
#objc func play() {
player?.playSpotifyURI("spotify:track:4aDLPXlzHZm26GppvRwms8", startingWith: 0, startingWithPosition: 0, callback: { (err) in
if err != nil {
print(err?.localizedDescription)
} else {
}
})
}
class AppDelegate: UIResponder, UIApplicationDelegate ,UNUserNotificationCenterDelegate{
auth.redirectURL = URL(string: "correctCallbackURl")
auth.sessionUserDefaultsKey = "current session"}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// 2- check if app can handle redirect URL
if auth.canHandle(auth.redirectURL) {
// 3 - handle callback in closure
auth.handleAuthCallback(withTriggeredAuthURL: url, callback: { (error, session) in
// 4- handle error
if error != nil {
print("error!")
}
// 5- Add session to User Defaults
let userDefaults = UserDefaults.standard
let sessionData = NSKeyedArchiver.archivedData(withRootObject: session!)
userDefaults.set(sessionData, forKey: "SpotifySession")
userDefaults.synchronize()
// 6 - Tell notification center login is successful
NotificationCenter.default.post(name: Notification.Name(rawValue: "loginSuccessfull"), object: nil)
})
return true
}
return false
}}
It looks like you're subscribed to the wrong notification, you're subscribing to "SpotifySession" but posting "loginSuccessfull"
this answer will help you making this kind of mistake in the future by having enums for events in a simple way.
good debugging is also key, you could've probably solved this problem with some breakpoints to see where you went wrong.
cheers

Swift - AWS Cognito-Facebook Login

I want to use Facebook for my login process with Cognito, and I've followed a lot of AWS documentation and look at tutorials and questions in Stackoverflow, but I've not found a solution for my problem.
When the user opens the app, it will check if the user is logged in using IdentityManager. If not, it will open a new view where the user can sign in using Facebook using Facebook SDK. After that, I stored the token with a custom IdentityProvider as the documentation said (credentialsProvider.logins is deprecated). Everything seems to work fine, but every time I reopen the application, my session isn't restored.
I found out that if I use AWSIdentityManager.defaultIdentityManager().resumeSessionWithCompletionHandler(handler)I restored my session, but in case the user isn't logged in, it doesn't show my custom login screen as expected, but a Safari web view to Facebook.
Here is my code:
AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let identityManager = AWSIdentityManager.defaultIdentityManager()
identityManager.resumeSessionWithCompletionHandler({
(result, error) -> Void in
if !identityManager.loggedIn {
let mainStoryboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewControlleripad : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("SignIn") as UIViewController
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = initialViewControlleripad
self.window?.makeKeyAndVisible()
}
})
return AWSMobileClient.sharedInstance.didFinishLaunching(application, withOptions: launchOptions)
}
SignInViewController.swift
#IBAction func openFacebookLoginScreen(sender: AnyObject) {
FBSDKLoginManager().logInWithReadPermissions(FACEBOOK_PERMISSIONS, fromViewController: self, handler: { (result, error) -> Void in
if error == nil {
let fbLoginResult : FBSDKLoginManagerLoginResult = result
if fbLoginResult.isCancelled {
print("Cancelled")
}
else {
if FBSDKAccessToken.currentAccessToken() != nil {
self.signInFacebook(FBSDKAccessToken.currentAccessToken().tokenString)
self.dismissSignInView()
}
}
}
})
}
func signInFacebook(fbToken: String){
let logins = [AWSIdentityProviderFacebook : fbToken]
let customProviderManager = CustomIdentityProvider(tokens: logins)
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType:.USEast1,
identityPoolId: COGNITO_IDENTITY_POOL_ID,
identityProviderManager: customProviderManager)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
}
class CustomIdentityProvider: NSObject, AWSIdentityProviderManager {
var tokens : [NSString : NSString]?
init(tokens: [NSString : NSString]) {
self.tokens = tokens
}
#objc func logins() -> AWSTask {
return AWSTask(result: tokens)
}
}
Apparently my problem was that I was calling IdentityManager.loggedIn inside AppDelegate.swift, so I move it to viewDidLoad() on my main view controller.
Also I changed my sign in code to:
#IBAction func openFacebookLoginScreen(_: AnyObject) {
handleLoginWithSignInProvider(AWSFacebookSignInProvider.sharedInstance())
}
func handleLoginWithSignInProvider(signProvider: AWSSignInProvider){
AWSIdentityManager.defaultIdentityManager().loginWithSignInProvider(signProvider) { (result, error) in
if(error == nil){
let logins = [AWSIdentityProviderFacebook : FBSDKAccessToken.currentAccessToken().tokenString!]
let customProviderManager = CustomIdentityProvider(tokens: logins)
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType:.USEast1,
identityPoolId: self.COGNITO_IDENTITY_POOL_ID,
identityProviderManager: customProviderManager)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
self.dismissSignInView()
}
}
}

Resources