检测Apple Pencil是否连接到iPad Pro

13

有没有一种API可以确定Apple Pencil是否连接到iPad Pro? 查看9.1 SDK,我没有发现直接实现此功能的内容。或者,也许可以使用蓝牙API来完成。


你在蓝牙API中找到了什么吗? - Rich
@rich 我相信你唯一能做的就是扫描蓝牙设备。但是你需要获得蓝牙权限。我还没有尝试过。 - combinatorial
2个回答

7

我找不到任何关于Apple Pencil的蓝牙实现的实际文档(我认为也不存在),但是以下代码可以正常工作。

它检查广告支持“设备信息”服务的连接设备,然后检查其中是否有名称为“Apple Pencil”的设备。

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
  }
}

1
@combinatorial 哎呀!你能看出我已经很久没有写过太多的Objective-C了吗?咳咳。感谢您的编辑! - Rich
@"180A"是什么?它是固定的吗? - Chandan Shetty SP
2
@ChandanShettySP @"180A" 是一个NSString,对应于十六进制值0x180A,这是蓝牙“分配号码”或“短UUID”用于“设备信息”服务。这是当前Pencil设备提供的一项服务,并且随着大多数(或可能所有)蓝牙设备都应该提供此服务,它应该会在未来继续工作。 - Rich
如果您在iOS 11及更高版本中从控制中心关闭蓝牙,则此代码将无法正常工作,因为CBCentralManager将处于CBManagerStatePoweredOff状态。 - passingnil
嗯 - 这只在设备连接时有效。如果与iPad分离一段时间,它会失去连接。在这种情况下,我们可能需要扫描所有蓝牙设备,而这将无法实现。 - Victor Engel

6
我花了很长时间才明白,只有在使用其connect(_ peripheral: CBPeripheral, options: [String : Any]? = nil)函数初始化连接时,CBCentralManager的centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)才会被调用(是的,阅读文档有所帮助:])。
由于我们没有回调来通知设备已通过用户连接到设备(就像Apple Pencil一样-我会很高兴被证明错误),我不得不在这里使用计时器。
它的工作原理如下:
当您初始化ApplePencilReachability时,设置一个定时器以每秒检查pencil的可用性。如果找到笔,则定时器将被无效化;如果关闭蓝牙,则也将被无效化。重新打开后会创建一个新的计时器。
我对此并不特别自豪,但它确实有效 :)
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
    }
  }

}

那个文件的版权是2017年的,所以可能是另一种情况,但是很好发现,我实际上认识丹尼尔:D - HAS
你应该通过查看笔的特定服务(UUID)来比较外设,而不是通过名称。你可以使用Xcode蓝牙工具浏览提供的服务。 - hnh
感谢 @hnh 的指针,我目前没有时间去看它,但我会把它写在我的待办事项清单上 :) (如果你已经有解决方案,请随意编辑代码 :)) - HAS
2
你是对的。另外,BT相关的所有内容都应该在后台队列上运行(可以使用DispatchQueue.main.async回调)。而且,名称比较可能是一个不好的想法。 - hnh
在 iOS 13 及更高版本中,有一个 CBCentralManager.registerForConnectionEvents 方法,因此不需要像这样每秒轮询。 - Matt Gallagher
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接