I'm working on an app in Swift using push notifications. So far I have the following code in my AppDelegate:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
println("Received alert and opened it")
debugPrintln(userInfo)
if application.applicationState == UIApplicationState.Active {
// App in foreground
println("App in foreground already")
} else {
// App in background
if let tripId = (userInfo["trip"] as? String)?.toInt() {
println(tripId)
Trips.load(tripId) { (responseCode, trip) in
debugPrintln("Got the trip")
if let trip = trip {
if let window = self.window {
if let rootViewController = window.rootViewController {
if let storyboard = rootViewController.storyboard {
let viewController = storyboard.instantiateViewControllerWithIdentifier("Trip") as! TripViewController
viewController.trip = trip
rootViewController.presentViewController(viewController, animated: true, completion: nil)
} else {
println("No storyboard")
}
} else {
println("No root view controller")
}
} else {
println("No window")
}
}
}
} else {
println("Failed to get trip id")
}
}
}
The storyboard is constructed that when the app first opens, it opens to the LoginViewController, which checks login state and redirects to a NavigationController containing a list of trips. From the list, a user can tap a trip to open the TripViewController (see screenshot).
When I run my app and test tapping on a push notification, the app loads the trip list and I see the following log in my console:
2015-09-04 09:50:07.158 GoDriver[883:377922] Warning: Attempt to present <GoDriver.TripViewController: 0x15f5b260> on <GoDriver.LoginViewController: 0x15d910e0> whose view is not in the window hierarchy!
Do I have to load up my Navigation Controller and populate it with the TripViewController?
If you are using UIStoryBoard and using the initialViewController, iOS automatically does the needful i.e loads it up, creates navigationController if needed and loads it to window.
However in this case you will need to do this bit manually. You would need to create a UINavigationController, populate it with your TripViewController and hook it with UIWindow.
Related
I'm showing notification alert when app is active. In AppDelegate I'm handling push notification alert and action. I want to detect if user is typing in UITextView in a VC.
How to check it?
Following is the code snippet in AppDelegate.swift
switch application.applicationState {
case .active:
if let topVC = currentNavInTab.visibleViewController as? ChatViewController {
if topVC.user_is_typing { // How to detect if user is typing?
return
}
}
I'm working for an app that has users. One of the functionalities is to allow a user to log out and be redirected to the first page. I came across the problem when a user logs out, a toast message "You logged out" should be displayed on the first view of the app and receiving the command from a different page. Basically a toast message that can work with all the views, not only with the current one.
I managed to call a toast function after a user logs out but it won't show the message because the current view is dismissed before to have the chance showing it.
This is the function called:
func showToast(controller: UIViewController, message : String, seconds: Double) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
alert.view.backgroundColor = UIColor.black
alert.view.alpha = 0.6
alert.view.layer.cornerRadius = 15
controller.present(alert, animated: true)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds) {
alert.dismiss(animated: true)
}
}
If you dont know which the current presented VC is you could use this extension here:
extension UIWindow {
func topViewController() -> UIViewController? {
var top = self.rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
return top
}
}
Then you can call it it this way:
if let topVC = UIApplication.shared.keyWindow?.topViewController() {
topVC.present(alert, animated: true)
}
Another option is that you pop/dismiss your viewcontroller after you dismissed your alert:
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds) {
alert.dismiss(animated: true)
// popViewController or dismiss here
}
I am trying to create registerVC, after the user fill the text field, and press the register button I try to create user by using Firebase Authentication,
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
// ...
}
here is the code when the sign up button did pressed
#IBAction func signUpButtonDidPressed(_ sender: Any) {
print("000")
SVProgressHUD.show(withStatus: "Harap Tunggu")
print("a")
userEmail = emailTextField.text!
userPassword = passwordTextField.text!
print("b")
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!) { (user, error) in
print("c")
print("d")
if let user = user {
print("e")
user.sendEmailVerification(completion: { (error) in
print("f")
print("g")
let fullName = "\(self.firstNameTextField.text!) \(self.lastNameTextField.text!)"
let emailOfTheUser = "\(self.emailTextField.text!)"
print("h")
let userKMViaEmail = UserKM(uid: user.uid, fullname: fullName, email: emailOfTheUser )
print("i")
print("111")
// save user basic information in NSUserDefault
self.saveUserKMBasicDataInUserDefault(userKM: userKMViaEmail)
print("2222")
self.showAlert(alertTitle: "BErhasil", alertMessage: "email dikirim", actionTitle: "OK")
// the user need to read the alert first, after tap the action the user will be moved to LoginUsingEmailVC
// self.showCustomAlert(alertTitle: "Periksa Email Anda!", alertMessage: "Assalamualaikum, Mohon Ikuti link yang ada pada email yang kami kirimkan. Harap tunggu 15-30 menit apabila email kami belum juga sampai.", actionTitle: "Login")
//
print("3333")
SVProgressHUD.dismiss()
print("444")
})
}
}
}
In app delegate, I write codes to decide whether the user will go straight to Main Tab Bar if the user has already sign in, otherwise the user will be sent to Welcome Screen VC when the app is launched.
here is the code in my App Delegate.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
checkUserLogin()
// to track the user default info.plist location in the device (simulator) for checking purpose.
print("The Location of info.plist of User Default: \(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last! as String)")
return true
}
}
extension AppDelegate {
// MARK: - HELPER METHODS
func checkUserLogin() {
// to check whether the user has already logged in or not
SVProgressHUD.show()
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
SVProgressHUD.dismiss()
self.goToWelcomeVC()
} else {
SVProgressHUD.dismiss()
self.goToMainTabBar()
}
// if user is not nil then automatically go to maintab bar (initialVC is indeed main tab bar controller)
SVProgressHUD.dismiss()
}
}
func goToMainTabBar () {
print("XXXXXXXX")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mainTabBar = storyboard.instantiateViewController(withIdentifier: "mainTabBar")
window?.rootViewController = mainTabBar
}
func goToWelcomeVC () {
let storyboard = UIStoryboard(name: "Welcome", bundle: nil)
let login = storyboard.instantiateViewController(withIdentifier: "WelcomeVC")
window?.rootViewController = login
}
}
here is the print result in my debugging area, we can see that the XXXXX that located in AppDelegate is triggered automatically.
as we can see, after the sign up button did pressed Auth.auth().createUser() is triggered. and when Auth.auth().createUser() is triggered then Auth.auth().addStateDidChangeListener in AppDelegate will also be triggered.
but unfortunately, when Auth.auth().addStateDidChangeListener in AppDelegate is triggered, it will make my app automatically go to Main Tab Bar.
at this point, I want my keep stay on SignUpVC, not to MainTabBarVC.
maybe I make mistake when writing the code below, because when createUser() is activated, basically the user in addStateDidChangeListener() is no longer nil.
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
SVProgressHUD.dismiss()
self.goToWelcomeVC()
} else {
SVProgressHUD.dismiss()
self.goToMainTabBar()
}
// if user is not nil then automatically go to maintab bar (initialVC is indeed main tab bar controller)
SVProgressHUD.dismiss()
}
but I also want the user to be in MainTab bar if they already log in, otherwise go to welcome screen if they are not sign in yet?
I have an alarm clock app. It has 2 VC. VC1 is a menu VC that linked with VC2. In VC2 there's setting of alarm clock. So I have troubles with getting local notifications.
For example, if I set Alarm Clock on VC2 then I move to VC1 and then go to Home Screen I will receive a notification on the top of the screen. After clicking on notification I will move to VC1 and I will get a message. But I will get an error 'Could not cast value of type 'MyApp.VC1' (0x10ee97730) to 'MyApp.VC2' (0x10ee96bd0)'. If I set Alarm Clock on VC2 then I move to Home Screen I will receive a notification on the top of the screen. After clicking on notification I will move to VC2 and I will get a message and everything will be fine.
Other problem is setting Alarm clock on VC2 and moving to VC1 without moving to Home Screen. When time will I come my app just crashing with same error 'Could not cast value of type 'MyApp.VC1' (0x10ee97730) to 'MyApp.VC2' (0x10ee96bd0)'
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
let storageController = UIAlertController(title: "Alarm", message: nil, preferredStyle: .alert)
var soundName: String = ""
var index: Int = -1
if let userInfo = notification.userInfo {
soundName = userInfo["soundName"] as! String
index = userInfo["index"] as! Int
}
playSound(soundName)
let stopOption = UIAlertAction(title: "OK", style: .default) {
(action:UIAlertAction)->Void in self.audioPlayer?.stop()
let mainVC = self.window?.visibleViewController as! MainAlarmViewController
storageController.addAction(stopOption)
(self.window?.visibleViewController as! MainAlarmViewController).present(storageController, animated: true, completion: nil)
}
Does anybody know how to resolve it?
When I getting an error I see highlight of this line:
(self.window?.visibleViewController as! MainAlarmViewController).present(storageController, animated: true, completion: nil)
Thank you so much!
P.S. Maybe is it possible to make a notification on the top of a screen with a link to VC2 when app in foreground or app in VC1?
Also sometimes I'm getting a message 'Warning: Attempt to present on whose view is not in the window hierarchy!'
Replace this line
(self.window?.visibleViewController as! MainAlarmViewController).present(storageController, animated: true, completion: nil)
with following code
if let viewController = self.window?.visibleViewController {
if viewController is MainAlarmViewController {
// view controller is MainAlarmViewController
} else {
// view controller is not MainAlarmViewController
}
viewController.present(storageController, animated: true, completion: nil)
} else {
print("Something wrong. Window can't provide visible view controller")
}
I'm implementing the login possibility with touchID using Swift.
Following: when the App is started, there is a login screen and a touchID popup - that's working fine. The problem occurs, when the app is loaded from background: I want the touchID popup appear over a login screen if a specific timespan hasn't been exceeded yet - but this time I want the touchID to go to the last shown view before the app entered background. (i.e. if the user wants to cancel the touchID, there is a login screen underneath where he then can authenticate via password, which leads him to the last shown view OR if the touchID authentication succeeded, the login screen should be dismissed and the last shown view presented.)
I really tried everything on my own, and searched for answers - nothing did help me. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
//notify when foreground or background have been entered -> in that case there are two methods that will be invoked: willEnterForeground and didEnterBackground
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "willEnterForeground", name:UIApplicationWillEnterForegroundNotification, object: nil)
notificationCenter.addObserver(self, selector: "didEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
password.secureTextEntry = true
if (username != nil) {
username.text = "bucketFit"
}
username.delegate = self
password.delegate = self
if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown == nil){
authenticateWithTouchID()
}
}
}
willEnterForeground:
func willEnterForeground() {
//save locally that the guide already logged in once and the application is just entering foreground
//the variable alreadyShown is used for presenting the touchID, see viewDidAppear method
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
showLoginView()
} else {
def.removeObjectForKey("alreadyShown")
showLoginView()
}
}
}
authenticateWithTouchID():
func authenticateWithTouchID() {
let context : LAContext = LAContext()
context.localizedFallbackTitle = ""
var error : NSError?
let myLocalizedReasonString : NSString = "Authentication is required"
//check whether the iphone has the touchID possibility at all
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
//if yes then execute the touchID and see whether the finger print matches
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString as String, reply: { (success : Bool, evaluationError : NSError?) -> Void in
//touchID succeded -> go to students list page
if success {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.performSegueWithIdentifier("studentsList", sender: self)
})
} else {
// Authentification failed
print(evaluationError?.description)
//print out the specific error
switch evaluationError!.code {
case LAError.SystemCancel.rawValue:
print("Authentication cancelled by the system")
case LAError.UserCancel.rawValue:
print("Authentication cancelled by the user")
default:
print("Authentication failed")
}
}
})
}
}
shouldPerformSegueWithIdentifier:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if (false) { //TODO -> username.text!.isEmpty || password.text!.isEmpty
notify("Login failed", message: "Please enter your username and password to proceed")
return false
} else if (false) { //TODO when backend ready! -> !login("bucketFit", password: "test")
notify("Incorrect username or password", message: "Please try again")
return false
//if the login page is loaded after background, dont proceed (then we need to present the last presented view on the stack before the app leaved to background)
} else if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown != nil){
//TODO check whether login data is correct
dismissLoginView()
return false
}
}
return true
}
Thank you in advance.
What you could do is create a AuthenticationManager. This manager would be a shared instance which keep track of whether authentication needs to be renewed. You may also want this to contain all of the auth methods.
class AuthenticationManager {
static let sharedInstance = AuthenticationManager()
var needsAuthentication = false
}
In AppDelegate:
func willEnterForeground() {
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
AuthenticationManager.sharedInstance.needsAuthentication = true
}
}
}
Then, subclass UIViewController with a view controller named SecureViewController. Override viewDidLoad() in this subclass
override fun viewDidLoad() {
super.viewDidLoad()
if (AuthenticationManager.sharedInstance().needsAuthentication) {
// call authentication methods
}
}
Now, make all your View Controllers that require authentication subclasses of SecureViewController.