14

I guess I'm struggling with generics. I want to create simple UIView extension to find recursively a superview of class passed in the function param. I want the function to return optional containing obviously either nil, or object visible as instance of provided class.

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

Any help much appreciated.

Chris Rutkowski
  • 1,774
  • 1
  • 26
  • 36
  • 3
    What exactly is the problem? Your code looks like it should work. You mentioned you're trying to do this recursively, but your solution is iterative. Are you trying to convert that code into a recursive version? Can you include your attempts to do so? – Mick MacCallum Jun 08 '16 at 14:51
  • sorry, my mistake, the recursive part of the question is totally irrelevant, I made it iterative for code simplicity, the key part is the generic. And actually... you're right, this code works, I've no idea why it failed before. I cleaned it a bit for the purpose of publishing it here and looks like by cleaning I fixed it). – Chris Rutkowski Jun 08 '16 at 15:04
  • misread superview as superclass lol. – Alexander Jun 08 '16 at 15:12

3 Answers3

25

Swift 3/4

This is a more concise way:

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
    }

}

Usage:

let tableView = someView.superview(of: UITableView.self)
let tableView = someView.subview(of: UITableView.self)
efremidze
  • 2,640
  • 1
  • 25
  • 35
  • 6
    You don't need `flatMap`, because calling `superview()` inside `flatMap` leads to recursions. Let's simply write `return superview as? T ?? superview?.superview(of: type)`. Hope that the compiler optimizes this tail recursion. – albert Mar 28 '19 at 06:13
  • flatMap for an optional unwraps the value, doesn't iterate. https://developer.apple.com/documentation/swift/optional/1540500-flatmap – efremidze Mar 28 '19 at 15:57
  • I using this function its working but need to make some changes `superview as? T ?? superview.compactMap { $0.superview(of: type) }` to `self.superview as? T ?? superview?.superView(of: type)`. Because **Value of type 'UIView?' has no member 'compactMap'**. – Abishek Thangaraj Feb 01 '22 at 12:29
1

No need to pass in the type of the class you want (at least in Swift 4.1)…

extension UIView {    
    func firstSubview<T: UIView>() -> T? {
        return subviews.compactMap { $0 as? T ?? $0.firstSubview() as? T }.first
    }
}
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • 1
    Does this then mean you have to write `let scrollview: NSScrollView? = view.firstSubview()`, rather than `let scrollview = view.firstSubview(ofType: NSScrollView.self)`. I think the second is more readable. Surely you still have to put the type somewhere? – Giles May 20 '19 at 15:12
  • That's the beauty of generics… the type is inferred, so there's on need to explicitly state it. In your example, you're assigning the result to a variable of type `NSScrollView?`, so `T` is therefore `NSScrollView` – Ashley Mills May 20 '19 at 20:13
  • Question asks about superview, while this answer is about subsubviews – Cristik Oct 01 '20 at 20:43
0

I'm using this.

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

Extension is really a single statement.

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

With this alternative implementation below, you can actually let the call site determine what type to look for, but I found it somewhat unconventional.

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

You can call it like this.

if let scrollView: UIScrollView = view.searchInViewAnchestors() {
    print("Found scrollView: \(scrollView)")
}
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172