Trouble with local notification in AppDelegate - ios

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")
}

Related

Refer a friend through sms getting White screen

Hi I am writing the following code to refer a friend through SMS.
When I click on cell, the sms app opens with text but when again I tried for second time, it shows white color screen.
Here is my code
var controller1 = MFMessageComposeViewController()
extension ReferaFriendController:UICollectionViewDelegate,UICollectionViewDataSource,MFMessageComposeViewControllerDelegate
{
if indexPath.item == 0
{
if MFMessageComposeViewController.canSendText() {
let urlToShare = self.referalmodeldata[0].referralCodeOnly
controller1.body = "Hey I just gave an Awesome Assessment on App you can also try it. I scored , Try to beat my score \(String(describing: urlToShare))"
controller1.messageComposeDelegate = self
self.present(controller1, animated: true, completion: nil)
}
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
self.dismiss(animated: true, completion: nil)
}
}
As far as I can see, there's no need to keep a reference to the MFMessageComposeViewController. Just move it to be created at the point you need it, inside your if closure:
if MFMessageComposeViewController.canSendText() {
let controller = MFMessageComposeViewController()
// ...
}

Create toast when log out (iOS)

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
}

Launching app from Local Notification; VC instantiates, but does not appear

I would like to launch my app from a local notification that will appear when the home screen is locked or when the user is in another app based on similar discussions here and here I have the following code in my AppDelegate:
func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
switch response.actionIdentifier {
case "play":
var setAlarmVC = self.window?.rootViewController as? SettingAlarmViewController
if setAlarmVC == nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
setAlarmVC = storyboard.instantiateViewController(withIdentifier: "AlarmViewController") as? SettingAlarmViewController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = setAlarmVC
self.window?.makeKeyAndVisible()
}
case "snooze":
print("I pressed pause")
default:
break
}
completionHandler()
}
Within my SettingAlarmViewController's viewDidLoad, I have set up some simple print-outs
override func viewDidLoad() {
super.viewDidLoad()
print("Setting Alarm View Controller was instantiated")
}
When I press play from the local notification while the app is in the background, I get the console print-out as expected:
Setting Alarm View Controller was instantiated
But the app does not actually launch and Setting Alarm View Controller does not appear. If I then click on the app, a fresh Setting Alarm View Controller is the first visible thing. I feel like I must be missing something obvious, but I cannot figure out what it is.
EDIT: Doing more testing. When the notification appears on the lock screen and the user presses "play" from the lock screen, the password / unlock screen does not appear, but the app still launches and I get the print-out " Setting Alarm View Controller was instantiated"
Well, a day of my life wasted on this, here is the problem so that others do not have the same issue: developer.apple.com/documentation/usernotifications/… you must ad [.foreground] to your Notification Action, as in let playAction = UNNotificationAction(identifier: "play", title: "play", options: [.foreground])

UIAlertController message won't display

I have two UIAlertControllers that can potentially be displayed – one for registering an account, and one for logging into it. They can be presented with an optional message.
func present(alertController: UIAlertController? = nil, message: String? = nil) {
self.currentAlertController = alertController ?? self.currentAlertController
self.currentAlertController.message = message
(UIApplication.shared.delegate as! AppDelegate).window?.rootViewController?.present(self.currentAlertController, animated: true, completion: nil)
}
And it look like the message is carried through the function.
But then it doesn't display.
I can't tell if I'm missing something basic, or there's just something about UIAlertController I'm not aware of.
Switching the present action and the setting of the message did the trick.
func present(alertController: UIAlertController? = nil, message: String? = nil) {
self.currentAlertController = alertController ?? self.currentAlertController
(UIApplication.shared.delegate as! AppDelegate).window?.rootViewController?.present(self.currentAlertController, animated: true, completion: nil)
self.currentAlertController.message = message
}
Another thing is, when initializing the UIAlertController to begin with, if you want the message to start blank but allow it to be updated later, the message should be "" as opposed to nil.

Open notification to specific view

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.

Resources