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
Related
I have created a new class to implement UITabBarControllerDelegate, but the method in the class does not been invoked.
AppDelegate.swift:
func application() {
// ...
let controller = MyTabItemController()
let tabBarController = UITabBarController()
tabBarController.viewControllers = [controller]
tabBarController.delegate = MyTabBarControllerDelegate()
self.window?.rootViewController = tabBarController
// ...
}
MyTabBarControllerDelegate.swift:
class MyTabBarControllerDelegate: NSObject, UITabBarControllerDelegate{
func tabBarController(/*...*/) {
print("method invoked")
}
}
When I selected the item, the message "method invoked" is not shown.
If I let AppDelegate extend UITabBarControllerDelegate, everything worked well and the message is shown in console.
I want to know why this happened?
The delegate property of UITabBarController is weak. Thus, your delegate is released directly after the assignment. You should keep you delegate object by a strong reference.
I’m trying to pass data between two ViewControllers with the initial call being made from a UITabBarController.
Here is what I’m doing. I’m using a class called RaisedTabBarController to add a custom button to a TabBarController, which works fine displaying the button, my issue is that when I tap the custom button I want it to take me to FirstViewController and then I want to pass data from FirstViewController to SecondViewController via protocols but for some reason I’m getting an error that in my opinion doesn’t make any sense, it complains about a labels not being accessible within SecondViewController.
Here is the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Here is the code…
Class ref from GitHub:
RaisedTabBarController
TabBarController
Here I'm adding the custom button and making the call to go to FirstViewController
import UIKit
/// TabBarController subclasses RaisedTabBarController
class TabBarController: RaisedTabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Insert empty tab item at center index. In this case we have 5 tabs.
self.insertEmptyTabItem("", atIndex: 2)
// Raise the center button with image
let img = UIImage(named: “myImage”)
self.addRaisedButton(img, highlightImage: nil, offset: -10.0)
}
// Handler for raised button
override func onRaisedButton(_ sender: UIButton!) {
super.onRaisedButton(sender)
// Go to FirstViewController
let pvc = storyboard?.instantiateViewController(withIdentifier: “firstStoryBoardID”) as! FirstViewController
/// Here, I’m not sure if this is the right way to tell that
/// SecondViewController will be the delegate not TabBarController, seem to work
pvc.delegate = SecondViewController() as FirstViewControllerDelegate
self.present(pvc, animated:true, completion:nil)
}
}
FirstViewController
From here I want to send data to SecondViewController
protocol FirstViewControllerDelegate {
func messageData(greeting: String)
}
class FirstViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
func sendData() {
self.delegate?.messageData(greeting: “Hello SecondViewController”)
}
}
SecondViewController
Here I want to receive the data sent from FirstViewController
class SecondViewController: UIViewController, FirstViewControllerDelegate{
#IBOutlet weak var labelMessage: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func messageData(greeting: String) {
/// I do get message from FirstViewController
print(" Message received from FirstViewController: \(greeting)")
/// Here I get error, fatal error: unexpectedly found nil while unwrapping an Optional value
/// I think it has something to do with the labelMessage not being accessible, but why?
labelMessage.text = greeting
}
}
Any idea why am I getting the error in SecondViewController, why wouldn't labels be accessible if they are declared in SecondViewController?
Ideally I would like to be able to call method onRaisedButton(_ sender: UIButton!) directly from SecondViewController but without having to subclass RaisedTabBarController. I’m not usr if this would solve the error but I think this would make my code cleaner.
EDIT: 06/19/2017 - Solved
The effect I was looking for can be done directly in XCode, in the storyboards. I stopped using the third party class (RaisedTabBarController), problem solved.
This seems wrong.
pvc.delegate = SecondViewController() as FirstViewControllerDelegate
Try to instantiate the SecondViewController like you did for the first from storyboard.
let svc = storyboard?.instantiateViewController(withIdentifier: “secondStoryBoardID”) as! SecondViewController
And then set the delegate to SecondViewController
pvc.delegate = svc
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
var messageViewController: MessageViewController?
var settingsViewController: SettingsViewController?
var otherViewController: OtherViewController?
override func viewDidLoad() {
messageViewController = //the first one
settingsViewController = //the second one
otherViewController = //the third one
}
And then later down the code, I can call methods in those controllers when needed.
messageViewController.reloadData()
The viewControllers property of UITabBarController is an array of references to each content view controller. If you know exactly which controller is at each index, you can just assign your variables from this array:
override func viewDidLoad() {
super.viewDidLoad()
messageViewController = viewControllers![0] as! MessageViewController
settingsViewController = viewControllers![1] as! SettingsViewController
otherViewController = viewControllers![2] as! OtherViewController
}
Or even make the variables computed properties:
var messageViewController: MessageViewController { return viewControllers![0] as! MessageViewController }
var settingsViewController: SettingsViewController { return viewControllers![1] as! SettingsViewController }
var otherViewController: OtherViewController { return viewControllers![2] as! OtherViewController }
try this,
let firstViewController = self.viewControllers![0] as FirstViewController
let secondViewController = self.viewControllers![1] as SecondViewController
and so on..
You will have to use delegate protocol to send variables or call methods over different View Controllers. I already answered this in another post, so please check out my answer on how to do this:
How to pass a variable from a class to another?.
Another solution(depend on your needs) is to implement one(whoever calls reloadData) to many(all view controllers) relationship. The easiest way to accomplish this is via notifications. You can read more about them here.
In this way your tabBar doesn't have to know about it's child public methods, and moreover doesn't care who listens for it's notifications(it may be even tableview, cell or whatever).
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.