I'm trying to access the frontmost controller of the Application during the user navigation using this code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println(UIApplication.sharedApplication().keyWindow?.rootViewController)
}
But it seems that the rootViewController always refers to the first controller defined by the storyboard independently by when I'm accessing that property.
Is there something I'm doing wrong or I've misunderstood about the rootViewController property?
rootViewController is indeed the topmost, ultimate view controller owned by UIWindow.
To get the currently displaying view controller, you need to walk down the controller hierarchy. Here is an Objective-C category that you can add to your application, and using a bridging header you'll easily be able to call this UIWindow category from your swift code.
OK, based on the code that Michael pointed out, I wrote some Swift (1.2) code to do the same thing. You can add this as an extension to UIViewController (as I did), UIApplication, or for that matter simply make it a global function.
extension UIViewController {
static func getVisibleViewController () -> UIViewController {
let rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
return getVisibleViewControllerFrom(rootViewController!)
}
static func getVisibleViewControllerFrom(viewController: UIViewController) -> UIViewController {
let vcToReturn: UIViewController
if let navController = viewController as? UINavigationController {
vcToReturn = UIViewController.getVisibleViewControllerFrom(navController.visibleViewController)
}
else if let tabBarController = viewController as? UITabBarController {
vcToReturn = UIViewController.getVisibleViewControllerFrom(tabBarController.selectedViewController!)
}
else {
if let presentedViewController = viewController.presentedViewController {
vcToReturn = UIViewController.getVisibleViewControllerFrom(presentedViewController)
}
else {
vcToReturn = viewController
}
}
return vcToReturn
}
}
You'd call this in the following way:
let visibleViewController = UIViewController.getVisibleViewController()
Hope this helps.
Andrew
PS I haven't tried this in Swift 2.0 yet, so I can't guarantee it will work without issues there. I know it won't work (as written) in Swift 1.1 or 1.0.
Related
I want to produce some function when user comes out of app
I found applicationDidEnterBackground function in appDelegate file but I can't reach view controller which is open right now therefore I can't reach needed function
I can't use instantiateViewController function because it creates a new one, but I need the info stored in the view which is already open
Is there any way to call function of already open instance of view controller???
I sometimes have a similar requirement, so I made a UIViewController extension with a static method to return the "top" viewController. It looks like this:
extension UIViewController {
static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? {
let viewController = viewController ?? UIApplication.shared.keyWindow?.rootViewController
if let navigationController = viewController as? UINavigationController, !navigationController.viewControllers.isEmpty {
return self.topViewController(navigationController.viewControllers.last)
} else if let tabBarController = viewController as? UITabBarController,
let selectedController = tabBarController.selectedViewController {
return self.topViewController(selectedController)
} else if let presentedController = viewController?.presentedViewController {
return self.topViewController(presentedController)
}
return viewController
}
}
This allows you to get a reference to the viewController being displayed by calling let top: UIViewController? = UIViewController.topViewController().
I found some way
I am pretty sure it is real stupid way, but it works in my situation because I exactly know which View Controller I need
1st step: create a variable in appDelegate class
class AppDelegate: UIResponder, UIApplicationDelegate {
var myViewController:ExampleViewController?
2nd step: in myViewController class
override func viewDidLoad() {
super.viewDidLoad()
let delegate = UIApplication.shared.delegate as! AppDelegate
delegate. myViewController = self
}
And now I can call any function of myViewController class from AppDelegate class
variable of view controller is optional so I unwrap it with "?" sign and don't have problems when app didEnterBackground from other view controllers
I'm trying to learn how to integrate coordinator pattern into iOS development.
I have an app which like this. In the storyboard, it looks like this. The navigation controllers and tabbars are not added in the storyboard because according to coordinator pattern, they will be added programatically.
The first view controller is PhoneViewController which takes user's phone number. This view controller is embedded in a navigation controller. After entering the phone number, it moves to the VerifyPhoneViewController. After verification, it moves to MainViewController a tabbarcontroller which contains three tabs. Each of these view controller will have a separate navigation controller of their own.
I have a protocol which contains all the necessary properties and functions each coordinator needs to implement.
protocol Coordinator {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
I created a separate coordinator called AuthCoordinator for the authentication flow part of the app.
class AuthCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
// The initial view
func start() {
let phoneViewController = PhoneViewController.instantiate()
phoneViewController.coordinator = self
navigationController.pushViewController(phoneViewController, animated: false)
}
func submit(phoneNo: String) {
let verifyPhoneViewController = VerifyPhoneViewController.instantiate()
verifyPhoneViewController.coordinator = self
verifyPhoneViewController.phoneNo = phoneNo
navigationController.pushViewController(verifyPhoneViewController, animated: true)
}
// Move to the tabbarcontroller
func main() {
let mainViewController = MainViewController.instantiate()
navigationController.pushViewController(mainViewController, animated: true)
}
}
The navigation works fine. However there's a small issue.
Notice after moving to the tabbarcontroller, the titles don't show in the navigationbar when I switch between view controllers (I do set them in viewDidLoad method of each view controller). Plus the back button to VerifyPhoneViewController is still there too.
The issue is obvious. The navigationcontroller I initialized for the AuthCoordinator is still there at the top. I'm literally pushing the MainViewController on to that stack.
func main() {
let mainViewController = MainViewController.instantiate()
navigationController.pushViewController(mainViewController, animated: true)
}
What I can't figure out is a way to not do it like this. I can hide the navigationbar in the start method but then it's not ideal because well, it hides the navigationbar and I don't want that.
func start() {
let phoneViewController = PhoneViewController.instantiate()
phoneViewController.coordinator = self
navigationController.navigationBar.isHidden = true
navigationController.pushViewController(phoneViewController, animated: false)
}
Is there a different way to keep the navigationcontroller for the duration of the auth flow and then discard it when/soon after showing the MainViewController?
The demo project is uploaded here.
I have a Tabbed App with two tabs... the first tab has the main Action, and the second has Settings that can be updated. I am trying to pass some variables data from Settings to the Action tab. Based on some suggestions, I have used the following code for the Update button:
#IBAction func updateBut(_ sender: Any) {
let myVC = self.storyboard?.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
myVC.totTime = totalTime
myVC.timeInt = intTime
self.present(myVC, animated: true, completion: nil)
}
The data does pass to the first view controller, however, the tabs have disappeared on this view now. So, how can I get the tabs back on the screen? I am quite the beginner to any form of app development, and am just trying to learn by doing... the Tabbed App has been created using one of the Xcode New Project templates. Thanks.
try this way
(self.tabBarController?.viewControllers?[0] as! FirstViewController).totTime = totalTime
(self.tabBarController?.viewControllers?[0] as! FirstViewController).timeInt = intTime
self.tabBarController?.selectedIndex = 0
self.tabBarController?.tabBar.isHidden = false try this
A much better way to pass data by using protocols, you can define a protocol like below
protocol PassDataDelegate{
func updateFirstVc(totalTime:String)
}
and in your SecondViewController class have a delegate property like below
class SecondViewController:UIViewController{
myDelegate:PassDataDelegate?//declaration part
#IBAction func updateBut(_ sender: Any) {
myDelegate.updateFirstVc(totalTime:totalTime)
}
}
And finally in your UITabController class implement the protocol
class myTabController:UITabController,PassDataDelegate{
var firstController:FirstViewController? //declaration part
var secondController:SecondViewController?
override func viewDidLoad(){
super.viewDidLoad()
//initialize your view controller here
self.firstViewController = FirstViewController()
self.secondViewController = SecondViewController()
self.secondViewController.myDelegate = self //assign delegate to second vc
self.viewcontrollers = [firstController, secondController]
}
updateFirstVc(totalTime:totalTime){
self.firstViewController?.totTime = totalTime
self.selectedIndex = 0// here change the tab to first vc if you want to switch the tab after passing data
}
}
I am new to iOS correct me If I am wrong,
The reason why the TabBar is not visible since you are instantiating new FirstViewController which is present on top of your TabBar.
TabBar by default does this Job or Add the new viewController to the TabBar Stack.
tabBarController.viewControllers.append(myVC)
For passing the data TabBar holds the reference of all its ViewControllers. So you can set or get in each other ViewControllers like this
var yourData{
set{
let yourVC = self.tabBarController?.viewController[0] as? FirstViewController ?? ErrorClass
yourVC.dataObj = newValue
}
get{
let yourVC = self.tabBarController?.viewController[0] as? FirstViewController ?? ErrorClass
return yourVC.dataObj
}
I've been following this tutorial to create a custom tab bar controller for an iPad app as I would like to implement a vertical tab bar. However, I would like one of the tabs to present a UISplitViewController, whilst the others just present UIViewControllers. My questions are:
1) Will this be accepted by the app store? Apple's documentation currently states adding UISplitViews as child views is not recommended but may be implemented with certain containers. Anyone had any experience with this?
2) Here is an extract from my custom tab bar controller. If secondViewController is presenting the UISplitView, can I leave it as is? I mean, it seems to work find when I run it, but is it acceptable?
class CustomTabBarController: UIViewController {
#IBOutlet weak var tabView: UIView!
#IBOutlet var tabButtons: [UIButton]!
var firstViewController: UIViewController!
var secondViewController: UISplitViewController!
var thirdViewController: UIViewController!
var viewControllerArray: [UIViewController]!
var selectedTabIndex: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
firstViewController = storyboard.instantiateViewController(withIdentifier: "firstVC")
secondViewController = storyboard.instantiateViewController(withIdentifier: "secondVC") as! UISplitViewController
thirdViewController = storyboard.instantiateViewController(withIdentifier: "thirdVC")
viewControllerArray = [firstViewController, secondViewController, thirdViewController]
tabButtons[selectedTabIndex].isSelected = true
didPressTab(tabButtons[selectedTabIndex])
}
3) I can't really get my head around what (if anything) needs to go in AppDelegate? Again seems to run fine but just wondering if its safe.
Thanks.
1) I believe Apple is simply recommending against this as potentially bad design, since they refer you to the Human Interface Guidelines. You don't always have to agree with their recommendations and very rarely will your app get rejected for design choices- the only instances off the top of my head would be mimicking the App Store or other core OS functionality.
2) If, as you say, this is working, I don't see any glaring issue.
3) Again if it's working, you may not need to do anything. But here's how Apple sets up their template for a Master-Detail app:
If your splitViewController is set up like this and you want the same functionality as this template, here's how you should be able to get it.
First add this to the very bottom of AppDelegate.swift:
extension AppDelegate: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
if topAsDetailController.detailItem == nil {
// Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return true
}
return false
}
}
Then, add this to the end of viewDidLoad in CustomTabBarController:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let navigationController = secondViewController.viewControllers[secondViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController!.navigationItem.leftBarButtonItem = secondViewController.displayModeButtonItem
secondViewController.delegate = appDelegate
I have a standard Master-Detail Application, and I'm trying to conditionally show/hide the status bar.
Overriding prefersStatusBarHidden() in MasterViewController does nothing. It never even gets called.
override func prefersStatusBarHidden() -> Bool {
return true
}
Setting UIViewControllerBasedStatusBarAppearance in Info.plist doesn't help, presumably since YES is already the default value. Calling setNeedsStatusBarAppearanceUpdate() doesn't help either.
I am targeting iOS 9.
There is a little bit cleaner solution. There is a function childViewControllerForStatusBarHidden which is specifically designed to return a child view controller to which prefersStatusBarHidden should be forwarded.
So, it will be better to override it. It will look like this:
override func childViewControllerForStatusBarHidden() -> UIViewController? {
if var topViewController = self.viewControllers.first {
if let navigationController = topViewController as? UINavigationController {
topViewController = navigationController.topViewController!
}
return topViewController
}
return super.childViewControllerForStatusBarHidden()
}
And probably you can even omit following. NavigationViewController has childViewControllerForStatusBarHidden() on it's own which will send it to child viewcontroller.
if let navigationController = topViewController as? UINavigationController {
topViewController = navigationController.topViewController!
}
The answer is to override prefersStatusBarHidden() starting from the window's root view controller. In a Master-Detail Application, this requires subclassing UISplitViewController to forward the message down the view controller hierarchy.
Something like this:
override func prefersStatusBarHidden() -> Bool {
if var topViewController = self.viewControllers.first {
if let navigationController = topViewController as? UINavigationController {
topViewController = navigationController.topViewController!
}
return topViewController.prefersStatusBarHidden()
}
return super.prefersStatusBarHidden()
}
If you're fine doing it for all your split view controllers, this works for me:
extension UISplitViewController {
override open var childForStatusBarHidden: UIViewController? {
return (viewControllers.last as? UINavigationController)?.visibleViewController
}
}