问题 检测Apple Pencil是否已连接到iPad Pro


是否有API可以让您确定Apple Pencil是否已连接到iPad Pro?查看9.1 SDK我没有看到任何直接这样做的东西。或者这可以使用蓝牙API完成。


12331
2017-09-12 18:28


起源

你在蓝牙API中找到了什么吗? - Rich
@rich我相信你唯一能做的就是扫描蓝牙设备。但是你需要BT的许可。我没试过。 - combinatorial


答案:


我在Apple Pencil的蓝牙上找不到任何实际文档 实现(我不相信任何存在),但以下代码工作 for Me™。

它检查自己宣传支持的连接设备 “设备信息”服务,然后如果其中任何一个具有名称“Apple 铅笔”。

PencilDetector.h

@import CoreBluetooth

@interface PencilDetector : NSObject <CBCentralManagerDelegate>

- (instancetype)init;

@end

PencilDetector.m

#include "PencilDetector.h"

@interface PencilDetector ()

@end

@implementation PencilDetector
{
  CBCentralManager* m_centralManager;
}

- (instancetype)init
{
  self = [super init];
  if (self != nil) {
    // Save a reference to the central manager. Without doing this, we never get
    // the call to centralManagerDidUpdateState method.
    m_centralManager = [[CBCentralManager alloc] initWithDelegate:self
                                                            queue:nil
                                                          options:nil];
  }

  return self;
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
  if ([central state] == CBCentralManagerStatePoweredOn)
  {
    // Device information UUID
    NSArray* myArray = [NSArray arrayWithObject:[CBUUID UUIDWithString:@"180A"]];

    NSArray* peripherals =
      [m_centralManager retrieveConnectedPeripheralsWithServices:myArray];
    for (CBPeripheral* peripheral in peripherals)
    {
        if ([[peripheral name] isEqualToString:@"Apple Pencil"])
        {
            // The Apple pencil is connected
        }
    }
  }
}

@end

在实践中,以下更简单的同步代码,它不等待 在检查连接的设备之前要打开中央管理器 似乎在我的测试中同样有效。但是,文档说明您不应该这样做 调用管理器上的任何方法,直到状态更新为止 CBCentralManagerStatePoweredOn,所以较长的代码可能更安全。

随时随地

m_centralManager = [[CBCentralManager alloc] initWithDelegate:nil
                                                        queue:nil
                                                      options:nil];

// Device information UUID
NSArray* myArray = [NSArray arrayWithObject:[CBUUID UUIDWithString:@"180A"]];

NSArray* peripherals =
  [m_centralManager retrieveConnectedPeripheralsWithServices:myArray];
for (CBPeripheral* peripheral in peripherals)
{
  if ([[peripheral name] isEqualToString:@"Apple Pencil"])
  {
    // The Apple pencil is connected
  }
}

5
2018-02-04 17:14



@combinatorial哎呀!你能告诉它已经有一段时间了,因为我写了很多Objective-C? 啊哈。谢谢你的编辑! - Rich
什么是@“180A”?它固定了吗? - Chandan Shetty SP
@ChandanShettySP @“180A”是对应于十六进制值的NSString 0x180A,“设备信息”服务的蓝牙“分配号码”或“短UUID”。这是当前铅笔设备提供的服务,并且它将在未来继续工作,因为大多数(或可能是所有)蓝牙设备应该提供此服务。 - Rich
如果你在ios 11及更高版本的控制中心关闭蓝牙,此代码无效,CBCentralManager将具有CBManagerStatePoweredOff状态 - passingnil


我花了很长时间才弄明白CBCentralManager的 centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) 仅在通过其启动连接时调用 connect(_ peripheral: CBPeripheral, options: [String : Any]? = nil) 功能(是的,阅读文档帮助:])。

由于我们没有通过用户将设备连接到设备的回调(就像Apple Pencil的情况一样 - 我很想在这一点上被证明是错误的),我不得不求助于在这里使用计时器。

这是它的工作原理:

初始化时 ApplePencilReachability 设置计时器,每秒检查铅笔的可用性。如果找到铅笔,定时器将失效,如果蓝牙关闭,它也会失效。当它再次打开时,会创建一个新的计时器。

我并不是特别自豪,但它有效:-)

import CoreBluetooth

class ApplePencilReachability: NSObject, CBCentralManagerDelegate {

  private let centralManager = CBCentralManager()
  var pencilAvailabilityDidChangeClosure: ((_ isAvailable: Bool) -> Void)?

  var timer: Timer? {
    didSet {
      if oldValue !== timer { oldValue?.invalidate() }
    }
  }

  var isPencilAvailable = false {
    didSet { 
      guard oldValue != isPencilAvailable else { return }
      pencilAvailabilityDidChangeClosure?(isPencilAvailable)
    }
  }

  override init() {
    super.init()
    centralManager.delegate = self
    centralManagerDidUpdateState(centralManager) // can be powered-on already?
  }
  deinit { timer?.invalidate() }

  func centralManagerDidUpdateState(_ central: CBCentralManager) {
    if central.state == .poweredOn {
      timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { 
        [weak self] timer in // break retain-cycle
        self?.checkAvailability()
        if self == nil { timer.invalidate() }
      }
    } else {
      timer = nil
      isPencilAvailable = false
    }
  }

  private func checkAvailability() {
    let peripherals = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: "180A")])
    let oldPencilAvailability = isPencilAvailable
    isPencilAvailable = peripherals.contains(where: { $0.name == "Apple Pencil" })
    if isPencilAvailable {
      timer = nil // only if you want to stop once detected
    }
  }

}

4
2017-12-21 14:20



承认吧!你刚从这里复制了代码! ;-): gitlab.com/DanielBocksteger/BOApplePencilReachability/blob/... - hnh
好吧,那个文件有2017年的版权所以它可能反过来了,但很好找,我实际上知道丹尼尔:D - HAS
您可能不应该按名称比较外围设备,而是查看铅笔特定服务(UUID)。您可以使用Xcode蓝牙工具浏览所提供的服务。 - hnh
感谢指针@hnh,我目前没时间看它,但我会把它写在我的待办事项清单上:)(你已经有了解决方案请随意编辑代码:)) - HAS
你是对的。此外,FWIW,所有BT的东西应该可能在后台队列上运行(它可能是DispatchQueue.main.async回调)。而且,名称比较可能是一个坏主意。 - hnh