I have noticed various strange behaviours with the EAAccessory interface on iOS 11 (Betas 5 and 6 **update and now in the full production version 11.0.2 **)). I wonder if anyone else has had these issues? Everything worked OK on iOS 10.
I am communicating with an accessory over a lightning socket (via a LAM), and get the following behaviour:
1. Connecting to a device, EAAccessoryManager returns a connected EAAccessory (an EASession can be established, and data received, although this doesn't always happen), but then immediately disconnects from the EAAccessory, and then returns another connected EAAccessory.
2. After opening an EASession on this new EAAccessory, no communications to the device is possible (sending data over the OutputStream doesn't result in the data getting to the accessory)
3. On switching away from the app and back again, either:
a) the app crashes
or
b) the input stream reports endEncountered and the output stream just seems to stop working, it never reports hasSpaceAvailable again.
Some simplified code which illustrates this problem is below:
import UIKit
import ExternalAccessory
class ViewController: UIViewController, StreamDelegate {
var connectedAccessory: EAAccessory?
var eaSession: EASession?
let supportedProtocolName = "My.Supported.Protocol"
@IBOutlet weak var logTextView: UITextView!
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
log("loaded")
// Do any additional setup after loading the view, typically from a nib.
NotificationCenter.default.addObserver(
forName: NSNotification.Name.EAAccessoryDidConnect,
object: nil,
queue: nil,
using: accessoryConnected)
NotificationCenter.default.addObserver(
forName: NSNotification.Name.EAAccessoryDidDisconnect,
object: nil,
queue: nil,
using: accessoryDisconnected)
EAAccessoryManager.shared().registerForLocalNotifications()
// Maybe we're already connected
if let connectedAccessory = EAAccessoryManager.shared().connectedAccessories.first {
self.connectedAccessory = connectedAccessory
openSession()
}
}
private func accessoryConnected(_ notification: Notification) {
log("accessoryConnected")
for accessory in EAAccessoryManager.shared().connectedAccessories {
log("accessory: \(accessory)")
log("")
}
// Connect to the first one (only expect there to be one for this test)
self.connectedAccessory = EAAccessoryManager.shared().connectedAccessories.first
openSession()
}
private func openSession() {
guard let connectedAccessory = self.connectedAccessory else {
return
}
self.eaSession = EASession(
accessory: connectedAccessory,
forProtocol: self.supportedProtocolName)
openStream(eaSession?.inputStream)
openStream(eaSession?.outputStream)
}
private func openStream(_ stream: Stream?) {
stream?.delegate = self
stream?.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
stream?.open()
}
public func stream(_ stream: Stream, handle streamEvent: Stream.Event) {
switch streamEvent {
case Stream.Event.openCompleted:
log("\(stream) openCompleted")
if (eaSession?.inputStream?.streamStatus == Stream.Status.open
&& eaSession?.outputStream?.streamStatus == Stream.Status.open) {
log("both input and output are open")
}
case Stream.Event.hasBytesAvailable:
log("hasBytesAvailable")
readAll()
case Stream.Event.hasSpaceAvailable:
log("hasSpaceAvailable")
case Stream.Event.errorOccurred:
log("\(stream) errorOccurred")
closeSession()
case Stream.Event.endEncountered:
log("\(stream) endEncountered")
closeSession()
default:
log("\(stream) default")
closeSession()
}
}
private func readAll() {
let bufferSize = 128
var numberOfBytesRead = 0
repeat {
var buffer = [UInt8](repeating: 0, count: bufferSize)
numberOfBytesRead = eaSession!.inputStream!.read(&buffer, maxLength: buffer.count)
if (numberOfBytesRead > 0) {
log("read:\(buffer)")
}
} while (numberOfBytesRead == 128)
}
@IBAction func sendDataButtonPressed(_ sender: UIButton) {
sendData()
}
private func sendData() {
let data: [UInt8] = [1, 0, 0, 0, 16, 0, 0, 0, 0, 3]
log("Sending: \(data)")
self.eaSession?.outputStream?.write(data, maxLength: data.count)
}
private func closeSession() {
closeStream(eaSession?.inputStream)
closeStream(eaSession?.outputStream)
eaSession = nil
}
private func closeStream(_ stream: Stream?) {
stream?.close()
stream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
stream?.delegate = nil
}
private func accessoryDisconnected(_ notification: Notification) {
log("accessoryDisconnected")
for accessory in EAAccessoryManager.shared().connectedAccessories {
log("accessory: \(accessory)")
log("")
}
guard let accessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory else {
return
}
log("disconnected accessory: \(accessory)")
log("")
closeSession()
}
private func log(_ msg: String) {
self.logTextView.insertText("\(msg)\n")
scrollLogToEnd()
}
private func scrollLogToEnd() {
let stringLength = self.logTextView.text.characters.count
self.logTextView.scrollRangeToVisible(NSMakeRange(stringLength - 1, 0))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}