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

不使用Segue实现ViewController间数据传递及代码崩溃排查

嘿,我来帮你搞定这两个iOS开发的问题!

问题1:如何不使用Segue在两个ViewController之间传递数据?

这里有几种常用且靠谱的方案,你可以根据实际场景选择:

1. 直接给目标VC的属性赋值

这是最简单直接的方式,手动实例化目标ViewController后,直接给它的公开属性赋值即可:

// 第一个ViewController中
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let targetVC = storyboard.instantiateViewController(withIdentifier: "backGroundController") as! BackGroundWithContainer
// 给目标VC的公开属性赋值
targetVC.passedFloat = floatItryToPass
// 跳转目标VC
navigationController?.pushViewController(targetVC, animated: true)

// 第二个ViewController中定义公开属性
class BackGroundWithContainer: UIViewController {
    var passedFloat: CGFloat = 0.0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 在这里可以安全使用传递过来的数据
        print("收到的浮点数:", passedFloat)
    }
}

2. 代理模式(Delegate)

如果需要从目标VC反向传值回原VC,或者需要回调通知,代理模式是经典选择:

// 1. 先定义代理协议
protocol BackGroundControllerDelegate: AnyObject {
    func myFunction(float: CGFloat)
}

// 2. 第二个ViewController中声明代理属性(用weak避免循环引用)
class BackGroundWithContainer: UIViewController {
    weak var delegate: BackGroundControllerDelegate?
    
    // 比如在某个用户操作触发时调用代理方法
    func userDidTriggerAction() {
        delegate?.myFunction(float: 100.0)
    }
}

// 3. 第一个ViewController遵守代理协议并设置代理
class FirstViewController: UIViewController, BackGroundControllerDelegate {
    func passData() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let targetVC = storyboard.instantiateViewController(withIdentifier: "backGroundController") as! BackGroundWithContainer
        targetVC.delegate = self
        navigationController?.pushViewController(targetVC, animated: true)
    }
    
    // 实现代理方法处理回调数据
    func myFunction(float: CGFloat) {
        print("从目标VC收到的浮点数:", float)
    }
}

3. 闭包(Closure)

闭包是代理模式的轻量替代,适合简单的回调场景,代码更简洁:

// 第二个ViewController中定义闭包属性
class BackGroundWithContainer: UIViewController {
    var onFloatPassed: ((CGFloat) -> Void)?
    
    func someAction() {
        // 触发闭包传递数据
        onFloatPassed?(100.0)
    }
}

// 第一个ViewController中设置闭包(用[weak self]避免循环引用)
class FirstViewController: UIViewController {
    func passData() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let targetVC = storyboard.instantiateViewController(withIdentifier: "backGroundController") as! BackGroundWithContainer
        targetVC.onFloatPassed = { [weak self] float in
            guard let self = self else { return }
            print("通过闭包收到的浮点数:", float)
            // 在这里处理收到的数据
        }
        navigationController?.pushViewController(targetVC, animated: true)
    }
}

4. 通知中心(NotificationCenter)

如果两个VC没有直接跳转关系,或者需要跨组件传值,可以用通知中心,但尽量少用避免代码耦合:

// 第一个ViewController中发送通知
NotificationCenter.default.post(
    name: NSNotification.Name("PassFloatNotification"),
    object: floatItryToPass
)

// 第二个ViewController中监听通知
class BackGroundWithContainer: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 添加通知监听
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(receiveFloat(_:)),
            name: NSNotification.Name("PassFloatNotification"),
            object: nil
        )
    }
    
    @objc func receiveFloat(_ notification: Notification) {
        if let floatValue = notification.object as? CGFloat {
            print("通过通知收到的浮点数:", floatValue)
        }
    }
    
    deinit {
        // 记得移除监听,防止内存泄漏
        NotificationCenter.default.removeObserver(self)
    }
}

5. 单例模式

如果是全局共享的数据,可以用单例存储,但要谨慎使用避免数据混乱:

// 定义单例类
class DataManager {
    static let shared = DataManager()
    var sharedFloat: CGFloat = 0.0
}

// 第一个ViewController中设置数据
DataManager.shared.sharedFloat = floatItryToPass

// 第二个ViewController中获取数据
let receivedFloat = DataManager.shared.sharedFloat
问题2:解决代码中的野指针异常

先帮你分析问题根源:
你在第一个VC中实例化BackGroundWithContainer后,立刻调用了delegate?.myFunction(float: floatItryToPass),但这时候第二个VC的视图还没有加载完成(viewDidLoad还没执行)!

  • TopBarView是IBOutlet,它只有在VC的view加载完成后才会和Storyboard中的控件绑定,此时它还是nil,直接访问它的属性就会触发野指针崩溃。
  • whiteView是你手动创建的UIView(),但你还没把它添加到VC的视图层级中,也没设置它的frame,直接操作center.y逻辑上也有问题。

给你几个可行的解决方案:

方案1:延迟调用代理方法,等目标VC视图加载完成

不要在实例化后立刻调用代理方法,而是等目标VC的视图加载完成后再触发:

修改第一个VC的代码:

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "backGroundController") as! BackGroundWithContainer
controller.delegate = self // 别忘了设置代理!你之前的代码里可能漏掉了这一步
navigationController?.pushViewController(controller, animated: true)
// 不要在这里调用delegate方法,移到目标VC的viewDidLoad中

然后在第二个VC的viewDidLoad中处理视图并调用代理(如果需要传数据的话):

override func viewDidLoad() {
    super.viewDidLoad()
    // 先初始化whiteView并添加到视图层级
    whiteView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
    view.addSubview(whiteView)
    
    // 现在可以安全操作视图属性了
    whiteView.center.y = view.frame.height
    
    // 检查TopBarView是否连接成功,再操作
    if let topBar = TopBarView {
        topBar.center.y = ... // 这里可以安全设置属性
    }
    
    // 如果需要给代理传数据,在这里调用
    delegate?.myFunction(float: someFloatValue)
}

方案2:在代理方法中延迟执行视图操作

如果必须在代理方法中处理视图,可以把操作放到主队列的异步任务中,确保视图已经加载:

func takeScrollUpFloat(float: CGFloat) {
    print("scrollUpFloatTaker: ", float)
    // 延迟到主队列下一次循环执行,确保视图已初始化
    DispatchQueue.main.async { [weak self] in
        guard let self = self else { return }
        // 初始化并添加whiteView
        self.whiteView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 50)
        self.view.addSubview(self.whiteView)
        
        // 安全操作视图属性
        self.whiteView.center.y = self.view.frame.height
        
        // 检查TopBarView不为nil再操作
        if let topBar = self.TopBarView {
            topBar.center.y = ...
        }
    }
}

额外注意事项:

  1. 检查IBOutlet是否正确连接:有时候拖拽连接失败会导致TopBarView为nil,访问就会崩溃,你可以在Storyboard中检查连线是否正常。
  2. 确保代理已设置:你之前的代码里没有给controller.delegate = self,如果没设置,代理方法根本不会执行,这也是常见的坑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:32:28