PlugchoiceSDK. It shows Link, the hosted flow at connect.plugchoice.com, in a sheet, and does what a web page can’t: join a charger’s Wi-Fi, find and reach chargers on the local network, talk Bluetooth to them and scan a QR code. It works with UIKit and SwiftUI.
Install
Requirements: iOS 16 or later, and Xcode 16 or later. On iOS 18 and later the SDK joins a charger’s Wi-Fi through AccessorySetupKit; on iOS 16 and 17 it uses the classic join prompt. Add the package with Swift Package Manager. In Xcode, choose File › Add Package Dependencies…, enterhttps://github.com/plugchoice/mobile-sdk, pick Up to Next Minor Version from 0.4.0, and add the PlugchoiceSDK library to your app target.
In a Package.swift:
Package.swift
Configure your app
Capabilities
In Xcode, open your app target’s Signing & Capabilities and add:
Your App ID needs the same capabilities in the Apple Developer portal.
Info.plist
Info.plist
Create the SDK
Create onePlugchoice instance for your app’s lifetime. Give it a callback that fetches a client secret from your server:
fetchClientSecret when Link opens, while the hosted flow loads. It calls it again, with the same action, whenever the secret expires (after 60 minutes), so a user can take as long as they need. The secret never goes into a URL, and the SDK never logs it.
Open Link
Open Link with an action, from a view controller:plugchoiceLink modifier. Setting isPresented to false closes Link with cancelled.
The SDK passes the action to the hosted flow without reading it, so a new action works through
.custom before it gets a helper. Your client session’s scope must cover the action’s charger or site. Actions explains each one.
Link is a sheet the user can’t swipe away. It keeps the screen awake while it’s open, because a charger’s Wi-Fi connection drops when the phone locks.
Handle the result
The completion gets aLinkResult:
LinkResult.Status
.success, .cancelled or .error.String
The action the link session did. The user may have picked another one from the list of what a charger can do; otherwise it’s the action you opened.
String?
The link session, when the hosted flow started one.
nil when Link closed before that.[Device]
The devices the link session reported, each
{ type, id }. On .success, the devices it finished. type is "charger" today; ignore types you don’t know.LinkError?
On
.error: a code, and a message for your logs.GET /sdk/v1/link-sessions/{id}). Link sessions explains why.
Errors
On.error, result.error?.code says why. The SDK sets two codes itself:
Every other code comes from the hosted flow. The one to know is
clientSecretUnavailable: your fetchClientSecret threw, returned an empty string or took longer than 30 seconds. The user then sees this screen, with Try again, and the result reports clientSecretUnavailable if they close it.
Other codes are for your logs; don’t branch on them. Errors lists them.
Show actions where they work
Not every charger can do every action, and not every phone has what an action needs. Show a button only when both are true:- The charger’s capability for it is
capable. Your server reads it with Get a charger. - Every transport in the capability’s
needsis inPlugchoice.transports().
Plugchoice.transports() asks for no permission. It returns:
wifi,httpandsocket: always.lan: when your Info.plist declares at least one type inNSBonjourServices.ble: on a device with Bluetooth LE (not the simulator), when your Info.plist hasNSBluetoothAlwaysUsageDescription.
Test on a real device
The simulator can open Link and run the flow up to the charger, but it can’t reach one. Use an iPhone for:- joining a charger’s Wi-Fi, and the AccessorySetupKit prompt on iOS 18 and later
- the Local Network prompt, and finding chargers on the home Wi-Fi
- everything Bluetooth (the simulator has no Bluetooth)
- scanning a QR code with the camera