在Swift中遍历视图控制器层次结构

时间:2014-09-13 22:01:34

标签: ios generics swift optional-variables

我想在Swift中遍历视图控制器层次结构并找到一个特定的类。这是代码:

extension UIViewController{

    func traverseAndFindClass<T : UIViewController>() -> UIViewController?{

        var parentController = self.parentViewController as? T?
                                    ^
                                    |
                                    |
        // Error: Could not find a user-defined conversion from type 'UIViewController?' to type 'UIViewController'
        while(parentController != nil){

            parentController = parentController!.parentViewController

        }

        return parentController

    }

}

现在,我知道parentViewController属性返回一个可选的UIViewController,但我不知道如何以God的名义将Generic作为可选类型。也许使用某种where子句?

3 个答案:

答案 0 :(得分:2)

您的方法应该返回T?而不是UIViewController?,以便通用类型 可以从上下文中推断出来。检查想要的课程也必须 在循环内完成,不仅在循环之前完成一次。

这应该有效:

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
    }
}

使用示例:

if let vc = self.traverseAndFindClass() as SpecialViewController? {
    // ....
}

更新:上述方法无法正常工作(至少在Debug中没有 配置),我已经发布了问题 作为一个单独的问题:Optional binding succeeds if it shouldn't。一种可能的解决方法(从该问题的答案) 似乎是要取代

if let result = parentVC as? T { ...

if let result = parentVC as Any as? T { ...

或删除方法定义中的类型约束:

func traverseAndFindClass<T>() -> T? {

更新2: Xcode 7解决了这个问题 traverseAndFindClass()方法现在可以正常使用。


Swift 4更新:

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
    }
}

答案 1 :(得分:1)

一个线性解决方案(使用递归), Swift 4.1 +

extension UIViewController {
  func findParentController<T: UIViewController>() -> T? {
    return self is T ? self as? T : self.parent?.findParentController() as T?
  }
}

使用示例:

if let vc = self.findParentController() as SpecialViewController? {
  // ....
}

答案 2 :(得分:0)

我们可以使用递归代替while循环(请注意,以下代码均未经过全面测试):

// Swift 2.3

public extension UIViewController {

    public var topViewController: UIViewController {
        let o = topPresentedViewController
        return o.childViewControllers.last?.topViewController ?? o
    }

    public var topPresentedViewController: UIViewController {
        return presentedViewController?.topPresentedViewController ?? self
    }
}

关于遍历视图控制器层次结构的更一般问题,一种可能的方法是拥有两个专用序列,以便我们可以:

for ancestor in vc.ancestors {
    //...
}

或:

for descendant in vc.descendants {
    //...
}

其中:

public extension UIViewController {

    public var ancestors: UIViewControllerAncestors {
        return UIViewControllerAncestors(of: self)
    }

    public var descendants: UIViewControllerDescendants {
        return UIViewControllerDescendants(of: self)
    }
}

实施祖先序列:

public struct UIViewControllerAncestors: GeneratorType, SequenceType {
    private weak var vc: UIViewController?

    public mutating func next() -> UIViewController? {
        guard let vc = vc?.parentViewController ?? vc?.presentingViewController else {
            return nil
        }
        self.vc = vc
        return vc
    }

    public init(of vc: UIViewController) {
        self.vc = vc
    }
}

实施后代序列:

public struct UIViewControllerDescendants: GeneratorType, SequenceType {
    private weak var root: UIViewController?
    private var index = -1
    private var nextDescendant: (() -> UIViewController?)? // TODO: `Descendants?` when Swift allows recursive type definitions

    public mutating func next() -> UIViewController? {
        if let vc = nextDescendant?() {
            return vc
        }
        guard let root = root else {
            return nil
        }
        while index < root.childViewControllers.endIndex - 1 {
            index += 1
            let vc = root.childViewControllers[index]
            var descendants = vc.descendants
            nextDescendant = { return descendants.next() }
            return vc
        }
        guard let vc = root.presentedViewController where root === vc.presentingViewController else {
            return nil
        }
        self.root = nil
        var descendants = vc.descendants
        nextDescendant = { return descendants.next() }
        return vc
    }

    public init(of vc: UIViewController) {
        root = vc
    }
}