I have a terms agreement that needs to pop up only once, however it is popping up each time the app is launched, how can I make it only pop up one time and when pressed agreed to never pop up again unless app is deleted and redownloaded. I am trying to follow How can I show a view on the first launch only?
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if !UserDefaults.standard.bool(forKey: "Walkthrough") {
UserDefaults.standard.set(false, forKey: "Walkthrough")
}
}
}
class FirstViewController: UIViewController, UIAlertViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if UserDefaults.standard.bool(forKey: "Walkthrough") {
print("already shown")
// Terms have been accepted, proceed as normal
} else {
agree()
}
}
}
my agree function is an alert controller
i forgot to add after agree()
UserDefaults.standard.set(true, forKey: "Walkthrough")
Just save a bool in UserDefaults with the answer if agreed or not and check if the key "WalkThrough" exists or not. If not you will show him the alert if found do nothing.
Add this to the initial viewController in viewDidAppear method:
let alert = UIAlertController(title: "Message", message: "You need to agree to terms and conditions", preferredStyle: .alert)
let action = UIAlertAction(title: "Not Agreed", style: .default) { (action) in
UserDefaults.standard.set(false, forKey: "WalkThrough")
alert.dismiss(animated: true, completion: nil)
}
let action2 = UIAlertAction(title: "Agreed", style: .default) { (action) in
UserDefaults.standard.set(true, forKey: "WalkThrough")
alert.dismiss(animated: true, completion: nil)
}
alert.addAction(action)
alert.addAction(action2)
if (UserDefaults.standard.object(forKey: "WalkThrough") == nil) {
//show alert and save answer
self.present(alert, animated: true, completion: nil)
}
Related
I have one scenario when the user did not use the application for more than 5 min app will show a popup with session expiration message.
The code for session expiration is added in the appDelegate and from there the popup will be presented on the current view controller.
code is
#objc func applicationDidTimeout(notification: NSNotification) {
if (window?.rootViewController?.isKind(of: UITabBarController.self))! {
for view in window?.rootViewController?.view.subviews ?? [(window?.rootViewController?.view)!] {
if view.isKind(of: MBProgressHUD.self) {
return
}
}
if window?.rootViewController?.presentedViewController != nil {
window?.rootViewController?.dismiss(animated: true, completion: {
self.showMessage(message: Message.sessionTimeout)
})
} else {
self.showMessage(message: Message.sessionTimeout)
}
}
}
fileprivate func showMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
DispatchQueue.main.async {
UIView.transition(with: self.window!, duration: 0.3, options: UIView.AnimationOptions.transitionCrossDissolve, animations: {
CommonFunctions.setLoginAsRootVC()
}, completion: nil)
}
}
alert.addAction(actionOkay)
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
Now if the user is doing some data entry and at that time, if the user leaves application ideal for 5 min or more the keyboard will dismiss and the session expiration message shown there.
But as the text field's delegate method textFieldShouldEndEditing has some validation and if that validation fails it shows a popup with the message and ok button.
So when the user taps on the ok button in the session expiration message popup, it will redirect the user to the login screen but due to the text field's delegate method validation, it shows one pop up in the login screen.
Code for the validation fail message popup is
fileprivate func showErrorMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
self.txtField.becomeFirstResponder()
}
alert.addAction(actionOkay)
self.present(alert, animated: true, completion: nil)
}
How to prevent the popup from being present in the login screen?
I try to get the proper way to prevent the popup from appearing on the login screen.
But Finally, I found one heck to solve this issue.
I have declared one boolean in AppDelegate and set it's value to false when I want to prevent the popup from appearing and then revert it back to true when I want to show the popup.
I know this is not the elegant or efficient solution for the issue, but it works for now.
If anyone knows the better answer can post here, I'm still open to any better solution.
#objc func applicationDidTimeout(notification: NSNotification)
{
let visibleView : UIViewController = self.getVisibleViewControllerFrom(self.window?.rootViewController)!
self.showMessage(message: Message.sessionTimeout,Controller: visibleView)
}
fileprivate func showMessage(message: String , Controller : UIViewController) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
//Now apply your code here to set login view controller as rootview
// This controller is for demo
window!.rootViewController = UIStoryboard(name: "Main", bundle:
nil).instantiateViewController(withIdentifier: "loginview")
window!.makeKeyAndVisible()
}
alert.addAction(actionOkay)
Controller.present(alert, animated: true, completion: nil)
}
//MARK:- Supporting method to get visible viewcontroller from window
func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return self.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return self.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return self.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
Try this code, I've use this code many times may be it's work for you.
I'm aware that there are some questions out there that are similar to this question, however, a lot of them are in Objective C and I haven't been able to find a really applicable solution yet that works.
The main problem I am having is that when I log out of one account in my app, then log into another, the tab bar is not reset, and it displays the previously signed in users data. In other words, I need a way to "reset" the app back to the state it was in before any user had signed in.
I have tried to achieve this by writing a function inside App Delegate (setupTabBarController) and calling it when the user logs out, but no such luck yet.
This is what I have so far:
Logout code:
#objc func handleSignOutButtonTapped() {
let signOutAction = UIAlertAction(title: "Sign Out", style: .destructive) { (action) in
do {
try Auth.auth().signOut()
let welcomeControl = WelcomeController()
let welcomeNavCon = UINavigationController(rootViewController: welcomeControl)
self.present(welcomeNavCon, animated: true, completion: nil)
} catch let err {
print("Failed to sign out with error", err)
Service.showAlert(on: self, style: .alert, title: "Sign Out Error", message: err.localizedDescription)
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
Service.showAlert(on: self, style: .actionSheet, title: nil, message: nil, actions: [signOutAction, cancelAction]) {
}
let delegate = AppDelegate()
delegate.setupTabBarController()
}
Part of my App Delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
setupTabBarController()
return true
}
func setupTabBarController() {
window = UIWindow()
window?.makeKeyAndVisible()
let vc = MainTabBarController()
let controller = UINavigationController(rootViewController: vc)
window?.rootViewController = controller
}
Sign in code:
#objc func handleNormalLogin() {
hud.textLabel.text = "Signing In..."
hud.show(in: view, animated: true)
//TODO
guard let email = emailTextField.text else { return }
guard let password = passwordTextField.text else { return }
Auth.auth().signIn(withEmail: email, password: password) { (user, error) in
if let error = error {
print("Error signing in: \(error)")
return
}
//sucessfully signed in
self.hud.dismiss(animated: true)
self.dismiss(animated: true, completion: nil)
}
}
Any help is really appreciated, I have been stuck on this for a few hours now and I really want to understand what I'm doing wrong.
Cheers
Problem
When you write let delegate = AppDelegate(). It means that you create a new AppDelegate. Instead of using current AppDelegate, you use another AppDelegate. That's why setupTabBarController method doesn't affects anything.
Calling setupTabBarController at the end of handleSignOutButtonTapped isn't a good idea. Because it will replace current rootViewController with UINavigation of MainTabBarController.
Answer
Use self.tabBarController? instead of self to present welcomeNavCon.
Don't call setupTabBarController at the end of handleSignOutButtonTapped method.
Recreate and set new viewControllers for MainTabBarController.
Code
#objc func handleSignOutButtonTapped() {
let signOutAction = UIAlertAction(title: "Sign Out", style: .destructive) { (action) in
do {
try Auth.auth().signOut()
let welcomeControl = WelcomeController()
let welcomeNavCon = UINavigationController(rootViewController: welcomeControl)
self.tabBarController?.present(welcomeNavCon, animated: true) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate;
appDelegate.resetTabBarController();
};
} catch let err {
print("Failed to sign out with error", err)
Service.showAlert(on: self, style: .alert, title: "Sign Out Error", message: err.localizedDescription)
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
Service.showAlert(on: self, style: .actionSheet, title: nil, message: nil, actions: [signOutAction, cancelAction]) {
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var tabBarController : UITabBarController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
setupTabBarController()
return true
}
func setupTabBarController() {
window = UIWindow()
window?.makeKeyAndVisible()
let vc = MainTabBarController()
let controller = UINavigationController(rootViewController: vc)
window?.rootViewController = controller
}
func resetTabBarController() -> Void {
let viewControllerAtIndex1 = ...
let viewControllerAtIndex2 = ...
let viewControllerAtIndex3 = ...
tabBarController?.viewControllers = [viewControllerAtIndex1, viewControllerAtIndex2, viewControllerAtIndex3];
}
}
I have a problem with showing alert after application start. I am using Notification Center observe notification error and find current visible view controller to show alert on it. This work when I handle error after start an app, but if I want to show this error just after start an app, it does not happen.
My code for app delegate:
func listenForRealmErrorNotification() {
NotificationCenter.default.addObserver(forName: MyDataModelDidFailNotification, object: nil, queue: OperationQueue.main, using: { notification in
let alert = UIAlertController(title: NSLocalizedString("Internal alert", comment: "Internal error header"),
message: NSLocalizedString("There was an error while working whith your data", comment: "Internal error description") + "\n\n" + NSLocalizedString("Press OK to terminate the app. Sorry for the inconvenience", comment: "Internal error excuses"),
preferredStyle: .alert)
let action = UIAlertAction(title: NSLocalizedString("OK", comment: "Agree button on internal error"), style: .default, handler: {_ in
let exeption = NSException(name: NSExceptionName.internalInconsistencyException, reason: "Realm error", userInfo: nil)
exeption.raise()
})
alert.addAction(action)
print("***Observe error")
self.viewControllerForShowingAlert().present(alert, animated: true, completion: nil)
})
}
func viewControllerForShowingAlert() -> UIViewController {
let rootViewController = self.window!.rootViewController!
return topViewController(from: rootViewController)
}
func topViewController(from controller: UIViewController) -> UIViewController {
if controller is UINavigationController {
return topViewController(from: (controller as! UINavigationController).visibleViewController!)
}
if controller is UITabBarController {
return topViewController(from:(controller as! UITabBarController).selectedViewController!)
}
if let presentedViewController = controller.presentedViewController {
return topViewController(from:presentedViewController)
}
return controller
}
And code for post notification:
func fatalRealmError(_ error: Error) {
print("***Fatal error with dataBase: \(error)")
Crashlytics.sharedInstance().recordError(error)
NotificationCenter.default.post(name: MyDataModelDidFailNotification, object: nil)
}
UPDATE:
Initiating my data source in delegate:
func initialDataSource() {
do {
dataSource = try UserDataSource()
}
catch let error as NSError {
fatalRealmError(error)
}
}
And here I have set am observer:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
customizeAppearance()
listenForRealmErrorNotification()
initialDataSource()
let rootViewController = window?.rootViewController as! UINavigationController
let rootContentController = rootViewController.viewControllers[0] as! YourFoodViewController
rootContentController.dataSource = dataSource
Fabric.with([Crashlytics.self])
return true
}
everyone!
When my app in foreground I want it to show alert when DidReceiveLocalNotification triggered.
I can add alert to mimic local notifications in AppDelegate.swift, but the problem is I don't know how to add method from my ViewController to UIAlertAction closure (see commented line), to finish animation when timer stopped.
My code below:
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
let alertTimerEnds = UIAlertController(title: "Timer finished!", message: nil, preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default) { finished in
print("You've pressed OK button")
//self.ViewController().finishAnimation()
}
alertTimerEnds.addAction(okAction)
self.window?.rootViewController?.presentViewController(alertTimerEnds, animated: true, completion: nil)
}
Maybe I should do it in ViewController usind AppDelegate?
let someAppDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
someAppDelegate?.application(UIApplication.sharedApplication(), didReceiveLocalNotification: UILocalNotification) { code for alert }
If you want to do that
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification)
{
var viewController : UIViewController = (application.keyWindow?.rootViewController)!
while ((viewController.presentedViewController) != nil) {
viewController = viewController.presentedViewController!
}
let alert = UIAlertController(title: "", message: notification.alertBody, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: {(action: UIAlertAction!) in}))
viewController.presentViewController(alert, animated: true, completion: nil)
})
Show UIAlertController in didReceiveLocalNotification method
Try with below code, this will display alert on viewcontroller which is present at a moment.
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
var latestViewController : UIViewController!
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let viewControllers = appDelegate.window?.rootViewController?.presentedViewController {
latestViewController = viewControllers as UIViewController
}
else if let viewControllers = appDelegate.window?.rootViewController?.childViewControllers {
latestViewController = viewControllers.last! as UIViewController
}
//var alert: UIAlertView!
//alert = UIAlertView(title: "Title", message:"Message , delegate: nil, cancelButtonTitle:"Ok" )
//alert.show()
let alert = UIAlertController(title: "Title", message:"Message", preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: .Default) { _ in
// Put here any code that you would like to execute when
// the user taps that OK button (may be empty in your case if that's just
// an informative alert)
}
alert.addAction(action)
latestViewController.presentViewController(alert, animated: true){}
}
How do I migrate from UIAlertView (deprecated in iOS8)
I am trying to make a UIAlertController pop up one time when the user first downloads the app. However, I get the following error when I put the code in didFinishLaunchingWithOptions,
2015-01-15 23:54:45.306 WeddingApp Warning: Attempt to present UIAlertController: on UITabBarController: whose view is not in the window hierarchy!
My code is below:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if window == nil {
println("window is nil");
return true;
}
if window!.rootViewController == nil {
println("window!.rootViewController is nil");
return true;
}
//TabBarController//
let tabBarController: UITabBarController = window!.rootViewController! as UITabBarController;
UITabBar.appearance().tintColor = UIColor.magentaColor();
UITabBar.appearance().translucent = false;
/* tableView cells are now completely visable. They do not hie behind the tabBar */
tabBarController.viewControllers = [
TwelveToTenMonths(nibName: nil, bundle:nil),
NineToSevenMonths(nibName: nil, bundle:nil),
SixToFourMonths(nibName: nil, bundle:nil),
ThreeToOneMonth(nibName: nil, bundle:nil),
];
var alert = UIAlertController(
title: "Welcome to WeddingApp!",
message: "Thank You for choosing the \nWedding App for your organization needs. \n\nFor more great tools please visit our website www.wedme.com",
preferredStyle: UIAlertControllerStyle.Alert);
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.Default, handler: nil));
self.window!.rootViewController!.presentViewController(alert, animated: true, completion: nil);
return true
}
***NOTE: I also tried adding the following to the appDelegate instead of the above code but the alert continues to appear whenever I return to the app.....
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.
showAlert();
}
func showAlert(){
println("alert");
var alert = UIAlertController(
title: "Welcome to WeddingApp!",
message: "Thank You for choosing the \nWedding App for your organization needs. \n\nFor more great tools please visit our website www.wedme.com",
preferredStyle: UIAlertControllerStyle.Alert);
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.Default, handler: nil));
self.window!.rootViewController!.presentViewController(alert, animated: true, completion: nil);
}
Does anyone know a way around this???
In the first case, you never added the UIAlertController (you could try adding it as a child view controller).
https://stackoverflow.com/a/17012439/3324388
In the second case you assigned the root controller to the UIAlertController and never assign the root controller back to the UITabBarController. You need to set it back.
Have you tried using a UIAlertView instead?
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAlertView_Class/index.html
Try something like this:
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
if self.window!.rootViewController as? UITabBarController != nil {
var tababarController = self.window!.rootViewController as UITabBarController
.
.
.
let alertController = UIAlertController(title: "Welcome...", message: "Thank You...", preferredStyle: .Alert)
tababarController.presentViewController(alert, animated: true, completion: nil);
}
return true
}