Move to different tab controller index from different class - ios

I am having this problem for quite awhile, I have a slider menu use third party library, and I have an option to move to different indexSelected for the tab bar but it is in the different class.
AppDelegate.swift
var main: FirstViewController = FirstViewController()
func getMain() -> FirstViewController{
return main
}
SliderMenuViewController.swift
slideMenuController()?.closeLeftNonAnimation()
DispatchQueue.main.asyncAfter(deadline: .now() + 1 ) {
let abc: AppDelegate = UIApplication.shared.delegate as! AppDelegate
let tst = abc.getMain()
tst.moveTab()
FirstViewController.swift
func moveTab(){
DispatchQueue.main.async {
self.tabBarController?.selectedIndex = 2
}
}

Related

how to unit test UIApplication extension

Suppose I use this code that extracts the top most viewController
import UIKit
extension UIApplication {
class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = base as? UINavigationController, navigationController.viewControllers.count > 0 {
return topViewController(navigationController.visibleViewController)
}
if let tabBarController = base as? UITabBarController {
if let selected = tabBarController.selectedViewController {
return topViewController(selected)
}
}
if let presentedViewController = base?.presentedViewController {
return topViewController(presentedViewController)
}
return base
}
}
How do I facilitate unit testing of this code? I would need to use an instance of UIApplication.shared. Any tips would be appreciated.
If instead this was an extension to UIViewController, you could omit the parameter (base) altogether.
The call would then change to
let top = UIApplication.shared.keyWindow?.rootViewController.topViewController()
In order to unit test this, we can simply create a ViewController and perform our tests.

UI View Controller is not updated when using VIPER Architecture

So, I am new to VIPER and have built simple demo app of http request using the architecture. I got an issue where the UI are not updated although the the method within the view is still called because I have checked that the print("test") is executed and shown on the debug console. This is the relevant code:
View :
private func getAllContacts(){
self.contacts = []
self.hud.show(in: self.view)
self.presenter?.fetchListContacts()
print("asd")
}
Presenter :
func fetchListContacts(){
print("sdf")
interactor?.getContacts()
}
Interactor :
func getContacts(){
print("123")
var contacts : [DetailContact] = []
contacts = \\APICALL
self.presenter?.listContactFetchSuccess(contacts)
}
Baxk to PResenter :
func listContactFetchSuccess(_ contacts: [DetailContact]) {
print("gxx")
view?.fetchSucceed(contacts: contacts)
}
back to View :
func fetchSucceed(contacts: [DetailContact]) {
self.contacts = contacts
self.hud.dismiss()
self.contactTableView.reloadData()
print("test")
}
My Router :
static func createListContactModule() -> ListViewController {
let view = mainstoryboard.instantiateViewController(withIdentifier: "ListViewController") as! ListViewController
var presenter: ViewToPresenterListProtocol & InteractorToPresenterListProtocol = ListContactPresenter()
var interactor: PresenterToInteractorListProtocol = ListContactInteractor()
let router:PresenterToRouterListProtocol = ListContactRouter()
view.presenter = presenter
presenter.view = view
presenter.router = router
presenter.interactor = interactor
interactor.presenter = presenter
return view
}
The progress hud at least closes? or does not suffer any change?
I suggest you put the content of your function fetchSucceed(contacts: [DetailContact]) into the main queue like:
func fetchSucceed(contacts: [DetailContact]) {
DispatchQueue.main.async {
self.contacts = contacts
self.hud.dismiss()
self.contactTableView.reloadData()
print("test")
}
}
Maybe the API call that you use, is changing the thread of the excecution therefore your UI calls are not showing, because it always must be excecuted in the main thread.

What is the proper way to get root view controller in iOS 13?

class TopViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Code Block 1
let controller = getTopController()
print(controller)// Prints out MyTestProject.TopViewController
//Code Block 2
let controller2 = getRootController()
print(controller2)//Prints out nil , because keywindow is also nil upto this point.
//Code Block 3
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
let controller2 = self.getRootController()
print(controller2)// Prints out MyTestProject.TopViewController
}
}
func getTopController() -> UIViewController? {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let sceneDelegate = windowScene.delegate as? SceneDelegate else {
return nil
}
return sceneDelegate.window?.rootViewController
}
func getRootController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
let topController = keyWindow?.rootViewController
return topController
}
}
Since iOS 13 there is two approach to get current active / top view controller of the app.
here:
getTopController() and getRootController() shows both of the approaches.
As commented in codes besides print() results are different though.
In Code Block 2:
getRootController can't find the window yet so it prints out nil. Why is this happening?
Also, which is the full proof method of getting reference to top controller in iOS 13, I am confused now?
The problem is that when your view controller viewDidLoad window.makeKey() has not been called yet.
A possible workaround is to get the first window in the windows array if a key window is not available.
func getRootController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) ?? UIApplication.shared.windows.first
let topController = keyWindow?.rootViewController
return topController
}
Please note that this will solve your problem but you should postpone any operation that involve using a key window until it is such.
According to the documentation of UIView, the window property is nil if the view has not yet been added to a window which is the case when viewDidLoad is called.
Try to access that in viewDidAppear
override func viewDidAppear(_ animated: Bool) {
let controller2 = self.view.window.rootViewController
}

Get another tab's viewcontroller variable in current tab bar - iOS - Swift

I have a tabBarController with below hierarchy
TabBarController
Tab 1 -> Contains Navigation Controller(NavController1) -> ViewController1 -has--> ContainerView --contains--> DisplayedViewControllerTab1 (this is my tab1 view controller displayed)
Variable dataForVC1 is in DisplayedViewControllerTab1
When user taps Tab3 (DisplayedViewControllerTab3), I'm trying to get value of dataForVC1 to pass to tab3 viewController
So far I've tried this
In in TabBarController - didSelect method
var data: ModelData?
if let navController = tabBarController.viewControllers?[0] as? NavController1,
let childNavVC = navController.children.first as? ViewController1 {
//Get container view
let conView = childNavVC.containerView. //This is outlet
//Looking for something like this - struck here
if let displayedVC1 = "Container view's VC as? DisplayedViewControllerTab1 {
data = displayedVC1.dataForVC1
}
}
Kindly advice how to achieve this
You don't need to hustle around with container views. In your ViewController1 simply add references to the DisplayedViewControllerTab1 or other vc's you need and then access them directly.
Your VC code would look something like this.
class ViewController1: UIViewController {
//...
var displayedVC1: DisplayedViewControllerTab1?
//...
}
And then your code now:
var data: ModelData?
if let navController = tabBarController.viewControllers?[0] as? NavController1,
let childNavVC = navController.children.first as? ViewController1 {
data = childNavVC.displayedVc1
}
P.S. For convenience, if you have your own TabBarController class you can add all the references you need when the controller is created or simply have computed properties, so you don't need to go deep in the hierarchy all the time.
In your TabbarViewController class:
var myViewController1: MyViewControllerOne? {
return (viewControllers[0] as? NavController)?.children.first as? MyViewControllerOne
}
var myViewController2: MyViewControllerTwo? {
return (viewControllers[1] as? NavController)?.children.first as? MyViewControllerTwo
}

performSegueWithIdentifier not working when being called from new instance of viewController in Swift

My mission
When app receive notification and user taps on the notification i want to redirect the user to the correct View. In my case, SingleApplicationViewController.
Current code
PushNotification.swift - A class with static functions to handle behaviors when receiving Push Notifications
The __getNavigationController returns a specific NavigationController based on a tab -and viewIndex from TabBarController.
internal static func __getNavigationController(tabIndex: Int, viewIndex: Int) -> UINavigationController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let window:UIWindow? = (UIApplication.sharedApplication().delegate?.window)!
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyBoard.instantiateViewControllerWithIdentifier("MainEntry")
window?.rootViewController = viewController
let rootViewController = appDelegate.window!.rootViewController as! UITabBarController
rootViewController.selectedIndex = tabIndex
let nav = rootViewController.viewControllers![viewIndex] as! UINavigationController
return nav
}
The applicationClicked is being called when user click on notification and that method calls on __getApplication to fetch the application from the db with the objectId received in the push notification and then instantiate a GroupTableViewController to perform segue to the SingleApplicationViewController.
(TabbarController -> Navigation Controller -> GroupTableViewController -> SingleApplicationViewController)
What is a bit strange is when I set tabIndex to 0 and viewIndex to 1. The GroupView however is on second tab (tab 1) and the view controller should be the first (0). But when I set them to the corresponding numbers, I receive nil and the application crashes.
I read that you will force the view controller to load when doing _ = groupTableViewController.view and which it actually does. When this is being called, the viewDidLoad -function is being called.
/************** APPLICATION ***************/
static func applicationClicked(objectId: String) {
__getApplication(objectId) { (application, error) in
if application != nil && error == nil {
let nav = __getNavigationController(0, viewIndex: 1)
let groupTableViewController = nav.viewControllers.first as! GroupsTableViewController
_ = groupTableViewController.view
groupTableViewController.performSegueWithIdentifier("GroupTableToApplicationToDetailApplication", sender: application!)
} else {
// Hanlde error
}
}
}
GroupTableViewController.prepareForSegue()
Here I create a new instance of the ApplicationTableViewController, which is a middle step before getting to SingleApplicationViewController
} else if segue.identifier == "GroupTableToApplicationToDetailApplication" {
let navC = segue.destinationViewController as! UINavigationController
let controller = navC.topViewController as! ApplicationViewController
controller.performSegueWithIdentifier("ApplicationsToSingleApplicationSegue", sender: sender as! Application)
}
So, what's not working?
Well, the prepareForSegue in GroupTableViewController is not being called. I use the same code structure on my TimeLineViewController, and almost the exact same code, when getting another Push Notification and it works perfectly. In that case I use tabIndex 0 and viewIndex 0 to get the proper NavigationController.
Please, any thoughts and/or suggestions is more than welcome!
There is change in following method..
internal static func __getNavigationController(tabIndex: Int) -> UINavigationController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let window:UIWindow? = (UIApplication.sharedApplication().delegate?.window)!
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyBoard.instantiateViewControllerWithIdentifier("MainEntry")
window?.rootViewController = viewController
let rootViewController = appDelegate.window!.rootViewController as! UITabBarController
rootViewController.selectedIndex = tabIndex
let nav = rootViewController.selectedViewController as! UINavigationController //This will return navigation controller..
//No need of viewIndex..
return nav
}
you have written
let nav = rootViewController.viewControllers![viewIndex] as! UINavigationController
change to rootViewController.selectedViewController give you UINavigationController.
Here you get navigavtion controller object..In your applicationClicked method nav object might be nil so it can not execute further performsegue code.
Check following method.
/************** APPLICATION ***************/
static func applicationClicked(objectId: String) {
__getApplication(objectId) { (application, error) in
if application != nil && error == nil {
let nav = __getNavigationController(0)//0 is your tab index..if you want 1 then replace it with 1
let groupTableViewController = nav.viewControllers.first as! GroupsTableViewController //Rootview controller of Nav Controller
groupTableViewController.performSegueWithIdentifier("GroupTableToApplicationToDetailApplication", sender: application!) //Perform seque from Root VC...
} else {
// Hanlde error
}
}
}

Resources