昨天我們完成了 BluetoothService 的藍牙核心邏輯,讓 App 可以進行掃描、連線並接收藍牙數據。
今天要更進一步,把這些功能與畫面串接起來,讓使用者能清楚地看到附近的藍牙裝置,並能點擊連接設備。
在前一篇中,我們在 BluetoothService 定義了一個協議:
protocol BluetoothServiceDelegate: AnyObject {
    func getBLEPeripherals(peripherals: [CBPeripheral])   // 回傳掃描到的裝置
    func getBLEPeripheralsValue(value: String)            // 回傳接收到的數據
}
這個 delegate 是 藍牙核心與畫面之間的橋樑。
當 Service 掃描到新裝置或接收到資料時,會透過這個協議回傳給 UI。
而今天的 MainViewController 就會負責實作這個協議,讓畫面能即時反映藍牙狀態。
先來看看控制器的架構:
class MainViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var lbLightNumber: UILabel!
    
    private var peripherals: [CBPeripheral] = []
    private var connectedPeripheral: CBPeripheral?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUI()
        BluetoothService.shared.delegate = self   // 關鍵:設定代理
    }
}
這裡主要做了兩件事:
peripherals 陣列,存放掃描到的藍牙裝置。viewDidLoad 裡設定代理 (delegate),確保藍牙事件能即時傳回 UI。接著,使用 UITableView 來顯示掃描到的藍牙清單:
extension MainViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return peripherals.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "BluetoothTableViewCell", for: indexPath) as? BluetoothTableViewCell else {
            return UITableViewCell()
        }
        
        let peripheral = peripherals[indexPath.row]
        cell.lbName.text = peripheral.name ?? "未知設備"
        return cell
    }
}
這樣就能在畫面上即時顯示掃描到的藍牙裝置名稱。
當使用者點擊其中一個項目時:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    let selectedPeripheral = peripherals[indexPath.row]
    BluetoothService.shared.connectPeripheral(peripheral: selectedPeripheral)
}
App 會呼叫 BluetoothService 去嘗試連線該設備。
接著實作 BluetoothServiceDelegate,處理掃描與資料更新事件:
extension MainViewController: BluetoothServiceDelegate {
    
    func getBLEPeripherals(peripherals: [CBPeripheral]) {
        self.peripherals = peripherals
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
    
    func getBLEPeripheralsValue(value: String) {
        DispatchQueue.main.async {
            self.lbLightNumber.text = "光線強度:\(value)"
        }
    }
}
這裡有兩個關鍵重點:
DispatchQueue.main.async)。getBLEPeripheralsValue 即時顯示裝置傳來的數據,例如感測器的光線值或其他量測資訊。今天我們完成了整個「藍牙掃描 → UI 顯示 → 連線 → 數據更新」的流程:
到這一步,我們的藍牙 App 已經具備完整的互動性與基本資料傳輸能力 🎉
明天(Day29),我們將深入探討 藍牙數據傳輸與解析機制,包含: