How to call function from viewcontroller in appdelegate? (Swift) - swift

So basically I am trying to run the function Refresh (Located in ViewController.swift) in AppDelegate.swift. After searching for the better part of 5 hours I can't figure out why this isnt working.
here is my function:
func Refresh(){
if(Count==0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent])
Current.text=String(TipArray[CountCurrent])
Deliveries.text="\(Runs)"
}
if(Count>0 && QuickActionChecker==true){
Previous.text=String(TipArray[CountCurrent-1])
Current.text=String(Total)
Deliveries.text="\(Runs)"
}
}
In my Appdelegate.swift I have initialized the ViewController by setting it to Main:
let Main = ViewController()
and here is where I'm attempting to run the function from (force touch quick action from the homescreen):
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
if shortcutItem.type == "com.Sicarix.Ticket.Add5"{
if(CountCurrent==0){
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+2
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
else{
TipArray.append(5)
Count=Count+1
CountCurrent=CountCurrent+1
Total=TipArray.reduce(0) {$0+$1}
Runs=Runs+1
QuickActionChecker=true
Main.Refresh()
}
}
}
however, when I try to use the shortcut it opens the app to a white screen and stays there. Console is spitting out:
fatal error: unexpectedly found nil while unwrapping an Optional value
the code runs fine when I remove the Main.Refresh(), it just doesn't update the labels in my app, hence the need for the function.
Please help, I'm so ready to move on past this bug....
also please bear in mind that I haven't even been coding in swift for a week yet, so please break down what was wrong as best you can. TIA

Change your viewController object
let nextVC = storyboard?.instantiateViewController(withIdentifier:"ViewController") as! ViewController

The problem is that you are instantiating a new ViewController using ViewController() and that ViewController isn't added to your view controller hierarchy.
You need to use your Storyboard to instantiate the ViewController by using let mainVC = UIStoryboard(name: "Main", bundle: "nil").instantiateViewController(withIdentifier:"ViewController") as! ViewController and make sure your identifier is set up in Storyboard.
If you are not using Storyboard, another solution is to store the text you want to display on the UI in data source variables and only update those variables in 'AppDelegate', then load the content of those variables onto your labels in 'viewDidLoad' or 'viewWillAppear'.

Related

swift pushkit -> open call screen without open total app

I have some problem with my app, the exactly I want is to only open call screen when get pushkit VOIP call. But the problem is that the app gets open again(when killed), so many request were sent to server, i just want to open only callscreen, then may exit app after call dismiss.
I will fully explain the problem now:
First, in app delegate i replace with this class(this class have same UI with splash Screen)
initiateFirstScreen("SplashScreen", storyboardName : "sheet")
Inside this class. I have to check token, user info, connect to signalr server, it's took about 5-8 seconds, and when all loaded, i call this function to navigate to the HomeScreen:
func checkLogin() {
if let oauth = AppDelegate.shared.authState, oauth.isAuthorized{
initiateFirstScreen("HomeVC", storyboardName : "main")
}else{
initiateFirstScreen("LoginVC", storyboardName : "main")
}
}
func initiateFirstScreen(_ vcName: String, storyboardName : String) {
guard let window = AppDelegate.shared.window else{
AlertUtils.alertMessageWithOkAction(vc: self, mes: Language.get("Something went wrong")){b in
exit(0)
}
return
}
let storyBoard: UIStoryboard = UIStoryboard(name: storyboardName, bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: vcName)
window.rootViewController = vc
window.makeKeyAndVisible()
}
Inside above code, im using window.rootViewController = vc to dislay HomeScreen without any animation.
The problem:
Because of long loading in SplashScreen, when I got pushKit -> show Callkit screen, user may took 3-4 seconds to answer(when the app is killed/swiped)
-> didFinishLaunchingWithOptions called
-> SplashScreen called, and while user is answering call, the "check token, user info, connect to signalr" is loaded
-> Hence, the below function called:
window.rootViewController = vc
window.makeKeyAndVisible()
-> It's clear my callscreen now, that is the problem.
So i want to solved this problem, sorry for my silly question, but it's make me waste 3 days but can not solved this :(
You can use the delegate
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession)
This delegate gets call when you answer the call. So just navigate directly to call screen. and add a flag eg. isCall = true and prevent the user to navigate to Home Screen this time

WatchOS: Pass data from one controller to another through "Next Page" segue

I am loading 3 interfacecontrollers with WKInterfaceController.reloadRootControllers with contexts in my app.
Everything works just fine. However, I need to pass data from Page 1 to Page 2 on swipe. It seems there is not segue that I can use programmatically to create a new context or pass data another way.
How could I solve this?
I don't want to create a singleton just for this. I know how to pass data in contexts with other segues, this question specifically relate to "Next Page" navigation on Watchkit. I couldn't find an answer so far.
Thanks!
Markus
Have you tried using contextForSegue in your first page?
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
let myDate = "today"
return myDate
}
I'm been looking around for an answer for this question also..
NextPage Segue is similar to UIPageViewController in iOS. I have tried contextForSegue(withIdentifier segueIdentifier: String) but it is not getting called on swipe, and you can't give this segue an identifier in the storyboard.
The only way around it for me was to use NotificationCenter
In the first InterfaceController I post a notification with the object on willDisappear/didDeactivate doesn't really make a difference with watchOS and on the second InterfaceController I subscribed to it.
NotificationCenter.default.post(name: Notification.Name("interface1WillDisappear"), object: context)
NotificationCenter.default.addObserver(self, selector: #selector(awake(withNotification:)), name: Notification.Name("interface1WillDisappear"), object: nil)
Add I added this func
func awake(withNotification notification: Notification) {
guard let context = context as? MyContextObject else { return }
print(context)
}
Finally don't forget to remove the observer
deinit {
NotificationCenter.default.removeObserver(self)
}
Hope this helps!

Run a function N time anywhere in the app in Swift

I trying to make a calling app for my project and I want to add a function that keeps checking if someone if calling. My app uses Firebase where I have a key for each users to check if he made a call or not.
There's two problem I am facing here, the first one is, as I said, that I want my function to keep checking anywhere in the app for an incoming call. The other problem is that i have a viewcontroller that I want to pop up when someone is calling. I have found this code on github but it uses navigationcontroller which I am not using in my app :
extension UIViewController{
func presentViewControllerFromVisibleViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let navigationController = self as? UINavigationController, let topViewController = navigationController.topViewController {
topViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else if (presentedViewController != nil) {
presentedViewController!.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else {
present(viewControllerToPresent, animated: true, completion: completion)
}
}
}
For your question on monitoring when incoming calls occur and to be called as a result, see this answer. It's probably what you need (I've never tried it, however). The example shows creating a CXCallObserver and setting your AppDelegate as delegate.
For your second question, I'd first try this answer which leverages the window.rootViewController so you can do this from your AppDelegate. Generally, the root VC is your friend when trying to do UI your AppDelegate. :)
A better answer based on Alex's added comments:
I'd first look at how to set up an observer to your Firebase model so that you can get a callback. If you don't have a way to do that, I'd use KVO on the Firebase model property. But to do exactly as you're requesting, and to do so lazily from AppDelegate (rather than from a singleton), see this code:
// In AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool
{
self.timerToCheckForCalls = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
func timerFired()
{
let didCall = // TODO: query your Firebase model...
guard didCall == true else
{
return
}
self.displayCallerView()
}
func displayCallerView()
{
// See below link.
}
See this answer for how to present your view controller, even when your app might be showing an action sheet, alert, etc... which I think you'd especially value since you need to display the caller regardless of what your app is doing.
Note while user is scrolling a UITextView, the timer won't fire yet. There may be other situations where the timer could be delayed too. So it really would be best to observe your Firebase model or receive a KVO callback than to use a timer.
If you want to make a function that can be called from anywhere, use a singleton pattern. You can also use that to store your special view controller.
Bear in mind that this code SHOULD NOT considered fully functioning code and will need to be customized by you to suit your needs.
class MyClass {
let shared = MyClass()
var viewController: SpecialViewController?
func checkForCall() {
// do function stuff
}
func getSpecialViewController() {
let storyBoard = UIStoryboard.init(name: "main", bundle: nil)
// keep it so we don't have to instantiate it every time
if viewController == nil {
viewController = storyBoard.instantiateViewController(withIdentifier: "SomeViewController")
}
return viewController
}
}
// Make an extension for UIViewController so that they can all
// use this function
extension UIViewController {
func presentSpecialViewController() {
let vc = MyClass.shared.getSpecialViewController()
present(vc, animated: false, completion: nil)
}
}
Somewhere in your code:
// in some function
MyClass.shared.checkForCall()
Somewhere else in code:
presentSpecialViewController()

Populate a Navigation Controller on First Launch

When my app is first launched I want to create some sample data that new users will see. I'd like them to start a level (maybe more) into the navigation controller, like so:
tableViewController0 -> tvc1 (user starts here)
Picture a notes app that has folders as its top level of navigation. You might want to show the user a few sample notes in a sample folder first, then let him/her go back later and create new folders.
My thought was that I'd run a method in application didFinishLaunchingWithOptions that would check for first launch (checking/setting a Bool in NSUserDefaults) and then, if we are in the first launch, create some sample data. Then I thought I could just create each view controller and set my UINavigationController's viewControllers property, but I get this error:
'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
(The cell definitely does have an identifier Cell in the storyboard and works if I don't create the data and view controllers beforehand.)
Some sample code from my AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// From Xcode's stock AppDelegate
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController
// Check for first launch, get back a sample object.
if isFirstLaunch == true {
let newObject = prepareFirstLaunch()
let tvc0 = TableViewController0()
tvc0.managedObjectContext = managedObjectContext
let tvc1 = TableViewController1()
tvc1.someObject = newObject
masterNavigationController.viewControllers = [tvc1, tvc0]
} else {
// this is moved from the stock AppDelegate down into this else statement.
let controller = masterNavigationController.topViewController as! TableViewController0
controller.managedObjectContext = self.managedObjectContext
}
return true
}
private func isFirstLaunch() -> Bool {
// return whether we're launching for the first time
}
private func prepareSampleObject() -> SomeObject {
/*
If we're launching for the first time
create someObject, create some other objects that are owned
by this object in CoreData, set up their relationships, etc.
*/
return someObject
}
Is there another way I can set this up so the user can jump right into a populated navigation stack rather than having to start at the top level?
You're using storyboards, which means you have an initial view controller.
Make this initial view controller a UINavigationController whose rootViewController is some SetupViewController where all of your checking logic occurs. Show a UIActivityIndicatorView in it, or whatever loading animation. Then, depending on what you've found, push either the dummy notes screen or the top-level folder screen.
In the storyboard, you will create two segues from the SetupViewController--one to the notes, one to the folder. Give each segue its own name. You call performSegueWithIdentifier in the code where you determine which screen is getting pushed.

AppDelegate, DidFinishLaunchingWithOptions, ImplicitlyUnwrappedOptional What does it mean?

The app build successfully with no error in debugging area, but immediately stops and bring me to this;
I'm not sure what to make of the error. I can only guess its in the AppDelegate.swift, somewhere along DidFinishLanchingWithOptions.
Does anyone know how to solve this error?
Sorry, not smart enough to figure out this probably trivial error to you guys
UPDATE:
I've tried the suggestion by user mstysf below, but the same problem occur? Am i doing it wrong or missing something?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
if let navigationController = self.window?.rootViewController as? UINavigationController {
let challengesController = navigationController.topViewController as ChallengesViewController
unarchiveDataSource()
if let dataSource = challengeDataSource {
challengesController.challengeDataSource = dataSource
} else {
loadDefaultChallenges()
challengesController.challengeDataSource = challengeDataSource
}
}
return true
}
Does anyone know what is wrong? Thanks again, any help is appreciated.
It means the navigationController is nil. You should handle with it like this:
if let navigationController = self.window?.rootViewController as? UINavigationController {
// do your work here
}
It builds successfully because compiler trusts you navigationController won't be nil and does not check it. That's the point of implicitly unwrapped optional type. In the runtime if it is nil then your app crashes. That's the danger of implicitly unwrapped optionals.