rasheed-k-mozaffar/reachable
Reachable is a lightweight Swift package for monitoring network connectivity on Apple platforms.
Why Reachable
- Simple observable API for SwiftUI and app state
AsyncStreamsupport for structured concurrency- Combine publisher support for existing reactive pipelines
- Callback-based hooks for reconnect and disconnect events
- SwiftUI helpers for common offline UI patterns
MockNetworkMonitorfor previews and tests
Platforms
- iOS 17+
- iPadOS 17+ via iOS support
- macOS 14+
- watchOS 10+
Installation
Add Reachable to your Package.swift:
.package(url: "https://github.com/your-org/Reachable.git", from: "1.0.0")Then depend on the product:
.product(name: "Reachable", package: "Reachable")Quick Start
Create a monitor and read the current connection state:
import Reachable
let monitor = NetworkMonitor()
if monitor.isConnected {
print("Online")
} else {
print("Offline")
}If you want more detail, inspect the full state snapshot:
let state = monitor.state
print("Connected:", state.isConnected)
print("Type:", state.connectionType)
print("Expensive:", state.isExpensive)
print("Constrained:", state.isConstrained)
print("VPN:", state.isVPN)SwiftUI
Use NetworkMonitor.shared for app-wide injection:
import Reachable
import SwiftUI
@main
struct DemoApp: App {
@State private var monitor = NetworkMonitor.shared
var body: some Scene {
WindowGroup {
ContentView()
.networkMonitor(monitor)
}
}
}
struct ContentView: View {
@Environment(\.networkMonitor) private var monitor
var body: some View {
VStack(spacing: 12) {
Text(monitor.isConnected ? "Online" : "Offline")
Text("Connection: \(String(describing: monitor.connectionType))")
Text(monitor.isConstrained ? "Low Data Mode" : "Unconstrained")
}
.networkRequired()
}
}You can also present built-in offline UI:
import Reachable
import SwiftUI
struct Screen: View {
var body: some View {
ZStack(alignment: .top) {
Content()
NetworkStatusBanner()
}
.networkRequired()
}
}Or provide a custom disconnected overlay:
ContentView()
.networkRequired { state in
VStack(spacing: 8) {
Text("Offline")
.font(.headline)
Text("Current type: \(String(describing: state.connectionType))")
.font(.caption)
}
.padding()
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
}Callbacks
Reachable can notify you when the network comes back, drops, or changes transport type:
import Reachable
let monitor = NetworkMonitor()
let reconnectToken = monitor.onReconnect {
print("Connection restored")
}
let disconnectToken = monitor.onDisconnect {
print("Connection lost")
}
let typeToken = monitor.onConnectionTypeChange { newType in
print("Now using:", newType)
}Keep the returned tokens alive for as long as you want the callbacks to remain active.
AsyncStream
import Reachable
let monitor = NetworkMonitor()
Task {
for await state in monitor.stateStream {
print("Connected:", state.isConnected)
print("Type:", state.connectionType)
print("Expensive:", state.isExpensive)
}
}Combine
import Combine
import Reachable
let monitor = NetworkMonitor()
let cancellable = monitor.statePublisher
.sink { state in
print("Current state:", state)
}Mocking and Previews
import Reachable
import SwiftUI
#Preview {
let monitor = MockNetworkMonitor(initialState: .disconnected())
return ContentView()
.networkMonitor(monitor)
}You can drive preview or test updates with monitor.state = ... or monitor.setState(...).
Core Types
NetworkMonitor: the mainNWPathMonitor-backed monitorMockNetworkMonitor: a manually controlled monitor for previews and testsNetworkState: the full snapshot of current network conditionsConnectionStatus: connected or disconnectedConnectionType: wifi, cellular, ethernet, vpn, or unknown
Common Use Cases
- Show an offline banner or overlay
- Pause syncing while disconnected
- Retry work when connectivity returns
- Respect Low Data Mode with
isConstrained - Avoid large transfers on expensive connections
Documentation
The package also ships with DocC documentation covering:
- monitoring connectivity
- using Reachable in SwiftUI
- testing connectivity-driven UI
Package Metadata
Repository: rasheed-k-mozaffar/reachable
Default branch: main
README: README.md