restarting app causes NSInvalidArgumentException - ios

So, I do a segue on fresh install of my app that shows a dialog that asks user to setup some variables that the app needs. Then, for admin users, I have an option to clear the app cache then restart the app so the user can redo the setup if they wanted to.
I restart my app using the code below.
func restartApplication () {
let viewController = SomeViewController()
let navCtrl = UINavigationController(rootViewController: viewController)
guard
let window = UIApplication.shared.keyWindow,
let rootViewController = window.rootViewController
else {
return
}
navCtrl.view.frame = rootViewController.view.frame
navCtrl.view.layoutIfNeeded()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
window.rootViewController = navCtrl
})
}
It works. But when the app reaches viewDidAppear() and needs to show my dialog, the app crashes saying
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'Receiver (<AppName.SomeViewController: 0x150dda00>) has no segue with identifier 'showSetup''
I think I messed up something for the restart, or I still need to do something for it to work but I don't know what is it.
I'm currently converting my android app to iOS so I want to do everything that I could convert.
This is how I do the segue.
DispatchQueue.main.async {
self.performSegue(withIdentifier: "showSetup", sender: self)
}

Since your "relaunched" view controller doesn't come from the storyboard, any references to segues (which relies on the storyboard) will fail.
You need something like:
func restartApplication () {
let navCtrl = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("NavController")
guard
let window = UIApplication.shared.keyWindow,
let rootViewController = window.rootViewController
else {
return
}
navCtrl.view.frame = rootViewController.view.frame
navCtrl.view.layoutIfNeeded()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
window.rootViewController = navCtrl
})
}

Related

Mapview delegate function continues to run after dismissing View Controller that contains mapview

I'm using mapbox and firebase.
I have a delegate function that updates the user's coordinates(inside of the firebase database) when the user's location changes.
To the best of my knowledge, it functions as it should when signed into the app. The mapviews delegate is the view controller (self.mapView.delegate = self)
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool){
let latitude = mapView.userLocation?.coordinate.latitude
let longitude = mapView.userLocation?.coordinate.longitude
Database.database().reference().child(pathToCoord).updateChildValues(["latitude":latitude, "longitude":longitude], withCompletionBlock: { (err, ref) in
if err != nil { print(err!); return }
}
When I sign out of the app, I would like to stop updating the user location.
Ideally I would just like the View Controller with the map to go away completely and for everything on it to stop running.
I've written this sign out function that try several different methods of making sure that the location is no longer updated.
func signOut(){
for id in Auth.auth().currentUser?.providerData{
if id.providerID == "facebook.com"{
FBSDKLoginManager().logOut()
}
}
do {
try Auth.auth().signOut()
}catch let logoutError {
print(logoutError)
}
self.mapView.delegate = nil
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "SignInViewController") as? SignInViewController{
UIApplication.shared.keyWindow?.rootViewController = vc
self.dismiss(animated: true, completion: nil)
}
}
Sometimes when I'm logged out though, I continuously get the error below in my console. The most logical solution I can think of for why this is happening is that the View Controller is still running. I don't know how to make it stop.
[Firebase/Database] updateChildValues: at `path/to/coord` failed: permission_denied
Error Domain=com.firebase Code=1 "Permission denied" UserInfo={NSLocalizedDescription=Permission denied}
Edit
So it looks like the problem was probably that I had this in my SignInViewController
if let uid = Auth.auth().currentUser?.uid{
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController{
vc.uid = uid
UIApplication.shared.keyWindow?.rootViewController = vc
}
}
And then the delegate would run once for each uid, as if two of the view controllers were running at the same time. When I signed out, I'm guessing the other one didn't sign out and kept running for the other user id.
This is off topic to my original question but I'd like to know what the proper way to check if a user is already signed in, and then sign them in is. Because clearly my method didn't work.
Why don't you try dismiss with Completion Handler block like below.
self.dismiss(animated: true, completion: {
if let vc = self.storyboard!.instantiateViewController(withIdentifier: "SignInViewController") as? SignInViewController{
UIApplication.shared.keyWindow?.rootViewController = vc
}
})

How to Handle Clickable Push Notification In One Signal iOS Swift

I already get the data from one signal (additional Data). But I want to present view controller by clicking the push notification itself. Can someone help me. Thanks in advance.
Did you check OneSignal's docs on Deep Linking? https://documentation.onesignal.com/docs/links
There is a demo project on Github that might help you: https://github.com/OneSignal/OneSignal-iOS-SDK/tree/master/Examples
add this in your didFinishLaunchingWithOptions
this code will check that if app was launched using appIcon or tapping on notification
self.window = UIWindow(frame: UIScreen.main.bounds)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let tabBar = storyBoard.instantiateViewController(withIdentifier: "MainNavigationController")
as? UINavigationController
self.window?.rootViewController = tabBar
self.window?.makeKeyAndVisible()
if let notification = launchOptions?[.remoteNotification] as? [String: AnyObject] {
// 2
let aps = notification["aps"] as! [String: AnyObject]
let vc = storyBoard.instantiateViewController(withIdentifier: "EmergencyRequestViewController") as? EmergencyRequestViewController
tabBar?.pushViewController(vc!, animated: true)
}
}

Trouble with local notification in AppDelegate

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

Clean application memory in swift

I've an XCode project programmed in swift with a registration/connection process. When you're connected, even if the app restarted you don't repeat this process until you click on "log out" button.
I've noticed that if you follow the registration/connection process when you start the app, memory is about 500Mb whereas if the app started with a connected user, memory is about 140Mb.
Registration process is composed of 3 view controllers connected with custom segue. Access to logged in view is made by following code :
func switchRootViewController(rootViewController: UIViewController, animated: Bool, completion: (() -> Void)?)
{
if animated
{
UIView.transitionWithView(window!, duration: 0.5, options: .TransitionCrossDissolve, animations:
{
let oldState: Bool = UIView.areAnimationsEnabled()
UIView.setAnimationsEnabled(false)
self.window!.rootViewController = rootViewController
UIView.setAnimationsEnabled(oldState)
}, completion: { (finished: Bool) -> () in
if (completion != nil)
{
completion!()
}
})
}
else
{
window!.rootViewController = rootViewController
}
}
Where rootViewController is replace by the logged in view I want to load as you can see here :
func goToNextView()
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = storyboard.instantiateViewControllerWithIdentifier("conversationViewController") as!ConversationViewController
homeViewController.currentUser = self.currentUser
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.switchRootViewController(home, animated: true, completion: nil)
}
However it doesn't clean the memory.
Here a screen of memory allocation :
So my question is : is it possible to "kill" some view controller in order to clean the memory ?
Here for example it can be obvious to "kill" registration process views when you're connected.
Thank you for answers.

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