Traverse view controller hierarchy in Swift
Your method should return T?
instead of UIViewController?
, so that the generic type
can be inferred from the context. Checking for the wanted class has also to
be done inside the loop, not only once before the loop.
This should work:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parentViewController {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
Example usage:
if let vc = self.traverseAndFindClass() as SpecialViewController? {
// ....
}
Update: The above method does not work as expected (at least not in the Debug
configuration) and I have posted the problem
as a separate question: Optional binding succeeds if it shouldn't. One possible workaround (from an answer to that question)
seems to be to replace
if let result = parentVC as? T { ...
with
if let result = parentVC as Any as? T { ...
or to remove the type constraint in the method definition:
func traverseAndFindClass<T>() -> T? {
Update 2: The problem has been fixed with Xcode 7, thetraverseAndFindClass()
method now works correctly.
Swift 4 update:
extension UIViewController {
func traverseAndFindClass<T : UIViewController>() -> T? {
var currentVC = self
while let parentVC = currentVC.parent {
if let result = parentVC as? T {
return result
}
currentVC = parentVC
}
return nil
}
}
Optional binding succeeds if it shouldn't
If you try to conditionally cast an object of type UINavigationController
to a UICollectionViewController
in a Playground:
var nc = UINavigationController()
if let vc = nc as? UICollectionViewController {
println("Yes")
} else {
println("No")
}
You get this error:
Playground execution failed: :33:16: error: 'UICollectionViewController' is not a subtype of 'UINavigationController'
if let vc = nc as? UICollectionViewController {
but if instead you do:
var nc = UINavigationController()
if let vc = (nc as Any) as? UICollectionViewController {
println("Yes")
} else {
println("No")
}
it prints "No".
So I suggest trying:
extension UIViewController {
func traverseAndFindClass<T where T : UIViewController>(T.Type) -> T? {
var currentVC = self
while let parentVC = currentVC.parentViewController {
println("comparing \(parentVC) to \(T.description())")
if let result = (parentVC as Any) as? T { // (XXX)
return result
}
currentVC = parentVC
}
return nil
}
}
How to iterate all the UIViewControllers on the app
Access them codewise like this:
NSArray * controllerArray = [[self navigationController] viewControllers];
for (UIViewController *controller in controllerArray){
//Code here.. e.g. print their titles to see the array setup;
NSLog(@"%@",controller.title);
}
Given a view, how do I get its viewController?
Yes, the superview
is the view that contains your view. Your view shouldn't know which exactly is its view controller, because that would break MVC principles.
The controller, on the other hand, knows which view it's responsible for (self.view = myView
), and usually, this view delegates methods/events for handling to the controller.
Typically, instead of a pointer to your view, you should have a pointer to your controller, which in turn can either execute some controlling logic, or pass something to its view.
How to find the calling viewController in my stack?
Every view controller has either a parent
or a presentingViewController
(or both), so by asking for these, you can figure out "where you are".
That will usually be sufficient to tell you the situation, especially if you use class types judiciously (for example, you can make your navigation controllers different UINavigationController subclasses just for the sake of knowing where you are later).
If you want a complete conspectus of the view controller chain to where you are, you can trace your way up through the chain recursively, like this:
func trace(_ vc: UIViewController) {
print(vc)
if let parent = vc.parent {
print("parent:")
trace(parent)
return
}
if let presenter = vc.presentingViewController {
print("presenter:")
trace(presenter)
return
}
print("done")
}
That example prints rather than accumulating a list of view controllers along with the nature of the connection between each of them (which is what you really need), but by calling it from the "last" view controller in the chain, you can get a mental picture of what the chain must look like at the point where you want to say "go no deeper".
Here's a more complete example that shows how to accumulate a backward trace into an array:
class MyVC: UIViewController {
func makeTrace() {
super.viewDidAppear(animated)
let result = self.trace([(.start, self)])
print(result)
}
enum Link: String {
case parent
case presenter
case start
}
typealias Chain = [(Link, UIViewController)]
func trace(_ chain: Chain) -> Chain {
if let parent = chain.last!.1.parent {
return trace(chain + [(.parent, parent)])
}
if let presenter = chain.last!.1.presentingViewController {
return trace(chain + [(.presenter, presenter)])
}
return chain
}
}
So result
will tell you enough to know "where you are".
Get all list of UIViewControllers in iOS Swift
You can do it with below code.
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let viewControllers = appDelegate.window?.rootViewController?.presentedViewController
{
// Array of all viewcontroller even after presented
}
else if let viewControllers = appDelegate.window?.rootViewController?.childViewControllers
{
// Array of all viewcontroller after push
}
Swift 4.2 ( XCode 10 )
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if (appDelegate.window?.rootViewController?.presentedViewController) != nil
{
// Array of all viewcontroller even after presented
}
else if (appDelegate.window?.rootViewController?.children) != nil
{
// Array of all viewcontroller after push
}
Accessing a view controller created through Storyboard using the App Delegate
You'll have to traverse the view hierarchy from the app delegate. Assuming the AppDelegate
holds a reference to the UITabBarController
, you could use the viewControllers
property or selectedViewController
property to get to your view controller.
How to find topmost view controller on iOS
iOS 4 introduced the rootViewController property on UIWindow:
[UIApplication sharedApplication].keyWindow.rootViewController;
You'll need to set it yourself after you create the view controller though.
Related Topics
Apple Watch Table - First 4 Rows Not Appearing
How to Change the Font Size of a Uilabel in Swift
What am I Doing Wrong with Allowsfractionalunits on Nsdatecomponentsformatter
Xcode 6 & Swift: Black Bars Appear Above and Below the Viewcontroller on iOS 7 iPhone 5 Device
How to Create a CSV File from Core Data (Swift)
Swift Sending Data from Child View to Parent View Controller
Transition Delegate for Uitabbarcontroller Animation
Removing a View Controller from Memory When Instantiating a New View Controller
Swift, Google Map Fit Bound for All the Markers
How Is This Slide-Up Menu from the iPhone Messages App Implemented
Swift Scenekit - Centering Scntext - the Getboundingboxmin:Max Issue
How to Convert Data Dictionary into an Array Swift
3D Touch Quick Actions Not Working at All
Libmobilegestalt Mobilegestalt.C:890: Mgisdeviceoneoftype Is Not Supported on This Platform
How to Read Heart Rate from iOS Healthkit App Using Swift