Skip to content

Add runnable iOS SwiftUI demo app with encapsulated SocketManager#5

Merged
dustturtle merged 2 commits intomainfrom
copilot/update-ios-app-demo
Mar 3, 2026
Merged

Add runnable iOS SwiftUI demo app with encapsulated SocketManager#5
dustturtle merged 2 commits intomainfrom
copilot/update-ios-app-demo

Conversation

Copy link
Contributor

Copilot AI commented Mar 3, 2026

Current demos are CLI-only (main.swift/main.m). This adds a proper iOS app demo with UI and a reusable manager class wrapping all socket operations.

New: Examples/iOSDemo/

  • SocketManagerObservableObject facade over NWAsyncSocket with @Published state (isConnected, logs, receivedText, sseEvents). Single-call connect(host:port:useTLS:enableSSE:enableStreaming:) + send(_:) / disconnect().
  • 4 SwiftUI demo views — StreamBuffer, SSEParser, UTF-8 Safety, Socket Connection — each with interactive "Run All" that exercises the library and shows pass/fail inline.
  • iOSDemo.xcodeproj — references ../../ as a local SPM package dependency. Open in Xcode and run.

Other changes

  • .gitignore — added !Examples/iOSDemo/iOSDemo.xcodeproj exception
  • README.md — added iOS demo section, updated file structure tree
// SocketManager encapsulates all operations
let manager = SocketManager()
manager.connect(host: "example.com", port: 443, useTLS: true, enableSSE: true)
manager.send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
// Observe manager.isConnected, manager.receivedText, manager.sseEvents via SwiftUI

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

- Create Examples/iOSDemo/ with complete SwiftUI iOS app
- Add SocketManager.swift encapsulating all NWAsyncSocket operations
- Add demo views for StreamBuffer, SSEParser, UTF-8 Safety, Socket Connection
- Create Xcode project with local NWAsyncSocket SPM dependency
- Update .gitignore to allow demo xcodeproj
- Update README.md with iOS demo documentation

Co-authored-by: dustturtle <2305214+dustturtle@users.noreply.github.com>
Copilot AI changed the title [WIP] Update iOS app demo to meet expectations Add runnable iOS SwiftUI demo app with encapsulated SocketManager Mar 3, 2026
@dustturtle dustturtle marked this pull request as ready for review March 3, 2026 06:09
Copilot AI review requested due to automatic review settings March 3, 2026 06:09
@dustturtle dustturtle merged commit 9fc942e into main Mar 3, 2026
3 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a runnable SwiftUI iOS demo app to showcase NWAsyncSocket features with an ObservableObject-based SocketManager, complementing the existing CLI demos.

Changes:

  • Introduces Examples/iOSDemo/ Xcode project + SwiftUI app with multiple feature demo views.
  • Adds SocketManager wrapper to encapsulate connection, send/receive, TLS, SSE parsing, and streaming-text behaviors for SwiftUI binding.
  • Updates repo docs and gitignore to include and describe the new iOS demo project.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
README.md Documents the new iOS demo app and updates the repo tree + demo section.
.gitignore Un-ignores the new Examples/iOSDemo/iOSDemo.xcodeproj so it can be committed.
Examples/iOSDemo/iOSDemo/iOSDemoApp.swift SwiftUI app entry point.
Examples/iOSDemo/iOSDemo/ContentView.swift Navigation menu linking to the component demo screens.
Examples/iOSDemo/iOSDemo/SocketManager.swift ObservableObject wrapper around NWAsyncSocket with published state and delegate handling.
Examples/iOSDemo/iOSDemo/Views/StreamBufferDemoView.swift UI-driven StreamBuffer demonstrations.
Examples/iOSDemo/iOSDemo/Views/SSEParserDemoView.swift UI-driven SSE parsing demonstrations.
Examples/iOSDemo/iOSDemo/Views/UTF8SafetyDemoView.swift UI-driven UTF-8 boundary safety demonstrations.
Examples/iOSDemo/iOSDemo/Views/SocketConnectionDemoView.swift Interactive connect/send/receive UI using SocketManager.
Examples/iOSDemo/iOSDemo/Assets.xcassets/Contents.json Asset catalog metadata.
Examples/iOSDemo/iOSDemo/Assets.xcassets/AppIcon.appiconset/Contents.json App icon asset metadata.
Examples/iOSDemo/iOSDemo/Assets.xcassets/AccentColor.colorset/Contents.json Accent color asset metadata.
Examples/iOSDemo/iOSDemo.xcodeproj/project.pbxproj Xcode project defining the iOS demo target and local SPM dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

/// Disconnect the current socket.
func disconnect() {
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disconnect() nils out socket but leaves isConnected unchanged until the async delegate callback arrives. This can temporarily show the wrong connection status in SwiftUI. Consider setting isConnected = false (and optionally resetting per-connection state) synchronously when initiating a disconnect.

Suggested change
func disconnect() {
func disconnect() {
// Update published connection state immediately so SwiftUI reflects
// the disconnect without waiting for async delegate callbacks.
isConnected = false

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +49
let portNum = UInt16(port) ?? 443
manager.connect(host: host, port: portNum,
useTLS: useTLS,
enableSSE: enableSSE,
enableStreaming: enableStreaming)
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user enters port "0", UInt16(port) succeeds and the app will attempt to connect to port 0, which will crash inside NWAsyncSocket.connect (it force-unwraps NWEndpoint.Port(rawValue:)). Validate that the parsed port is in 1...65535 and surface an error to the user instead of proceeding.

Copilot uses AI. Check for mistakes.
### Swift Demo
### iOS App Demo (Recommended)

A complete SwiftUI iOS app is included at `Examples/iOSDemo/`. Open `Examples/iOSDemo/iOSDemo.xcodeproj` in Xcode and run on a simulator or device.
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README says the iOS demo can be run “on a simulator or device”, but the Xcode project has an empty DEVELOPMENT_TEAM, so running on a physical device will require the user to set a signing team in Xcode first. Consider adding a short note here to avoid confusion.

Suggested change
A complete SwiftUI iOS app is included at `Examples/iOSDemo/`. Open `Examples/iOSDemo/iOSDemo.xcodeproj` in Xcode and run on a simulator or device.
A complete SwiftUI iOS app is included at `Examples/iOSDemo/`. Open `Examples/iOSDemo/iOSDemo.xcodeproj` in Xcode and run on a simulator or device (for a physical device, first select your Apple Developer team under **Signing & Capabilities** in Xcode).

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +130
func socket(_ sock: NWAsyncSocket, didConnectToHost host: String, port: UInt16) {
isConnected = true
appendLog("✅ Connected to \(host):\(port)")
// Start reading
sock.readData(withTimeout: -1, tag: readTag)
readTag += 1
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delegate callbacks can arrive from a previous socket instance after connect() creates a new one (because disconnect() is async). Right now any late didConnect/didRead will still mutate this manager’s state. Consider ignoring callbacks unless sock matches the current socket (e.g., identity check) to avoid mixing state across reconnections.

Copilot uses AI. Check for mistakes.
Comment on lines +144 to +151
func socketDidDisconnect(_ sock: NWAsyncSocket, withError error: Error?) {
isConnected = false
if let error = error {
appendLog("🔴 Disconnected: \(error.localizedDescription)")
} else {
appendLog("🔴 Disconnected")
}
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

socketDidDisconnect unconditionally sets isConnected = false. If a disconnect from an older socket instance is delivered after a newer socket has connected, this will incorrectly flip the UI back to disconnected. Add a guard so only the active/current socket can update connection state.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants