Show alert after app start - ios

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
}

Related

Swift UIKit / presenting UIAlert blocks pushing VC

I got simple UIViewController + UIAlert extension:
extension UIViewController {
func alert(title: String = "", message: String){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: Localized.ok(), style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}
Within the ViewController I got a method:
func findUser() {
userService.findUser { (userinfo, error) in
if error != nil {
if let errText = error?.localizedDescription {
self.alert(message: errText)
}
self.doAuth()
return
}
}
}
this doAuth() method should redirect to loginViewController using:
navigationController?.pushViewController(loginViewController, animated: false)
The problem is, that in this scenario, this push doesn't work (nothing appears) (I click OK button on the alert, alert dissapears but loginViewController is not pushed)
I refactored extension a little bit:
extension UIViewController {
func alert(title: String = "", message: String, action completion: (() -> Void)? = nil){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: Localized.ok(), style: .default, handler: { _ in
completion?()
}))
present(alert, animated: true, completion: nil)
}
}
so findUser() method is calling doAuth() in differently:
func findUser() {
userService.findUser { (userinfo, error) in
if error != nil {
if let errText = error?.localizedDescription {
self.alert(message: errText){ [weak self] in
self?.doAuth()
}
}
return
}
}
}
and it works!
Problem is I have no idea why. And what could have happened in the first scenario?
I feel it should be some simple explanation, but I can't figure it out.
Edit
The explanation is simple and was printed in the console:
pushViewController:animated: called on <UINavigationController 0x7f86050b4400>
while an existing transition or presentation is occurring; the navigation stack
will not be updated.
So doAuth() (with pushing VC method) was called while alert was visible/presented, so alert took the focus and VC couldn't be pushed.
cc: #Paulw11 # cookednick
Problem is navigating and presenting in same if statement
If error != nil , means it only show the alert not try to doAuth().
But you are calling doAuth() in same if block then it is trying to present alert as well as to navigate
func findUser() {
userService.findUser { (userinfo, error) in
if error != nil {
if let errText = error?.localizedDescription {
self.alert(message: errText)
}
return
}
//Out side if block
self.doAuth()
}
}

Logout and reset UITabBarController

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];
}
}

How to dismiss other Alert Controller and present a new Alert Controller immediately without delay

I am currently having problems with delay of presenting a new alert controller after dismissing another alert controller.
My situation is as below:
There are two roles for peer to peer connection using MPC: Master and Slave.
Ideal case: only one master available
However, each device can set as Master when there is no connection between devices.
When both master devices are connected, there would be a conflict to the ideal case.
Therefore, I want to make an election of Master.
When two devices which are both Master individually
are now in connection, then alert controller of Master election will prompt in each device. When one of the device tabs "Stay As Master" button first, then another device will dismiss election Alert Controller and prompt "xxx remains as master" alert.
However, I discover that there is delay of dismissing the previous Alert Controller.
Codes in MainVC:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(testing), name: NSNotification.Name(rawValue: "hasMaster"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(stayAsMaster), name: NSNotification.Name(rawValue: "StayAsMaster"), object: nil)
}
func testing(notification: Notification){
if(appDelegate.mpcHandler.checkNoOfPeersConnected()>0){
if self.appDelegate.masterSlaveDataController.checkHasMaster() && self.appDelegate.masterSlaveDataController.checkMaster(){
self.promptHasMaster()
}
else{
DispatchQueue.main.asyncAfter(deadline: .now()){
if self.appDelegate.masterSlaveDataController.checkHasMaster() && self.appDelegate.masterSlaveDataController.checkMaster(){
self.promptHasMaster()
}
}
}
}
}
func promptHasMaster(){//prompt Elect Master Alert Controller
let alertController = UIAlertController(title: "Elect Master", message: "There are more than one masters in the connection. One of you has to give up the master role and switch to slave!", preferredStyle: .alert)
let peerID = self.appDelegate.mpcHandler.session.myPeerID
let m = UIAlertAction(title: "Stay as Master", style: .default, handler: { (alert) in
self.appDelegate.mpcHandler.send(d: self.mToDictionary(peerID: peerID, action: "stayAsMaster")!)
})
let s = UIAlertAction(title: "Switch to Slave", style: .default, handler: { (alert) in
self.appDelegate.mpcHandler.send(d: self.mToDictionary(peerID: peerID, action: "switchToSlave")!)
})
alertController.addAction(m)
alertController.addAction(s)
self.present(alertController, animated: true, completion: nil)
}
//Create NSDictionary for sending peerID
func mToDictionary(peerID: MCPeerID, action: String) -> [NSDictionary]?{
var dict: [NSDictionary] = [["action" : action]]
let d = ["peerID" : peerID] as NSDictionary
dict.append(d)
return dict
}
func stayAsMaster(notification: Notification){
let peerId = NSDictionary(dictionary: notification.userInfo!)
print("stay as master", peerId.allValues)
if presentedViewController == nil {
let alertController = UIAlertController(title: String(describing: peerId.allValues) + " remains as Master", message: "", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alertController.addAction(dismiss)
self.present(alertController, animated: true, completion: nil)
} else{
let alertController = UIAlertController(title: String(describing: peerId.allValues) + " remains as Master", message: "", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alertController.addAction(dismiss)
self.dismiss(animated: false) { () -> Void in
self.present(alertController, animated: true, completion: nil)
}
}
}
Codes in MPCHandler:
func send(d: [NSDictionary]){
NSLog("%#", "Send data: \(d) to \(session.connectedPeers.count) peers")
if session.connectedPeers.count > 0{
do {
let data = NSKeyedArchiver.archivedData(withRootObject: d)
try self.session.send(data, toPeers: session.connectedPeers, with: .reliable)
} catch {
NSLog("%#", "Error for sending data: \(error)")
}
}
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
NSLog("%#", "didReceive: \(data)")
if var dict: [NSDictionary] = NSKeyedUnarchiver.unarchiveObject(with: data) as? [NSDictionary]{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let d = dict.removeFirst()
switch d.value(forKey: "action") as! String {
case "stayAsMaster":
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "StayAsMaster"), object: nil, userInfo: [peerID:dict.first!])
default: break
}
}
}
When I tab "Stay as Master" in the Alert Controller of one device, the console of Xcode of another device prints the print("stay as master", peerId.allValues) immediately, but it dismisses the current alert controller after several seconds. Does anyone have any idea, please? Thanks for any help in advance.
I see a couple of potential issues in your code, try to change these:
whenever you post a notification wrap such code inside a DispatchQueue.main.async
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "StayAsMaster"), object: nil, userInfo: [peerID:dict.first!])
}
please check here:
Posting NSNotification on the main thread
please add removeObserver (or you might crash after deinit):
deinit {
NotificationCenter.default.removeObserver(self)
}

How to use a UIAlertController in MVVM?

I have a VC with code to show an alert:
func showMessage() {
let alertView = UIAlertController(title: "TEST",
message: self.loginViewModel.errorText,
preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil))
present(alertView, animated: true, completion: nil)
}
and I have this login logic in my viewModel which needs to trigger this function:
func submitLoginRequest(userLogin: String, loginPassword: String, loginSecret: String, deviceToken: String) {
let userLogin = UserServices.init()
manager.userServicesApiRequest(url: Endpoints.login, request: userLogin) { (data, error) in
if let data = data {
let status = data["status"].stringValue
if status == "success" {
guard let userObject = UserProfileModel.init(data) else { return }
let encodedUserObject: Data = NSKeyedArchiver.archivedData(withRootObject: userObject)
UserDefaults.standard.set(encodedUserObject, forKey: "userProfile")
print("Login Succeeded") self.coordinatorDelegate?.loginViewModelDidLogin(viewModel: self)
} else {
self.errorText = data["reason"].stringValue
// here is where the controller needs calling!
}
}
I wanted to know how i should have them interact correctly to trigger the VC when the VM case is hit?

Receiving Push Notification from Sinch, Swift 3

I have the following problem: I receive Push notifications, but the title and body of the push notification is not getting showed. The Push Notification is only showing what I defined in localizable.string and not what I have defined in the code. I have the following code in my app delegate:
// SINManagedPushDelegate - FORWARD INCOMING PUSH NOTIFICATIONS TO A SINCH CLIENT
func managedPush(_ unused: SINManagedPush, didReceiveIncomingPushWithPayload payload: [AnyHashable: Any], forType pushType: String) {
}
//Describes what happens with handleRemoteNotfication from above
func handleRemoteNotification(_ userInfo: [AnyHashable: Any]) {
let result: SINNotificationResult = (self.client?.relayRemotePushNotification(userInfo))!
if !(self.client != nil) {
let userId: String? = UserDefaults.standard.object(forKey: "userId") as! String?
if userId != nil {
self.initSinchClientWithUserId(userId: userId!)
}
}
if result.isCall() && result.call().isTimedOut {
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.categoryIdentifier = "awesomeNotification"
content.title = "Missed Call"
content.body = String(format: "Missed Call from %#", arguments: [result.call().remoteUserId])
content.sound = UNNotificationSound.default()
}
else {
let alert: UIAlertController = UIAlertController(title: "Missed Call", message: String(format: "Missed Call from %#", arguments: [result.call().remoteUserId]), preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: {_ -> Void in
alert.dismiss(animated: true, completion: nil)
} )
alert.addAction(okAction)
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
self.client?.relayRemotePushNotification(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
// func application(_ application: UIApplication, didReceiveRemoteNotification data: [AnyHashable : Any]) {
print("Push notification received: \(userInfo)")
print("Remote Notification")
self.sinchPush.application(application, didReceiveRemoteNotification: userInfo)
}
// implementation of SINCallClientDelegate - PRESENTING LOCAL NOTIFICATIONS FOR INCOMING CALLS
private func client(_ client: SINClient, localNotificationForIncomingCall call: SINCall) -> SINLocalNotification {
let notification = SINLocalNotification()
notification.alertAction = "Answer"
notification.alertBody = "Incoming call from \(call.remoteUserId)"
return notification
}
It also does not work, that I get a notification if the call is timeout.
who can help? Many thanks
You can only define what you want to see in a push for VoiP pushes, so thats why you are seeing what you see.

Resources