How can I present a ViewController using applicationdidBecomeActive func with some exceptions? - swift

Using applicationDidBecomeActive in AppDelegate is a good way of presenting a specific ViewController each time an ios app becomes active. But how can I override This function and make a few exceptions of when to show the view controller or not after the app becomes active again. When I pick an image from UIImgaePickerController, the view controller shows up again. how can I make my app active even if it’s not to avoid the rootcontroller assigned in applicationDidBecomeActive() to popup again.

If I understood the question correctly you want to change the root view controller when something happens while using your app, so you can put this piece of code in your AppDelegate: this just catches the current rootViewController, set the new one and dismisses and remove the old one inside a transition
func changeRootViewController(with viewController: UIViewController) {
guard let oldViewController = self.window?.rootViewController else { return }
UIView.transition(from: oldViewController.view, to: viewController.view, duration: 0.3, options: [.transitionCrossDissolve, .allowAnimatedContent]) { _ in
self.window!.rootViewController = viewController
self.window!.makeKeyAndVisible()
oldViewController.dismiss(animated: false) {
oldViewController.view.removeFromSuperview()
}
}
}
then, when you need to change the rootViewController you can just:
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.changeRootViewController(with: MyNewViewController())

Related

How to check if UIViewController is already being displayed?

I'm working on an app that displays a today extension with some information. When I tap on the today extension, it opens the app and navigates to a subview from the root to display the information. Normally the user would then click the back arrow to go back to the main view, but there is no way to tell if this is actually done. It is possible for the user to go back to the today extension and tap again. When this is done, the subview is opened once again with new information. If this is done a bunch of times, I end up with a bunch of instances of the subview and I have to click the back button on each of them to get back to the main view.
My question: Is it possible to check if the subview is already visible? I'd like to be able to just send updated information to it, instead of having to display an entirely new view.
I am currently handling this by keeping the instance of the UIViewController at the top of my root. If it is not nil, then I just pass the information to it and redraw. If it is nil, then I call performSegue and create a new one.
I just think that there must be a better way of handling this.
Edit: Thanks to the commenter below, I came up with this code that seems to do what I need.
if let quoteView = self.navigationController?.topViewController as? ShowQuoteVC {
quoteView.updateQuoteInformation(usingQuote: QuoteService.instance.getQuote(byQuoteNumber: quote))
}
else {
performSegue(withIdentifier: "showQuote", sender: quote)
}
This is different from the suggested post where the answer is:
if (self.navigationController.topViewController == self) {
//the view is currently displayed
}
In this case, it didn't work because I when I come in to the app from the Today Extension, it goes to the root view controller. I needed to check whether a subview is being displayed, and self.navigationController.topViewcontroller == self will never work because I am not checking to see if the top view controller is the root view controller. The suggestions in this post are more applicable to what I am trying to accomplish.
u can use this extension to check for currently displayed through the UIApplication UIViewController:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
and usage example:
if let topController = UIApplication.topViewController() {
if !topController.isKind(of: MainViewController.self) { //MainViewController- the controller u wish to equal its type
// do action...
}
}

Swift -injecting vars in ViewControllers

I am proudly new to iOS developing and I am trying to build my first app. I am doing a course on an online platform which does the following in the
AppDelegate -> application didFinishLaunchingWithOptions:
let navigationController = window?.rootViewController as! UINavigationController
let notebooksListViewController = navigationController.topViewController as! NotebooksListViewController
notebooksListViewController.dataController = dataController
This app has a Navigation controller which begins with an UIViewController.
I have 2 questions here, first is why this works, I mean, I am in AppDelegate, so the NotebooksListViewController (first view of the app) is not instantiated yet (I think), so why I am able to inject a variable in it?
On the other hand, the second question, is how can I do this in a different scene? I have a TabBarViewController as first scene, and the first tab is a UITableViewController and I want to inject the same way my dataController var, how can I accomplish this? I could not get to do it, neither understand it.
Thanks in advance.
It works, because of some Xcode magic:
In your Target Setting, General tab, the Main Interface entry specifies the name of the Storyboard to be loaded automatically when your app starts up:
In the storyboard, the Initial View Controller then will be instantiated. It seems like this is an UINavigationController.
Since this is done automatically, it just works - until you want to do something special :-)
If you want to start up with a different scene - maybe from a different view controller - you could just change either the Main Interface to another storyboard, the Initial View Controller (inside the storyboard) or both.
Or, you could just start up by yourself, by leaving the Main Interface empty and create your own view controller inside the app delegate (didFinishLaunchingWithOptions), something like
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let tabVC = mainStoryboard.instantiateViewControllerWithIdentifier("TabCtrl") as? UITabBarController {
self.window?.rootViewController = tabVC
// Access the subcontrollers, or create them
// Initialize their values
// tabVC.viewControllers[0].data = ...
} else {
// Ooops
}
self.window?.makeKeyAndVisible()
Answer to your first question
as the method name is self explanatory didFinishLaunchingWithOptions means your application is didfinish with launching with options and its about to enter in foreground so here application need to set rootViewController so in this method controller you want to set as view controller is initiated thats why you can inject variable in it
answer to second question
let navigationController = window?.rootViewController as! UITabbarController
let VC = navigationController.childViewController
//Now Using VC you can access all you controller of tabbar controller
let notebooksListViewController = navigationController.topViewController as!
NotebooksListViewController
notebooksListViewController.dataController = dataController
now as shown above you can use VC to access you view controllers
but be careful here because VC return viewcontroller array so you need make checks for perticular VC you want to access

How to pop to root controller in app where root viewcontroller is of type UIViewController?

I have implemented SWRevealViewController class for revealing a rear (left and/or right) view controller behind a front controller. SWRevealViewController of type UIViewController is the entry point in the app.
What do I want?
In class AppDelegate when applicationDidEnterBackground is called, I want to pop all the view controllers on the stack except the root view controller and updates the display.
What I tried?
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.window?.rootViewController?.dismiss(animated: true, completion: nil)
}
I know that if SWRevealViewController were embedded in a UINavigationController, I could simply do (appDelegate.window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: true) , but it is not embedded in nav controller and if I embed it, then I will not get the same behaviour, so how can I sort this out?
I believe what you are trying to do is an "unwindSegue".
They are are documented in a technical note:
https://developer.apple.com/library/content/technotes/tn2298/_index.html
With that you should be able to identify your root view controller as the target of the unwind segue and, of needed, follow the unwind through various steps as it occurs.
You can also look at the documentation for the various methods of UIViewController that contain the term "unwind"
https://developer.apple.com/documentation/uikit/uiviewcontroller
Try This:
let navVC = ((UIApplication.shared.delegate as? AppDelegate)?.window)?.rootViewController as? UINavigationController
for VC: UIViewController in (navVC?.viewControllers)! {
if (VC is MYViewController) {
navVC?.popToViewController(VC, animated: true)
break
}
}

How can I refresh an SKScene embed in a previous View Controller when dismissing View Controller with ARSKView?

In my SpriteKit game, I have two View Controllers. One is my MenuViewController, which has an SKView/SKScene in it - this handles the level selection, etc.
I then have my GameSceneViewController - this has an ARSKView/SKScene in it. When a user completes multiple levels and, for example, earns 3 stars for each one, and then uses the pause menu to go back to the MenuViewController, the stars that show for each level are (obviously) not refreshed.
This is because at the moment, when a user wants to go back to the MenuViewController, I simply call:
self.dismiss(animated: true, completion: nil)
I was wondering if there is a function I could call to "refresh" the MenuSceneController when a user dismisses the GameSceneViewController.
First of all add the following method to MenuSceneController
class MenuSceneController: UIViewController {
func reloadScene() {
// write here your logic to reload the scene
}
}
Now when you dismiss GameSceneViewController you can call that method
class GameSceneViewController: UIViewController {
func goBackToMenu() {
guard let menuSceneController = presentingViewController as? MenuSceneController else {
debugPrint("Error, this view controller was not presented by a MenuSceneController")
return
}
self.dismiss(animated: true) {
menuSceneController.reloadScene()
}
}
}

Calling a function of the current UIViewController when using SWRevealViewController

I have an app written in swift that displays location information on different views. I am receiving location updates in AppDelegate (I don't want to use a singleton class since I want the location updates even when the app is in the background).
Now, I am using SWRevealViewController to implement a sidebar menu to toggle between the different views. When a new location update is received, how do I call the function of the viewController that is currently active to update the UI?
I searched a lot and all the solutions that talk about how to find the current UIViewController actually return SWRevealViewController as the current UIViewController, which doesn't help.
I gave it another shot and was able to find my viewController in my app from another view controller. However, I couldn't get my child view controllers app delegate.
Looking forward to other answers...
here's what I did:
let app = UIApplication.sharedApplication().delegate! as! AppDelegate
if let nav = app.window?.rootViewController?.childViewControllers {
nav.forEach { vc in
if let viewControllers = vc as? UINavigationController {
viewControllers.childViewControllers.forEach { vc in
if let x = vc as? ViewController {
print("Got it \(x)")
}
}
}
}
}