Traverse View Controller Hierarchy in Swift

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, the
traverseAndFindClass() 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



Leave a reply



Submit