UIResponder chain, Swift style

I was building an MVVM-C PoC and came up with this little thing to combine my controller delegate, responder chain, and UIApplicationDelegate/coordinator by testing for protocol conformance rather than function availability.

Has anyone else found success with this strategy, or if you haven't seen it before, what do you think?

//  UIResponder+Target.swift
extension UIResponder {
    func target<Type>(conformingTo type: Type.Type) -> Type? {
        if let target = self as? Type {
            return target
        } else {
            return next?.target(conformingTo: type)
        }
    }
    
    func target<Type>(default value: Type?) -> Type? {
        if let value = value {
            return value
        } else {
            return next?.target(conformingTo: Type.self)
        }
    }
}

Usage

class SomeViewController: UITableViewController {
        
   weak var delegate: SomeViewControllerDelegate?
    
   private var target: SomeViewControllerDelegate? { return target(default: delegate) }
    
   override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      target?.someViewController(_ controller:self, didSelectItemAt: indexPath)
    }
}

Yes, I do something similar. But since mine is for AppKit, it's somewhat more complicated. To mimic NSApplication's target search strategy, I have to look at several objects not in the responder chain, and I have to search multiple possible responder chains in succession.

extension NSResponder {
    public func nearestResponder<Target>(ofType type: Target.Type) -> Target? {
        var app: NSApplication? = NSApplication.shared
        var keyWindow = app!.keyWindow
        var keyWindowController = keyWindow?.windowController
        var mainWindow = app!.mainWindow
        var mainWindowController = mainWindow?.windowController
        if mainWindow === keyWindow { mainWindow = nil }
        var next: NSResponder? = self

        while let candidate = next {
            if let match = candidate as? Target {
                return match
            }

            if candidate === keyWindow {
                if let match = keyWindow?.delegate as? Target { return match }
                keyWindow = nil
            }

            if candidate === keyWindowController {
                if let match = keyWindowController?.document as? Target { return match }
                keyWindowController = nil
            }

            if candidate === mainWindow {
                if let match = mainWindow?.delegate as? Target { return match }
                mainWindow = nil
            }

            if candidate === mainWindowController {
                if let match = mainWindowController?.document as? Target { return match }
                mainWindowController = nil
            }

            if candidate === app {
                if let match = app?.delegate as? Target { return match }
                if let match = NSDocumentController.shared as? Target { return match }
                app = nil
            }

            next = candidate.nextResponder ?? keyWindow.firstResponder ?? mainWindow.firstResponder ?? app
        }

        return nil
    }
}
1 Like

Seems like Apple fixed the nextResponder in iOS to go all the way to the app delegate. Do you have an equivalent of UIResponder.target(default:) to combine the delegate with the responder chain?

I haven't felt the need for that.