Skip to content

Commit 8a8ae68

Browse files
wxtskyclaude
andcommitted
fix(ios): reconnect to Mac when returning to the foreground
Reported as "Code Island Buddy keeps disconnecting when app minimized or iPhone closed" (#261). Root cause: the MultipeerConnectivity session that carries the rich, foreground status (the "Mac connected" state ContentView shows) is torn down by iOS whenever the app is backgrounded or the phone is locked — that part is expected, standard platform behavior for Wi-Fi/Bonjour-based connectivity, not a bug, and it's exactly why a separate Bluetooth channel already exists for background Live Activity/Watch summaries (bluetooth-central is already declared in Info.plist and works as designed). The actual bug: nothing ever told the browser to restart afterwards. `ContentView`'s `.onAppear { connection.start() }` runs once, when the view first appears, not on every return to the foreground, and `start()` itself no-ops once `browsing` is already (stale-)true. So after any background/foreground cycle the app was stuck showing "disconnected" until the user manually tapped the search toggle — indistinguishable from "keeps disconnecting" to a user who never touches that button. Fix: observe `scenePhase` at the App level and, only on a real background -> active transition (not initial launch) with no connected peer, force a clean stop()+start() of discovery. No-ops whenever the connection is already healthy. Verified via full `xcodebuild` (all targets, complete embedded app). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d716c2b commit 8a8ae68

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

ios/CodeIslandCompanion/CodeIslandCompanion/CodeIslandCompanionApp.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import SwiftUI
44
struct CodeIslandCompanionApp: App {
55
@StateObject private var connection: CompanionConnection
66
@StateObject private var liveActivity: LiveActivityController
7+
@Environment(\.scenePhase) private var scenePhase
78

89
init() {
910
let connection = CompanionConnection()
@@ -26,6 +27,13 @@ struct CodeIslandCompanionApp: App {
2627
.environmentObject(connection)
2728
.environmentObject(liveActivity)
2829
}
30+
// MultipeerConnectivity sessions don't survive backgrounding; without
31+
// this, returning to the foreground after minimizing the app or
32+
// locking the phone left it stuck showing "disconnected" (#261).
33+
.onChange(of: scenePhase) { oldPhase, newPhase in
34+
guard oldPhase == .background, newPhase == .active else { return }
35+
connection.reconnectIfNeeded()
36+
}
2937
}
3038

3139
#if DEBUG

ios/CodeIslandCompanion/CodeIslandCompanion/CompanionConnection.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,26 @@ final class CompanionConnection: NSObject, ObservableObject {
100100
pendingReconnectPeer = nil
101101
}
102102

103+
/// MultipeerConnectivity sessions are torn down by iOS while the app is
104+
/// backgrounded — unlike Bluetooth, Wi-Fi/Bonjour connectivity has no
105+
/// platform-level background continuation, so this is expected, not a bug
106+
/// (#261). The problem is that nothing ever told the browser to restart:
107+
/// `ContentView`'s `.onAppear { connection.start() }` only fires once, when
108+
/// the view first appears, not on every return to the foreground, and
109+
/// `start()` no-ops while `browsing` is already (stale-)true. So once the
110+
/// session drops in the background, the app was stuck showing
111+
/// "disconnected" until the user manually tapped the search toggle.
112+
///
113+
/// Call this from a scene-phase observer whenever the app becomes active
114+
/// again; it forces a clean restart of discovery only when we're not
115+
/// already connected, so it's a no-op for an already-healthy connection.
116+
func reconnectIfNeeded() {
117+
guard !isDemoMode, mockStatePayload == nil else { return }
118+
guard connectedPeer == nil else { return }
119+
stop()
120+
start()
121+
}
122+
103123
func connect(to peer: MCPeerID) {
104124
exitDemoMode()
105125
browser.invitePeer(peer, to: session, withContext: nil, timeout: 12)

0 commit comments

Comments
 (0)