You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 3中如何实现通用方法检测视图控制器是否来自指定类

适配导航控制器的通用来源检测方案

针对你遇到的问题——新增控制器时重复硬编码判断、编译失败且要适配导航控制器场景,我给你两个递进的解决方案,都是基于Swift 3和Xcode 8.3.3的:

方案一:通用的呈现链查找扩展(解决硬编码+导航适配)

首先,我们给UIViewController写一个扩展,让它能自动遍历呈现链,处理导航控制器的嵌套情况,找到你要的来源控制器:

extension UIViewController {
    /// 在呈现链中查找指定类型的控制器,自动适配导航控制器
    func findPresentingController<T: UIViewController>(ofType type: T.Type) -> T? {
        var currentPresenter = self.presentingViewController
        
        while let presenter = currentPresenter {
            // 先检查当前控制器是否是目标类型
            if let target = presenter as? T {
                return target
            }
            
            // 如果是导航控制器,先检查它的顶层控制器是否匹配
            if let navController = presenter as? UINavigationController {
                if let topTarget = navController.topViewController as? T {
                    return topTarget
                }
                // 不匹配的话,继续往上找导航控制器的呈现者
                currentPresenter = navController.presentingViewController
            } else {
                // 非导航控制器,直接往上遍历
                currentPresenter = presenter.presentingViewController
            }
        }
        
        return nil
    }
}

然后在你的TestViewController里,就可以用这个方法替代硬编码的isFromA/isFromB:

class TestViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        configureContent()
    }
    
    private func configureContent() {
        if findPresentingController(ofType: AViewController.self) != nil {
            loadContentForA()
        } else if findPresentingController(ofType: BViewController.self) != nil {
            loadContentForB()
        } else if findPresentingController(ofType: CViewController.self) != nil {
            loadContentForC()
        } else {
            loadDefaultContent()
        }
    }
    
    // 以下是各个场景的内容加载方法
    private func loadContentForA() {
        // 展示来自A控制器的数据
    }
    
    private func loadContentForB() {
        // 展示来自B控制器的数据
    }
    
    private func loadContentForC() {
        // 展示来自C控制器的数据
    }
    
    private func loadDefaultContent() {
        // 默认展示内容
    }
}

这个方案的好处是:

  • 新增控制器时,只需要在configureContent里加一行else if判断,不需要修改核心的查找逻辑
  • 自动适配导航控制器场景——不管来源控制器是直接呈现Test,还是被包裹在导航控制器里呈现,都能正确找到

方案二:基于协议的解耦(更优的扩展性)

如果后续还要新增更多控制器,方案一的else if链还是会越来越长。我们可以用协议来解耦,让来源控制器自己负责提供数据,Test控制器只需要拿数据展示:

第一步:定义数据提供协议

// 假设你有一个TestData模型用来承载展示内容
struct TestData {
    let title: String
    let content: String
    // 其他字段根据你的需求定义
}

protocol TestContentProvider {
    func provideTestContent() -> TestData
}

第二步:让来源控制器遵守协议

class AViewController: UIViewController, TestContentProvider {
    func provideTestContent() -> TestData {
        return TestData(title: "来自A的内容", content: "这是A控制器对应的展示数据")
    }
}

class BViewController: UIViewController, TestContentProvider {
    func provideTestContent() -> TestData {
        return TestData(title: "来自B的内容", content: "这是B控制器对应的展示数据")
    }
}

// 新增C控制器时,只需要让它遵守协议并实现方法即可
class CViewController: UIViewController, TestContentProvider {
    func provideTestContent() -> TestData {
        return TestData(title: "来自C的内容", content: "这是C控制器对应的展示数据")
    }
}

第三步:修改Test控制器的内容配置逻辑

我们可以扩展之前的查找方法,让它支持协议类型的查找:

extension UIViewController {
    func findContentProvider() -> TestContentProvider? {
        var currentPresenter = self.presentingViewController
        
        while let presenter = currentPresenter {
            if let provider = presenter as? TestContentProvider {
                return provider
            }
            
            if let navController = presenter as? UINavigationController {
                if let topProvider = navController.topViewController as? TestContentProvider {
                    return topProvider
                }
                currentPresenter = navController.presentingViewController
            } else {
                currentPresenter = presenter.presentingViewController
            }
        }
        
        return nil
    }
}

然后在Test控制器里:

class TestViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        configureContent()
    }
    
    private func configureContent() {
        guard let contentProvider = findContentProvider() else {
            loadDefaultContent()
            return
        }
        
        let content = contentProvider.provideTestContent()
        // 用content配置你的UI组件,比如label.text = content.title
    }
    
    private func loadDefaultContent() {
        // 默认展示内容
    }
}

这个方案的优势是:

  • 新增控制器时,Test控制器不需要做任何修改,只需要新控制器遵守协议即可
  • 数据提供逻辑完全封装在来源控制器里,符合单一职责原则
  • 彻底告别硬编码的类型判断,代码更简洁易维护

关于你之前编译失败的可能原因

你之前写通用方法编译失败,大概率是以下情况之一:

  1. 没有正确处理导航控制器的可选值(比如直接强制解包navigationController!.topViewController)
  2. 通用方法的类型约束写错(比如没有指定T: UIViewController)
  3. 递归遍历呈现链时没有正确终止条件,导致编译器报错

上面的两个方案都已经规避了这些问题,你可以直接复用。

内容的提问来源于stack exchange,提问作者Tim Fuqua

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:40:43