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

健身APP开发:如何在ViewController间传递连续心率数据流?

解决连续数据流在ViewController间传递的问题

首先得说,你想解耦蓝牙数据获取和显示模块的思路非常正确——把蓝牙Manager和UI逻辑混在同一个VC里确实会让代码越来越难维护。针对连续数据流的传递,我们有几个比prepareForSegue或单次Notification更合适的方案,同时也能解决你提到的Container View和UIPageViewController之间的数据传递问题:

方案1:自定义Delegate协议(适配连续数据流)

Delegate模式可不是只能用来传递单次数据,我们可以把它改成“持续回调”的形式,完美适配心率这种连续数据流:

步骤1:抽离蓝牙逻辑并定义协议

先把蓝牙相关的代码从VC里单独拎出来,做成一个独立的Manager类(这步很关键,比放在VC里解耦度高太多):

protocol HeartRateDataDelegate: AnyObject {
    func didReceiveHeartRateUpdate(_ heartRate: Int)
}

class BluetoothHeartRateManager {
    weak var delegate: HeartRateDataDelegate?
    
    func startScanning() {
        // 实际场景中,这里替换成蓝牙传感器的连续回调逻辑
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            let simulatedHeartRate = Int.random(in: 60...180)
            self?.delegate?.didReceiveHeartRateUpdate(simulatedHeartRate)
        }
    }
}

步骤2:在显示端VC实现代理

不管是Container View里的子VC,还是UIPageViewController中的页面VC,都可以实现这个协议来接收连续数据:

class HeartRateDisplayVC: UIViewController, HeartRateDataDelegate {
    @IBOutlet weak var heartRateLabel: UILabel!
    let heartRateManager = BluetoothHeartRateManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        heartRateManager.delegate = self
        heartRateManager.startScanning()
    }
    
    func didReceiveHeartRateUpdate(_ heartRate: Int) {
        // 切到主线程更新UI(蓝牙数据通常在后台线程返回)
        DispatchQueue.main.async {
            self.heartRateLabel.text = "\(heartRate) BPM"
        }
    }
}

针对Container View的传递

如果蓝牙Manager在Container的父VC里,你可以在父VC中拿到Container的子VC实例,直接设置代理或传递Manager:

class ParentVC: UIViewController {
    let heartRateManager = BluetoothHeartRateManager()
    private var childDisplayVC: HeartRateDisplayVC?
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ContainerSegue" {
            childDisplayVC = segue.destination as? HeartRateDisplayVC
            childDisplayVC?.heartRateManager = heartRateManager
            childDisplayVC?.heartRateManager.delegate = childDisplayVC
        }
    }
}

针对UIPageViewController的传递

UIPageViewController需要先获取当前显示的页面VC,再传递数据:

class MyPageViewController: UIPageViewController {
    let heartRateManager = BluetoothHeartRateManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        heartRateManager.delegate = self
        heartRateManager.startScanning()
    }
}

extension MyPageViewController: HeartRateDataDelegate {
    func didReceiveHeartRateUpdate(_ heartRate: Int) {
        // 获取当前活跃的页面VC
        guard let currentVC = viewControllers?.first as? HeartRateDisplayVC else { return }
        currentVC.updateHeartRate(heartRate)
    }
}

// 在页面VC中添加更新方法
extension HeartRateDisplayVC {
    func updateHeartRate(_ heartRate: Int) {
        DispatchQueue.main.async {
            self.heartRateLabel.text = "\(heartRate) BPM"
        }
    }
}

方案2:使用Combine框架(iOS 13+,推荐)

Combine天生就是为处理连续数据流设计的,用它来传递心率数据非常优雅,几乎不需要额外的耦合:

步骤1:在蓝牙Manager中定义数据流

import Combine

class BluetoothHeartRateManager {
    // 定义一个PassthroughSubject,用来发射心率数据(Never表示不会发射错误)
    let heartRatePublisher = PassthroughSubject<Int, Never>()
    
    func startScanning() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            let simulatedHeartRate = Int.random(in: 60...180)
            self?.heartRatePublisher.send(simulatedHeartRate)
        }
    }
}

步骤2:在显示端订阅数据流

class HeartRateDisplayVC: UIViewController {
    @IBOutlet weak var heartRateLabel: UILabel!
    let heartRateManager = BluetoothHeartRateManager()
    // 用来存储订阅,防止被销毁
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 订阅数据流,自动切换到主线程更新UI
        heartRateManager.heartRatePublisher
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] heartRate in
                self?.heartRateLabel.text = "\(heartRate) BPM"
            })
            .store(in: &cancellables)
        
        heartRateManager.startScanning()
    }
}

这种方式的好处是,不管是Container View还是UIPageViewController里的VC,只要能拿到BluetoothHeartRateManager的实例,就能订阅数据流,不需要繁琐的代理设置,Combine还会帮你自动处理线程切换。

方案3:使用NotificationCenter(适配连续流)

虽然你觉得Notification是针对单次数据,但其实它也能用来发送连续数据流,不过耦合度比前两种方案高,建议只在跨多层VC且不方便用Delegate/Combine的场景下使用:

// 蓝牙Manager中发送通知
class BluetoothHeartRateManager {
    func startScanning() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            let simulatedHeartRate = Int.random(in: 60...180)
            NotificationCenter.default.post(
                name: NSNotification.Name(rawValue: "HeartRateUpdate"),
                object: nil,
                userInfo: ["heartRate": simulatedHeartRate]
            )
        }
    }
}

// 显示端VC监听通知
class HeartRateDisplayVC: UIViewController {
    @IBOutlet weak var heartRateLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleHeartRateUpdate(_:)),
            name: NSNotification.Name(rawValue: "HeartRateUpdate"),
            object: nil
        )
    }
    
    @objc func handleHeartRateUpdate(_ notification: Notification) {
        guard let heartRate = notification.userInfo?["heartRate"] as? Int else { return }
        heartRateLabel.text = "\(heartRate) BPM"
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

额外建议:尽量抽离独立Manager

不管用哪种方案,都强烈建议把蓝牙逻辑做成独立的BluetoothHeartRateManager(可以用单例或者依赖注入传递),这样所有需要心率数据的VC都直接和这个Manager交互,而不是VC之间互相传递数据,能进一步降低耦合度,代码也更易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:27:50