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.
Related
i have a UIHostingController that is hosting a SwiftUI view called CatalogView.
when showing it, an environment object is attached, so basically from UIKit it is shown like this:
let rootCatalogView = CatalogView()
let appState = AppState.get()
let catalogView = UIHostingController(rootView: rootCatalogView.environmentObject(appState))
navigationController.pushViewController(catalogView, animated: true)
now at a later time i need to check if this UIHostingController is in the list of navigationController.viewControllers
the type(of:) is showing the following, which kind of make sense:
UIHostingController<ModifiedContent<CatalogView, _EnvironmentKeyWritingModifier<Optional<AppState>>>>
things like vc.self is UIHostingController.Type or vc.self is UIHostingController< CatalogView >.Type both return false (vc is an element of navigationController.viewControllers
the following obviously works, it returns true, but any change in the initialisation of the UIHostingController will change its type
vc.isKind(of: UIHostingController<ModifiedContent<CatalogView, _EnvironmentKeyWritingModifier<Optional<StoreManager>>>>.self)
how can i check if the view controller is of type UIHostingController?
or at least how can i cast the controller to UIHostingController so that i can check its rootview?
Yes, you can't cast to generic class, but you can declare protocol and implement it only for UIHostingViewController
private protocol AnyUIHostingViewController: AnyObject {}
extension UIHostingController: AnyUIHostingViewController {}
and somewhere in your code
func justMakeMeHappy(viewController: UIViewController) {
if viewController is AnyUIHostingViewController {
print("That is hosting view controller")
} else {
print("That s not uihosting view controller 🤣")
}
}
Due to the generic parameter, we cannot cast the ViewController to find if it is a UIHostingController without knowing the full constraint.
I should note that this it not an ideal fix and it is really just a work around.
UIHostingController is a subclass of UIViewController so we could do the following.
Create a computed property on UIViewController that returns the name of the class that is used to create UIViewController. This gives us something to search for in the list of ViewControllers contained in the UINavigationController
extension UIViewController {
var className: String {
String(describing: Self.self)
}
}
Create a few UIViewController subclasses and our UIHostingController
class FirstViewController: UIViewController {}
class SecondViewController: UIViewController {}
class MyHostingController<Content>: UIHostingController<Content> where Content : View {}
let first = FirstViewController()
let second = SecondViewController()
let hosting = UIHostingController(rootView: Text("I'm in a hosting controller"))
let myHosting = MyHostingController(rootView: Text("My hosting vc"))
We can then add these to a UINavigationController.
let nav = UINavigationController(rootViewController: first)
nav.pushViewController(second, animated: false)
nav.pushViewController(hosting, animated: false)
nav.pushViewController(myHosting, animated: false)
Now that we have some ViewControllers inside our UINavigationController we can now iterate across them and find a ViewController that has a className that contains what we are looking for.
for vc in nav.viewControllers {
print(vc.className)
}
This would print the following to the console:
FirstViewController
SecondViewController
UIHostingController<Text>
MyHostingController<Text>
You can then for-where to find the ViewController in the hierarchy.
for vc in nav.viewControllers where vc.className.contains("UIHostingController") {
// code that should run if its class is UIHostingController
print(vc.className)
}
for vc in nav.viewControllers where vc.className.contains("MyHostingController") {
// code that should run if its class is MyHostingController
print(vc.className)
}
As I said above, this is not an ideal solution but it may help you until there is a a better way of casting without knowing the generic constraint.
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 have 2 view controllers VCA and VCB.
Moving from VCA to VCB, with somevalue, is working fine
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("VCB") as! VCB
vc.entity = somevalue
self.navigationController?.pushViewController(vc, animated: true)
But for reverse, I want to call a method in VCA from VCB after uploading certain data from VCB. And after that refresh textfields valuesin VCA. I could have resfreshing code in viewwillappear in VCA but due to some reason i am n ot doing that but trying to implement delegate.
I have written some code as:
VCA:
class ProfileEditViewController:UIViewControoler, MobileVerifyDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let mobileVC = MobileVerificationViewController()
mobileVC.delegate = self
}
//MARK: - Mobileverify Delegate method
func delegateMethod() {
print("a")
}
}
VCB:
protocol MobileVerifyDelegate{
func delegateMethod()
}
class MobileVerificationViewController: UIViewController{
var delegate: MobileVerifyDelegate! = nil
func certainFunction(){
//aftersuccessful upload
self?.delegate.delegateMethod()// code crashes
}
}
Thanks in advance
In your viewDidLoad of VCA you've created mobileVC but when you transition to VCB, you're creating a new instance of VCB named vc. mobileVC is unused the way it is. You have a few options:
make mobileVC a class property or set the delegate while creating vc.
The latter would be:
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("VCB") as! VCB
vc.entity = someValue
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
On a sidenote, make your delegate confirming the class protocol so you can set the delegate as weak.
protocol MobileVerifyDelegate: class {
func delegateMethod()
}
class MobileVerificationViewController: UIViewController {
weak var delegate: MobileVerifyDelegate?
func certainFunction() {
// After successful upload
delegate?.delegateMethod()
}
}
Notice that when you set an implicitly unwrapped property, it is already nil. So it's redundant to set it as nil again.
var delegate: MobileVerifyDelegate! = nil // "= nil" is not needed
I don't know what your case is but the easiest solution would be to move the delegate method and delegate to VCB. If for whatever reason you VCA has to be the delegate class then you need to create an instance of it or pass it to VCB.
//Set delegate when you push to the new controller in VCA
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("VCB") as! VCB
vc.entity = somevalue
vc.delegate = self //Sets VCA as VCB's Delegate
self.navigationController?.pushViewController(vc, animated: true)
//Inside VCB
self.delegateMethod() //Now you can call any delegate protocol methods from VCA
Ya, Delegate is the way that you need to get what you aspected. there is some problem in completely implementing delegate in swift. here i provide link which fully guide you how to implemented delegate in swift.
Delegate In Swift
As you said that, App crash on calling delegate. that means, you method is not available in VCA or delegate has not reference of VCA. kindly check this two condition.
Thanks
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.