---
title: creaturesurvive/localnetworkaccess
framework: Swift Package Catalog
role: article
path: packages/creaturesurvive/localnetworkaccess
---

# creaturesurvive/localnetworkaccess

Check and request the Local Network privilege on iOS, iPadOS, visionOS and macOS, which Apple

## Why

Every app that talks to devices on the LAN (casting, smart home, NAS, printers, game consoles) hits local network privacy. There's no `authorizationStatus` and no `requestAuthorization` (FB8711182). Most apps paper over this with a copy-pasted Bonjour hack that:

- shows the alert when you only wanted to check the status, - can't tell "denied" from "the alert is still on screen", - breaks when `NSBonjourServices` is missing an entry, and - never notices when the user flips the switch in Settings.

## How it works

It follows Apple's [TN3179](https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy):

- **Probe:** it connects a UDP socket to an address in the device's own subnet. That's a local   network operation, but connecting a UDP socket sends no packets. A denial shows up as   `NWPath.UnsatisfiedReason.localNetworkDenied`; otherwise the connection becomes ready. The   system retries the connection when the privilege changes, so the probe also follows later   changes. - **Checking vs. asking:** probing an undetermined privilege is what shows the alert, so   `status()` doesn't probe until the app has asked through `request()` (or called   `markAsRequested()`). That flag is stored in `UserDefaults`. Deleting the app clears it along   with the privilege itself. - **Waiting for the answer:** while the alert is on screen the system reports "denied", so   `request()` watches app activity:   - The alert deactivates the app. When the app becomes active again, the user has answered.   - If no alert appears, the decision was made earlier and the result is final. - **Background:** in the background the system denies local network operations silently and   records nothing, so `request()` doesn't probe there. - **Platforms without the privilege** (tvOS, watchOS, macOS 14 and earlier) report   `.notRequired`.

## Features

- `status()`: current status, never prompts. - `request()`: prompts if needed and waits for the answer (up to a 30-second timeout). - `statusUpdates()`: an `AsyncStream` that re-checks on foreground and follows live changes. - `lastKnownStatus`: the persisted last answer, for an instant initial UI. - `LocalNetworkAccessMonitor`: `@Observable` wrapper with `request()` and `openSettings()`. - `configurationIssues(bonjourServices:)`: catches a missing `NSLocalNetworkUsageDescription`   and undeclared or malformed `NSBonjourServices` entries, which otherwise fail silently. - Network.framework helpers:   - `NWBrowser.State.isLocalNetworkDenied` and `NWListener.State.isLocalNetworkDenied`   - `NWConnection.isBlockedByLocalNetworkPrivacy` and `NWPath.isLocalNetworkDenied`   - `NWError.isLocalNetworkPolicyDenied` (-65570)   - `URLError.mayBeLocalNetworkDenial` - `LocalAddress.isLocal(_:)`: whether a host is a local network destination (RFC 1918,   link-local, ULA, multicast, `.local`). - The probe and app-activity sources are protocols, so your own logic can be unit tested.

## SwiftUI

```swift struct DevicesView: View {     @State private var localNetwork = LocalNetworkAccessMonitor()

var body: some View {         switch localNetwork.status {         case .notDetermined:             ContentUnavailableView {                 Label("Find Devices", systemImage: "tv")             } actions: {                 Button("Continue") { Task { await localNetwork.request() } }             }         case .denied:             Button("Allow Local Network Access in Settings") { localNetwork.openSettings() }         default:             DeviceList()         }     } } ```

## Configuration

Add `NSLocalNetworkUsageDescription` to your app's `Info.plist`. If you use Bonjour, also list each service type in `NSBonjourServices`. Check both in debug builds:

```swift #if DEBUG for issue in LocalNetworkAccess.configurationIssues(bonjourServices: ["_googlecast._tcp"]) {     assertionFailure(issue.description) } #endif ```

Raw multicast on iOS (SSDP, mDNS sockets) also needs the `com.apple.developer.networking.multicast` entitlement, which can't be checked at run time.

## Installation

Add LocalNetworkAccess to your `Package.swift`:

```swift dependencies: [     .package(url: "https://github.com/CreatureSurvive/LocalNetworkAccess.git", from: "1.0.0"), ], targets: [     .target(name: "MyApp", dependencies: ["LocalNetworkAccess"]), ] ```

Or in Xcode, choose **File › Add Package Dependencies…** and enter `https://github.com/CreatureSurvive/LocalNetworkAccess`.

### Requirements

| Platform | Minimum | | --- | --- | | iOS | 17.0 | | macOS | 14.0 | | tvOS | 17.0 | | watchOS | 10.0 | | visionOS | 1.0 |

Swift 6.1 (Xcode 16.4) or later, in Swift 6 language mode. No third-party dependencies.

## Testing

`swift test` covers:

- the request state machine (allow, deny, an answer arriving late, no alert, timeouts,   background), using a scripted probe and scripted app activity - status streaming - the configuration checks - address classification - subnet math

On macOS it also runs the real probe, which must report "allowed" because command-line tools always have local network access.

The simulator doesn't implement local network privacy. `Example/` has an iOS/macOS demo app for checking the real alert on a device:

```sh cd Example && xcodegen generate && open LocalNetworkDemo.xcodeproj ```

## Limitations

- The status can't be read before the app has requested it, because no API exists for that.   If other code in your app triggers the alert first, call `markAsRequested()` afterwards. - On macOS, if the app isn't frontmost when the alert appears, `request()` can't see the answer   until the app is activated. It returns `.notDetermined` at the timeout. - With no Wi-Fi or Ethernet connection there's nothing to probe, so `status()` returns   `.unknown`.

## Changelog

See [CHANGELOG.md](CHANGELOG.md). Releases follow [Semantic Versioning](https://semver.org).

## Contributing

Issues and pull requests are welcome. Please run `swift test` before opening a pull request, and add tests for new behavior.

## License

Available under the MIT license. See [LICENSE](LICENSE) for details.

## Package Metadata

Repository: creaturesurvive/localnetworkaccess

Default branch: main

README: README.md
