不使用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 = ... } } }
额外注意事项:
- 检查IBOutlet是否正确连接:有时候拖拽连接失败会导致
TopBarView为nil,访问就会崩溃,你可以在Storyboard中检查连线是否正常。 - 确保代理已设置:你之前的代码里没有给
controller.delegate = self,如果没设置,代理方法根本不会执行,这也是常见的坑。
内容的提问来源于stack exchange,提问作者NilsBerni
相关产品推荐
相关产品推荐

