UI
先把畫面配置好
tableView -> 顯示搜尋到的藍牙設備
cell -> 點擊可以連結設備
Unbutton -> 點擊掃描周圍的藍牙設備
就這樣就好
BleViewController
關聯outlet到BleViewController
配置完像這樣
import UIKitclass BleViewController: UIViewController { var bleArray:[String] = [] @IBOutlet weak var scanButton: UIButton!
@IBOutlet weak var bleTableView: UITableView! override func viewDidLoad() {
super.viewDidLoad()
initUI()
bind()
} func initUI(){ } func bind(){ }
}
extension BleViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return bleArray.count
} func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: “bleCell”, for: indexPath)
cell.textLabel?.text = bleArray[indexPath.row]
return cell
}}
導入BleManager
新增一個檔案(快捷鍵 cmd + n)命名為BleManager
我們就透過這個檔案來操作藍芽功能
import CoreBluetoothclass BLEManager: NSObject {
//單例物件
static let shared = BLEManager()
//中心物件
var central: CBCentralManager?
//裝置列表
var deviceList: NSMutableArray?
//當前連線的裝置
var peripheral: CBPeripheral!
//傳送資料特徵(連線到裝置之後可以把需要用到的特徵儲存起來,方便使用)
var sendCharacteristic:CBCharacteristic?
//可以用下面兩種方式來搜尋設備
// let servicesUUID : [CBUUID] = [CBUUID.init(string: “servicesUUIDs”)]
var deviceName: String?override init() {
super.init()
self.central = CBCentralManager.init(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey:false])
self.deviceList = NSMutableArray()
}//掃描裝置的方法 //可以搜尋特定servicesuuid
func scanForPeripheralsWithServices(_ serviceUUIDS:[CBUUID]?, options:[String: AnyObject]?, deviceName:String){
self.deviceName = deviceName
self.central?.scanForPeripherals(withServices: serviceUUIDS, options: options)
// self.central?.scanForPeripherals(withServices: servicesUUID, options: options)
}//停止掃描
func stopScan(){
self.central?.stopScan()
}//寫資料
func writeToPeripheral(_ data: Data) {
peripheral.writeValue(data, for: sendCharacteristic!, type: .withResponse)
}//連線某個裝置的方法
func requestConnectPeripheral(_ model: CBPeripheral) {
if model.state != .connected {
central?.connect(model, options: nil)
}
}//取得裝置名稱列表
func getDeviceNameArray() -> [String]{
var nameArray:[String] = []
guard let array = deviceList as? [CBPeripheral] else {
print(“deviceList as? [CBPeripheral] EOORO”)
return nameArray
}
for value in array {
nameArray.append(value.name ?? “No device name”)
}
return nameArray
}
}extension BLEManager: CBCentralManagerDelegate{
// 檢查執行這個App的裝置是不是支援BLE
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print(“藍牙開啟”)
case .unauthorized:
print(“沒有藍芽功能”)
case .poweredOff:
print(“藍牙關閉”)
default:
print(“未知狀態”)
}
// 手機藍芽狀態發生變化,可以傳送通知出去。提示使用者
}//中心管理器掃描到了裝置
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 因為iOS目前不提供藍芽裝置的UUID的獲取,所以在這裡通過藍芽名稱判斷是否是本公司的裝置
if deviceList!.contains(peripheral) {
print(“deviceList!.contains(peripheral) == true”)
return
}guard peripheral.name != nil else {
print(“peripheral.name == nil”)
return
}//如果deviceName為空則把所有掃到的設備加入列表中
if let deviceName = deviceName, deviceName != “” {
if peripheral.name!.contains(deviceName) {
deviceList?.add(peripheral)
}
} else {
deviceList?.add(peripheral)
}
}//連線外設成功,開始發現服務
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral){
// 設定代理
peripheral.delegate = self
// 開始發現服務
peripheral.discoverServices(nil)
// 儲存當前連線裝置
self.peripheral = peripheral
// 這裡可以發通知出去告訴裝置連線介面連線成功
}
// MARK: 連線外設失敗
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
// 這裡可以發通知出去告訴裝置連線介面連線失敗
}
// MARK: 連線丟失
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
NotificationCenter.default.post(name: Notification.Name(rawValue: “DidDisConnectPeriphernalNotification”), object: nil, userInfo: [“deviceList”: self.deviceList as AnyObject])
// 這裡可以發通知出去告訴裝置連線介面連線丟失
}
}// 外設的代理
extension BLEManager : CBPeripheralDelegate {
//MARK: — 匹配對應服務UUID
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
if error != nil {
return
}
for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service )
}
}
//MARK: — 服務下的特徵
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){
if (error != nil){
return
}
for characteristic in service.characteristics! {switch characteristic.uuid.description {case “具體特徵值”:
// 訂閱特徵值,訂閱成功後後續所有的值變化都會自動通知
peripheral.setNotifyValue(true, for: characteristic)
case “******”:
// 讀區特徵值,只能讀到一次
peripheral.readValue(for:characteristic)
default:
print(“掃描到其他特徵”)
}
}
}
//MARK: — 特徵的訂閱狀體發生變化
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?){
guard error == nil else {
return
}
}
// MARK: — 獲取外設發來的資料
// 注意,所有的,不管是 read , notify 的特徵的值都是在這裡讀取
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)-> (){
if(error != nil){
return
}
switch characteristic.uuid.uuidString {
case “***************”:
print(“接收到了裝置的溫度特徵的值的變化”)
default:
print(“收到了其他資料特徵資料: \(characteristic.uuid.uuidString)”)
}
}
//MARK: — 檢測中心向外設寫資料是否成功
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if(error != nil){
print(“傳送資料失敗!error資訊:\(String(describing: error))”)
}
}
}
使用BleManager掃描
那怎麼用呢..
期望的效果是,當我按下scan button的時候,開始掃描五秒鐘,掃完更新bleTableView。
回到BleViewController
先完成點擊scan button進行掃描
1. 新增按鈕點擊的function
2. 將scan與新增的func綁定
3. 導入我們的ble單例
4. 透過bleManager使用掃描的功能(deviceName這邊直接帶空值測試能不能掃到設備)
class BleViewController: UIViewController {var bleArray:[String] = []
//3.導入我們的ble單例
var bleManager = BLEManager.shared@IBOutlet weak var scanButton: UIButton!
@IBOutlet weak var bleTableView: UITableView!override func viewDidLoad() {
super.viewDidLoad()
initUI()
bind()
}func initUI(){}func bind(){
//2.將scan與新增的func綁定
scanButton.addTarget(self, action: #selector(scanButtonAction), for: .touchUpInside)
}//1.新增按鈕點擊的function
@objc func scanButtonAction(){
//4.透過bleManager使用掃描的功能(deviceName這邊直接帶空值測試能不能掃到設備)
bleManager.scanForPeripheralsWithServices(nil, options: nil, deviceName: “”)
}
}
透過bleManager使用掃描的功能
這邊說明一下原理,掃描到設備的func是
func scanForPeripheralsWithServices(_ serviceUUIDS:[CBUUID]?, options:[String: AnyObject]?, deviceName:String){
self.deviceName = deviceName
self.central?.scanForPeripherals(withServices: serviceUUIDS, options: options)
}
而掃到設備後會自動進到下面func中
這個func我們將掃的資訊儲存在deviceList之中,之後要娶的設備名稱或是連接設備都是透過這個deviceList。
```
//中心管理器掃描到了裝置
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 因為iOS目前不提供藍芽裝置的UUID的獲取,所以在這裡通過藍芽名稱判斷是否是本公司的裝置
if deviceList!.contains(peripheral) {
print(“deviceList!.contains(peripheral) == true”)
return
}guard peripheral.name != nil else {
print(“peripheral.name == nil”)
return
}//如果deviceName為空則把所有掃到的設備加入列表中
if let deviceName = deviceName, deviceName != “” {
if peripheral.name!.contains(deviceName) {
deviceList?.add(peripheral)
}
} else {
deviceList?.add(peripheral)
}
}
```
再完成掃五秒後更新tableView,這邊要使用到gcd的技巧
1. 先來完成重置bleTableView的功能
2. 5秒後停止掃描,並呼叫重置tableView的func
```
@objc func scanButtonAction(){
bleManager.scanForPeripheralsWithServices(nil, options: nil, deviceName: “”)
//2. 5秒後停止掃描,並呼叫重置tableView的func
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
self.bleManager.stopScan()
self.reloadBleTableView()
}
}//1. 先來完成重置bleTableView的功能
func reloadBleTableView(){
bleArray = bleManager.getDeviceNameArray()
bleTableView.reloadData()
}
```
bleManager.getDeviceNameArray()
就是透過掃描時儲存的deviceList抓出裡面的每一筆localName當作設備名稱顯示出來。
如此一來基本功能就完成了,但是在實測前我們還要實作讓手機獲得使用藍牙權限的設定。
Ble權限請求
Info.plist新增這一個應該就可以了。
好了上實機測試看看吧!GOGOGO!
失敗了….好檢討一下為什麼會失敗,咳
因為我們在做
`extension BleViewController: UITableViewDelegate, UITableViewDataSource`
這個的時候沒有指定他的delegate…我慚愧,將他補進func bind()裡面會長這樣
```
func bind(){
bleTableView.delegate = self
bleTableView.dataSource = self
scanButton.addTarget(self, action: #selector(scanButtonAction), for: .touchUpInside)
}
```
這很基本,不該忘記的Orz
再試一次
GOGOGO!
哼,成功了吧,嫩!
小結語
整個app看起來還有很多問題啊
列表,然後慢慢處理他們@_@
* 沒icon感覺很low?
* 沒設備列表線條很礙眼?
* 不是說cell可以點嗎?
* 掃描沒有提示啊?
* 多國語系也加一下吧?
* 搖一搖手機就去掃描設備可以嗎?
* 沒開藍牙按掃描怎麼辦?掃描時要做判斷吧?
各位大德大能…讓我慢慢來加上去這堆功能吧Orz
這東西我做第二次了居然還是要花兩小時我傻眼….
而且進度還沒趕上之前Orz
這篇文章使用到
* 藍牙控制
* UIKit
* Autolayout
* gcd概念
* Singleton 架構
最後附上範例的github 預祝學習愉快