Wired data transfer between an app on two iOS/iPadOS devices--Possible or pipe dream?

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.

Answered by wmk in 779656022

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

is it possible to have the apps send data to each other over a wired connection?

Yes. Add an Ethernet USB dongle to each device and they can talk over Ethernet.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Reposting as reply since Quinn said that comments are lower-visibility to him

Hi Quinn, I get what you're saying, but that would mean my users would have to get two USB dongles and an ethernet cable as opposed to just using the USB cable they already own. This is complicated by the fact that devices with a Lightning port need to attach the USB dongle to a Lightning-to-USB adaptor. At this point I'm not looking for ways to implement a USB-to-USB connection using just a single USB cable--I just want to know if the system(s) involved permit this sort of direct connection

Quinn said that comments are lower-visibility to him

I’m not notified about folks adding comments, so I didn’t see your reply until today.

I just want to know if the system(s) involved permit this sort of direct connection

I doubt it. This doesn’t even work on the Mac. That is, if you plug two Macs together via USB [1], it doesn’t bring up a network interface between them.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] USB, not Thunderbolt. This will work if you use a Thunderbolt cable, but Thunderbolt isn’t going to help you on iOS.

Thanks; I would've gone on a (really long) wild goose chase without that

Accepted Answer

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()
    }
}
Wired data transfer between an app on two iOS/iPadOS devices--Possible or pipe dream?
 
 
Q