If two iOS/iPadOS devices have your app opened, is it possible to have the apps send data to each other over a wired connection?
E.g. If two iPhone 15s are connected by USB-C, can I get my app in iPhone A to send data to iPhone B and vice-versa?
I've been looking around for quite a while now and at this point I just want to know if it's technically feasible.
tl;dr - Connect the devices and use the Network framework as usual.
So I was figuring out another problem when I stumbled upon this thread: https://forums.developer.apple.com/forums/thread/658393
In it, Quinn demonstrates how to use the Network framework over pure USB (no ethernet dongle) to connect to a Mac. It turns out this also applies to two iOS devices (and maybe beyond). So far I've only tested it with two devices with Lightning ports connected by a Lightning-to-USB adaptor (no power attached) plus a USB-to-Lightning cable.
Code to reproduce:
class NetworkManager: ObservableObject {
static var shared = NetworkManager()
@Published private(set) var text = ""
@Published private(set) var listenerQ: NWListener?
@Published private(set) var browserQ: NWBrowser?
var isListenerStarted: Bool { listenerQ != nil }
var isBrowserStarted: Bool { browserQ != nil }
func log(_ string: String) {
print(string)
text.append(string + "\n")
}
func startBrowser() -> NWBrowser {
let browser = NWBrowser(for: .bonjour(type: "_customService._tcp", domain: nil), using: .tcp)
browser.stateUpdateHandler = { self.log("Browser did change state, new: \($0)") }
browser.browseResultsChangedHandler = { self.log("Browser did change results, new: \($0) \($1)") }
browser.start(queue: .main)
return browser
}
func stopBrowser(_ browser: NWBrowser) {
log("Browser will stop")
browser.stateUpdateHandler = nil
browser.cancel()
}
func startStopBrowser() {
if let browser = browserQ {
browserQ = nil
stopBrowser(browser)
} else {
browserQ = startBrowser()
}
}
func startListener() -> NWListener {
print("Listener will start")
let listener = try! NWListener(using: .tcp)
listener.service = .init(type: "_customService._tcp")
listener.stateUpdateHandler = { print("Listener did change state, new: \(($0))") }
listener.newConnectionHandler = { connection in
let remotePeer = connection.currentPath?.remoteEndpoint
self.log("Listener did receive connection, from: \(remotePeer)")
connection.cancel()
}
listener.start(queue: .main)
return listener
}
func stopListener(_ listener: NWListener) {
log("Listener will stop")
listener.stateUpdateHandler = nil
listener.cancel()
}
func startStopListener() {
if let listener = self.listenerQ {
self.listenerQ = nil
self.stopListener(listener)
} else {
self.listenerQ = self.startListener()
}
}
}
struct ContentView: View {
@ObservedObject var netMan = networkManager
var body: some View {
VStack {
HStack {
Button("\(netMan.isListenerStarted ? "Stop" : "Start") listener") {
netMan.startStopListener()
}
Button("\(netMan.isBrowserStarted ? "Stop" : "Start") browser") {
netMan.startStopBrowser()
}
}
.padding()
Text("Log")
.font(.title.bold())
ScrollView {
Text(netMan.text)
}
}
.padding()
}
}