使用泛型在Swift中查找指定类的父视图

14

我想我在泛型上遇到了困难。 我想创建一个简单的UIView扩展,以递归方式查找函数参数中传递的类的superview。 我希望该函数返回可选项,显然包含nil或作为提供的类的实例可见的对象。

extension UIView {
    func superviewOfClass<T>(ofClass: T.Type) -> T? {
        var currentView: UIView? = self

        while currentView != nil {
            if currentView is T {
                break
            } else {
                currentView = currentView?.superview
            }
        }

        return currentView as? T
    }
}

非常感谢您的帮助。


3
问题具体在哪里?你的代码看起来应该能够工作。你提到你正在尝试以递归的方式解决问题,但是你的解决方案是迭代式的。你是想将那段代码转换为递归版本吗?你能否把你尝试过的内容包含进来? - Mick MacCallum
抱歉,我的错误。问题中的递归部分完全不相关,为了代码简单起见我将其改为了迭代方式。关键部分是通用的。而且...你是对的,这段代码有效,我不知道之前为什么失败了。我稍微整理了一下,以便在此处发布,并且似乎通过清理我修复了它。 - Chris Rutkowski
误读superview为superclass,哈哈。 - Alexander
3个回答

26

Swift 3/4

这是一种更为简洁的方式:

extension UIView {

    func superview<T>(of type: T.Type) -> T? {
        return superview as? T ?? superview.compactMap { $0.superview(of: type) }
    }

    func subview<T>(of type: T.Type) -> T? {
        return subviews.compactMap { $0 as? T ?? $0.subview(of: type) }.first
    }

}

使用方法:

let tableView = someView.superview(of: UITableView.self)
let tableView = someView.subview(of: UITableView.self)

6
不需要使用 flatMap,因为在 flatMap 中调用 superview() 会导致递归。我们可以简单地编写 return superview as? T ?? superview?.superview(of: type)。希望编译器能够优化这个尾递归。 - albert
flatMap用于可选类型,它会解包值,但不会迭代。https://developer.apple.com/documentation/swift/optional/1540500-flatmap - efremidze
我使用这个函数是可以工作的,但需要进行一些更改:将 superview as? T ?? superview.compactMap { $0.superview(of: type) } 改为 self.superview as? T ?? superview?.superView(of: type)。因为“UIView?”类型的值没有成员“compactMap”。 - Abishek Thangaraj

1
在Swift 4.1中,不需要传递您想要的类的类型...
extension UIView {    
    func firstSubview<T: UIView>() -> T? {
        return subviews.compactMap { $0 as? T ?? $0.firstSubview() as? T }.first
    }
}

1
这是否意味着您必须编写 let scrollview: NSScrollView? = view.firstSubview(),而不是 let scrollview = view.firstSubview(ofType: NSScrollView.self)。我认为第二种更易读。当然,您仍然必须在某个地方放置类型? - Giles
那就是泛型的美妙之处...类型是被推断出来的,因此没有必要明确声明它。在你的例子中,你将结果赋给了一个类型为NSScrollView?的变量,因此T就是NSScrollView - Ashley Mills
问题询问关于父视图,而这个答案是关于子子视图的。 - Cristik

0

我正在使用这个。

// Lookup view ancestry for any `UIScrollView`.
if let scrollView = view.searchViewAnchestors(for: UIScrollView.self) {
    print("Found scrollView: \(scrollView)")
}

扩展实际上是一个单一的语句。

extension UIView {
    
    func searchViewAnchestors<ViewType: UIView>(for viewType: ViewType.Type) -> ViewType? {
        if let matchingView = self.superview as? ViewType {
            return matchingView
        } else {
            return superview?.searchViewAnchestors(for: viewType)
        }
    }
}

通过下面这种替代实现,你可以让调用方决定要查找的类型,但我发现这有点不寻常。
extension UIView {
    
    func searchInViewAnchestors<ViewType: UIView>() -> ViewType? {
        if let matchingView = self.superview as? ViewType {
            return matchingView
        } else {
            return superview?.searchInViewAnchestors()
        }
    }
}

你可以这样调用它。
if let scrollView: UIScrollView = view.searchInViewAnchestors() {
    print("Found scrollView: \(scrollView)")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接