I want to make a timer that start on app load and when it finishes to alert a text and restart the app when alert is closed(when click the ok button of the alert). Have tried with a class timer but I can't send alert and reload the app. I need it to be working in the whole app, and when move to other view not restart it countdown. Is there any easy way without using CocoaPods.
import Foundation
class GlobalTokenTimer {
static let sharedTimer: GlobalTokenTimer = {
let timer = GlobalTokenTimer()
return timer
}()
var internalTimer: Timer?
var jobs = [() -> Void]()
func startTimer(withInterval interval: Double, andJob job: #escaping () -> Void) {
if internalTimer != nil {
internalTimer?.invalidate()
}
jobs.append(job)
internalTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(doJob), userInfo: nil, repeats: true)
}
func stopTimer() {
guard internalTimer != nil else {
print("No timer active, start the timer before you stop it.")
return
}
jobs = [()->()]()
internalTimer?.invalidate()
}
#objc func doJob() {
guard jobs.count > 0 else{return}
}
}
Your timer is static and will work while app is alive. Implement some function that will show alert on topViewController of your application.
extension UIApplication {
// function that lets you get topViewController of the app
func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
Inside your app delegate start your timer and show alert. In iOS you can't restart you app, but can set newly created MyStartViewController which is your home page for example and restart logic of your app.
UPD:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func startTimer() {
GlobalTokenTimer.sharedTimer.startTimer(withInterval: 10) {
let alertVC = UIAlertController(title: "Restart your app!", message: "Select ok!", preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "Restart", style: .default) { [weak self] _ in
self?.window?.rootViewController = MyStartViewController()
startTimer()
})
application.topViewController()?.present(alertVC, animated: true)
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
startTimer()
}
}
Set your timer repeat: false and it would stop after finishing once.
internalTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(doJob), userInfo: nil, repeats: false)
UPD:
You do nothing in your doJob function, change it to this.
#objc func doJob() {
guard jobs.count > 0 else { return }
jobs.forEach { $0() }
}
Related
I have a basic application for iPads which consists of 4 views. Across the whole app, I want to be able to detect user inactivity and then display an Alert after 4 minutes which will ask the user if they are still there.
I have found some useful resources for the Timer and Alert functions. I have played around with these tutorials and can get the Timer working on it's own. However, this is my first time developing in Swift so I would like some guidance on the best way to connect the Timer to the Alert is? I would like the Timer to run for 4 minutes and then create the Alert.
I would also like to know where the best place is to put the code for these elements so that they work across all 4 of my views. E.g is there a single place I can put the code and reuse it rather than having it repeated in each of the 4 views?
First you can follow the instructions here to set up your timer: https://blog.gaelfoppolo.com/detecting-user-inactivity-in-ios-application-684b0eeeef5b
Then you can do something like this to handle the timeout notifications:
extension UIViewController {
func observeTimeout() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleTimeout),
name: .appTimeout,
object: nil)
}
#objc func handleTimeout() {
let alert = UIAlertController(title: "Timeout", message: "Oh no!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
}))
present(alert, animated: true, completion: nil)
}
}
And then in each of your view controllers do this to register for the timeout:
class SomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
observeTimeout()
}
}
It's been a while since this question was asked however, since I came across the same problem I slightly changed the answer #rob-c provided using this guide:
Tested on Swift 5.5 and iOS 15.1
1- Create a subclass of UIApplication
import Foundation
import UIKit
class TimerApplication: UIApplication {
private var timeoutInSeconds: TimeInterval {
return 40.0
}
private var idleTimer: Timer?
override init() {
super.init()
resetIdleTimer()
}
private func resetIdleTimer() {
if let idleTimer = idleTimer {
idleTimer.invalidate()
}
idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds,
target: self,
selector: #selector(TimerApplication.timeHasExceeded),
userInfo: nil,
repeats: false
)
}
#objc private func timeHasExceeded() {
NotificationCenter.default.post(name: .appTimeout, object: nil)
}
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
if idleTimer != nil {
self.resetIdleTimer()
}
if let touches = event.allTouches {
for touch in touches where touch.phase == UITouch.Phase.began {
self.resetIdleTimer()
}
}
}
}
2- Add notification name
import Foundation
extension Notification.Name {
static let appTimeout = Notification.Name("appTimeout")
}
3- Create main.swift file and add this
import Foundation
import UIKit
/// NOTE: comment out #UIApplicationMain in AppDelegate
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
NSStringFromClass(TimerApplication.self),
NSStringFromClass(AppDelegate.self)
)
4- Remove #UIApplicationMain from AppDelegate
5- Add observer for the notification
Add the observer where appropriate for your case, in AppDelegate or any view controller:
func addObservers() {
NotificationCenter.default.addObserver(self,
selector: #selector(idleTimeLimitReached(_:)),
name: .appTimeout,
object: nil)
}
#objc func idleTimeLimitReached(_ notification: Notification) {
print("***** IDLE Time called")
}
You can run Timer in AppDelegate itself and display alert like below from AppDelegate
func showAlertFromAppDelegates(){
let alertVC = UIAlertController(title: "Oops" , message: "Presented Alert from AppDelegates", preferredStyle: UIAlertController.Style.alert)
let okAction = UIAlertAction(title: "Okay", style: UIAlertAction.Style.cancel) { (alert) in
}
alertVC.addAction(okAction)
var presentVC = self.window!.rootViewController
while let next = presentVC?.presentedViewController
{
presentVC = next
}
presentVC?.present(alertVC, animated: true, completion: nil)
}
Edit:
Timer related code added, call startTimer() when you want to start inactivity and call stopTimer() when inactivity ends
func startTimer()
{
let interval = 4*60
timer = Timer.scheduledTimer(timeInterval: TimeInterval(interval), target: self, selector: #selector(self.showAlertFromAppDelegates), userInfo: nil, repeats: true)
}
func stopTimer()
{
if let timer = timer
{
timer.invalidate()
}
timer = nil
}
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've integrated cognito into my xcode project. The sign up/password update features are working correctly. However I can't seem to get the sign in process to work. I turned on the logs and I get the following error
{"__type":"NotAuthorizedException","message":"Access Token has expired"}
Domain=com.amazonaws.AWSCognitoIdentityProviderErrorDomain Code=-1000 "Authentication delegate not set" UserInfo={NSLocalizedDescription=Authentication delegate not set}]
I have also implemented the AWSCognitoIdentityInteractiveAuthenticationDelegate delegate in the AppDelegate script.
Here's the AppDelegate code
class AppDelegate: UIResponder, UIApplicationDelegate {
class func defaultUserPool() -> AWSCognitoIdentityUserPool {
return AWSCognitoIdentityUserPool(forKey: userPoolID)
}
var window: UIWindow?
var loginViewController: LoginVC?
var navigationController: UINavigationController?
var storyboard: UIStoryboard?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Warn user if configuration not updated
if (CognitoIdentityUserPoolId == "us-east-1_TavWWBZtI") {
let alertController = UIAlertController(title: "Invalid Configuration",
message: "Please configure user pool constants in Constants.swift file.",
preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
alertController.addAction(okAction)
self.window?.rootViewController!.present(alertController, animated: true, completion: nil)
//print("Please configure user pool constants in Constants.swift file.")
}
// setup logging
AWSDDLog.sharedInstance.logLevel = .verbose
AWSDDLog.add(AWSDDTTYLogger.sharedInstance)
// setup service configuration
let serviceConfiguration = AWSServiceConfiguration(region: CognitoIdentityUserPoolRegion, credentialsProvider: nil)
// create pool configuration
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoIdentityUserPoolAppClientId,
clientSecret: CognitoIdentityUserPoolAppClientSecret,
poolId: CognitoIdentityUserPoolId)
// initialize user pool client
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
// fetch the user pool client we initialized in above step
let pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
self.storyboard = UIStoryboard(name: "Main", bundle: nil)
pool.delegate = self
return AWSMobileClient.sharedInstance().interceptApplication(
application, didFinishLaunchingWithOptions:
launchOptions)
//return true
}
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let navigationController = self.window?.rootViewController as? UINavigationController {
if navigationController.visibleViewController is SummaryReportVC ||
navigationController.visibleViewController is GoalStatusReportVC || navigationController.visibleViewController is YearTotalsReportVC || navigationController.visibleViewController is DailyActivityReportVC {
return UIInterfaceOrientationMask.all
} else {
return UIInterfaceOrientationMask.portrait
}
}
return UIInterfaceOrientationMask.portrait
}
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) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
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 {
print("Calling signin VC from app delegate")
if (self.navigationController == nil) {
self.navigationController = self.storyboard?.instantiateViewController(withIdentifier: "NCFirst") as? UINavigationController
}
if (self.loginViewController == nil) {
self.loginViewController = self.navigationController?.viewControllers[0] as? LoginVC
}
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!
}
}
Here's my LoginVC code
class LoginVC: UIViewController {
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var forgotPasswordLabel: UILabel!
#IBOutlet weak var signUpLabel: UILabel!
#IBOutlet weak var emailTF: UITextField!
#IBOutlet weak var passwordTF: UITextField!
var passwordAuthenticationCompletion: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>?
let pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
var usernameText: String?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.tintColor = UIColor.white
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController!.navigationBar.shadowImage = UIImage()
self.navigationController!.navigationBar.isTranslucent = true
loginButton.addTarget(self, action: #selector(loginUser), for: .touchUpInside)
loginButton.layer.cornerRadius = 18
emailTF.addPadding(.left(35))
passwordTF.addPadding(.left(35))
let tap = UITapGestureRecognizer(target: self, action: #selector(goToForgotPasswordVC))
let tap2 = UITapGestureRecognizer(target: self, action: #selector(goToSignUp1VC))
forgotPasswordLabel.isUserInteractionEnabled = true
forgotPasswordLabel.addGestureRecognizer(tap)
signUpLabel.isUserInteractionEnabled = true
signUpLabel.addGestureRecognizer(tap2)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.passwordTF.text = nil
self.emailTF.text = usernameText
}
#objc func loginUser() {
print("Got inside Login func")
if (self.emailTF.text != nil && self.passwordTF.text != nil) {
print("Calling login method now")
let authDetails = AWSCognitoIdentityPasswordAuthenticationDetails(username: self.emailTF.text!, password: self.passwordTF.text! )
self.passwordAuthenticationCompletion?.set(result: authDetails)
} else {
print("Empty fields")
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)
}
}
#objc func goToActivitySessionsVC() {
let storyboard = UIStoryboard(name: "TabBar", bundle: nil)
let destVC = storyboard.instantiateViewController(withIdentifier: "TabBarVC")
self.navigationController?.pushViewController(destVC, animated: true)
self.navigationController?.isNavigationBarHidden = true
}
#objc func goToForgotPasswordVC() {
let storyboard = UIStoryboard(name: "ForgotPassword", bundle: nil)
let destVC = storyboard.instantiateViewController(withIdentifier: "ForgotPasswordVC")
self.navigationController?.pushViewController(destVC, animated: true)
}
#objc func goToSignUp1VC() {
let storyboard = UIStoryboard(name: "SignUp", bundle: nil)
let destVC = storyboard.instantiateViewController(withIdentifier: "SignUp1VC")
self.navigationController?.pushViewController(destVC, animated: true)
}
/* func checkLoginStatus() {
if !AWSSignInManager.sharedInstance().isLoggedIn {
showSignIn()
}
else {
print("Logged In")
AWSSignInManager.sharedInstance().logout(completionHandler: {(result: Any?, error: Error?) in
self.showSignIn()
print("Sign-out Successful");
})
}
}
}
*/
extension LoginVC: AWSCognitoIdentityPasswordAuthentication {
public func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
print("Get details called")
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
DispatchQueue.main.async {
if (self.usernameText == nil) {
self.usernameText = authenticationInput.lastKnownUsername
}
}
}
public func didCompleteStepWithError(_ error: Error?) {
print("Did commplete step with error called")
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)
print(error.description)
} else {
self.emailTF.text = nil
self.dismiss(animated: true, completion: nil)
print("Got in else")
}
}
}
}
One other thing to note is that getDetails never gets called and so does the didCompleteStepWithError method. When I click the sign in button, nothing happens.
The AWS documentation is quite confusing. After much trial and error, I was able to successfully set up Cognito, sign up, authenticate on log in, and un-authenticate on sign out. To be quite honest, I don't fully understand why I call certain things. To the best of my ability, I will explain here.
Here's how Cognito works.. First it assumes that the user is already logged in and authenticated. It checks to see if this is true. This is the reason why the entry point for your storyboard is the view controller that users see after they are logged in. This is all done with the code that runs in your AppDelegate on launch. More on what you need to fix in that below.
If the user is not logged in, startPasswordAuthentication() will be called. In your code, (as it should be) this defined in the extension of AppDelegate for the protocol AWSCognitoIdentityInteractiveAuthenticationDelegate. Furthermore, startPasswordAuthentication() is called every time the user needs to log in. If the user is already logged in once the app starts, this is not called.
Another note on your question - getDetails is only called on launch if the user is not signed in. If on launch the user is not signed in, then this is called. It is also called if you are signed in and then you sign out.
So make sure the entry point for your storyboard is the logged-in screen.
On the statement that follows I am not entirely sure, so feel free to correct me if so: AWS automatically accesses the entry point upon successful log-in. Everything you are going in your #objc func loginUser() looks correct to me. That's what I have. But make sure your entry point is not the log-in screen but rather what shows after successful log in. Here is a picture of my storyboard:
Try adding the following. I am not quite sure why exactly this works, but it results in proper authentication:
In your AppDelegate, right after your variable for the storyboard, add a boolean isInitialized as such:
var isInitialized : Bool = false
Then add this code after you set up your Cognito configuration. (right before your return statement in didFinishLaunchingWithOptions) :
let didFinishLaunching = AWSSignInManager.sharedInstance().interceptApplication(application, didFinishLaunchingWithOptions: launchOptions)
if (!self.isInitialized) {
AWSSignInManager.sharedInstance().resumeSession(completionHandler: { (result: Any?, error: Error?) in
print("Result: \(String(describing: result)) \n Error:\(String(describing: error))")
})
self.isInitialized = true
}
Now replace the return statement you currently have for didFinishLaunching with the following:
return didFinishLaunching
Make sure you have this delegate set in your viewDidLoad() method for your login screen (Note you have to import AWSAuthCore):
AWSSignInManager.sharedInstance().delegate = self
and implement the protocol in your log-in VC as such:
extension LoginViewController : AWSSignInDelegate {
func onLogin(signInProvider: AWSSignInProvider, result: Any?, error: Error?) {
if error == nil {
}
}
}
Add these variables as class variables to your view controller that users see after they are logged in. They are referenced below.
var user : AWSCognitoIdentityUser?
var userAttributes : [AWSCognitoIdentityProviderAttributeType]?
/*
* :name: defaultUserPool
* :description: Returns the cognito identity pool for the global pool ID.
* :return: A Cognito Identity pool instantiation
*/
class func defaultUserPool() -> AWSCognitoIdentityUserPool {
return AWSCognitoIdentityUserPool(forKey: userPoolID)
}
Finally, make sure that you are checking the user attributes in the initial screen in viewWillAppear(). For example call the function fetchUserAttributes in this method:
func fetchUserAttributes() {
self.user = AppDelegate.defaultUserPool().currentUser()
self.user?.getDetails().continueOnSuccessWith(block: { [weak self = self] (task) -> Any? in
AWSSignInManager.sharedInstance().resumeSession(completionHandler: { (result: Any?, error: Error?) in
print("Result: \(String(describing: result)) \n Error:\(String(describing: error))")
})
guard task.result != nil else {
// alert error
return nil
}
self?.username = self?.user?.username
self?.userAttributes = task.result?.userAttributes
self?.userAttributes?.forEach({ (attribute) in
print("Name: " + attribute.name!)
})
DispatchQueue.main.async {
self?.setAttributeValues()
}
}
return nil
})
}
func resetAttributeValues() {
self.user = nil
self.userAttributes = nil
}
Finally, here is my code for signing out:
let comp = { [weak self = self] (_ result: Any?, _ error: Error?) -> Void in
if error == nil {
self?.user?.signOut()
self?.resetAttributeValues()
self?.fetchUserAttributes()
}
}
AWSSignInManager.sharedInstance().logout(completionHandler: comp)
I hope this helps. I understand this is really confusing, and to be honest, I was quite confused just writing this.. Good luck and feel free to message me with any questions.
You need to call getDetails.
In the sample app, they call getDetails in UserDetailTableViewController.
Try this line of code below pool.delegate = self in AppDelegate.
self.pool?.currentUser()?.getDetails()
I referred AWS Cognito User Pools in iOS (Swift) app
I'm getting an error of
Value of type 'AppDelegate?' has no member 'present'
when I tried to keep my users logged in even when the app was quit.
Here are my view controllers, any idea how I'd keep my users logged in? And why am I getting this error?
I'm using Firebase in my code as my Database.
LoginViewController.swift
import UIKit
import Firebase
class LoginViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var pwField: UITextField!
func createAlert(title:String, message:String) {
let alert=UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "error" , style: UIAlertActionStyle.default, handler: { (action) in alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated:true, completion: nil)
}
/* #IBAction func emailKeyboardField(_ sender: Any) {
hideKeyboard()
}
func hideKeyboard() {
emailField.resignFirstResponder()
pwField.resignFirstResponder()
}
*/
override func viewDidLoad() {
super.viewDidLoad()
emailField.delegate = self
// Do any additional setup after loading the view.
}
func emailKeyField(_ emailField: UITextField) -> Bool {
self.view.endEditing(true)
return true
}
#IBAction func loginPressed(_ sender: Any) {
guard emailField.text != "", pwField.text != "" else {return}
FIRAuth.auth()?.signIn(withEmail: emailField.text!, password: pwField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
self.createAlert(title: "Error", message: "We could not locate your account. Check your email and password.")
}
if let user = user {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "usersVC")
self.present(vc, animated: true, completion: nil)
}
})
}
}
AppDelegate.swift
import UIKit
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var window: UIWindow?
var actIdc = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
var container: UIView!
private let auth = FIRAuth.auth()
class func instance() -> AppDelegate {
return UIApplication.shared.delegate as! AppDelegate
}
func showActivityIndicator() {
if let window = window {
container = UIView()
container.frame = window.frame
container.center = window.center
container.backgroundColor = UIColor(white: 0, alpha: 0.8)
actIdc.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
actIdc.hidesWhenStopped = true
actIdc.center = CGPoint(x: container.frame.size.width / 2, y: container.frame.size.height / 2)
container.addSubview(actIdc)
window.addSubview(container)
actIdc.startAnimating()
}
}
func dismissActivityIndicatos() {
if let _ = window {
container.removeFromSuperview()
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
auth?.addStateDidChangeListener { [weak self] (_, user) in
if let user = user {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "usersVC")
self.present(vc, animated: true, completion: nil) //error on this line
// user is already logged in
} else {
// user is not logged in
}
}
FIRApp.configure()
return true
}
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) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
Any and all help would be appreciated.
App delegate is not of type ViewController, hence it doesn't have a present method to show views on it. Instead what you can do is show a view using it's window like:
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeC = storyboard.instantiateViewController(withIdentifier: "YOUR_VIEWCONTROLLER_IDENTIFIER_IN_STORYBOARD") as? HomeC
if homeC != nil {
homeC!.view.frame = (self.window!.frame)
self.window!.addSubview(homeC!.view)
self.window!.bringSubview(toFront: homeC!.view)
}
I have the following in my touchesBegan function in my GameScene so that the timer only starts when the user touches a certain sprite:
let start = SKAction.runBlock({
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "printDuration:", userInfo: NSDate(), repeats: true)
})
tapToStartNode.runAction(start)
and the following function:
func printDuration(timer: NSTimer) {
if self.view?.paused == false {
guard let userInfo = timer.userInfo else {
return
}
guard let startDate = userInfo as? NSDate else {
return
}
let duration = NSDate().timeIntervalSinceDate(startDate)
currentTime = NSDate().timeIntervalSinceDate(startDate)
currentTimeValueLabel.text = "\(NSString(format:"%3.2f", duration))"
}
}
However, when applicationDidEnterBackground or applicationWillResignActive the timer is not paused and continues to run when the app is in the backgorund. How do I make it stop?
Here a tips you can use for your problem
You can use func applicationDidEnterBackground(_ application: UIApplication) or func applicationWillResignActive(_ application: UIApplication) on AppDelegate
Access your controller by initialize rootViewController example :
let vc = self.window?.rootViewController as? ViewController
Then you can access your function (make sure function is on public state)
vc.pauseTimer() (customize based on your situation)
Hope it will be helpful