5

我想遍历 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,但我不知道如何以上帝的名义将 Generic 设为可选类型。也许使用某种 where 子句?

4

3 回答 3

6

您的方法应该返回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? {
    // ....
}

更新:上述方法没有按预期工作(至少在调试配置中没有),我已将问题作为一个单独的问题发布:可选绑定成功,如果它不应该。一种可能的解决方法(来自该问题的答案)似乎是替换

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

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

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

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

更新 2: Xcode 7 已修复该问题,该 traverseAndFindClass()方法现在可以正常工作。


斯威夫特 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
    }
}
于 2014-09-13T23:30:53.547 回答
4

一种线性解决方案(使用递归),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? {
  // ....
}
于 2018-03-15T09:21:00.257 回答
1

我们可以使用递归来代替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
    }
}
于 2016-11-15T11:06:19.583 回答