NEHotspotNetwork headaches

I'm trying to use NEHotspotNetwork to configure an IoT. I've read all the issues that have plagued other developers when using this framework, and I was under the impression that bugs were filed and fixed. Here are my issues in hopes that someone can catch my bug, or has finally figured this out and it's not a bug in the framework with no immediate fix on the horizon.

If I use the following code:

let config = NEHotspotConfiguration(ssid: ssid)
config.joinOnce = true

KiniStatusBanner.shared.show(text: "Connecting to Kini", in: presentingVC.view)

NEHotspotConfigurationManager.shared.apply(config) { error in
  DispatchQueue.main.async {
    if let nsError = error as NSError?,
       nsError.domain == NEHotspotConfigurationErrorDomain,
       nsError.code == NEHotspotConfigurationError.alreadyAssociated.rawValue {
      print("Already connected to \(self.ssid)")
      KiniStatusBanner.shared.dismiss()
      self.presentCaptivePortal(from: presentingVC, activationCode: activationCode)
    } else if let error = error {
// This doesn't happen
      print("❌ Failed to connect: \(error.localizedDescription)")
      KiniStatusBanner.shared.update(text: "Failed to Connect to Kini. Try again later.")
      KiniStatusBanner.shared.dismiss(after: 2.5)
    } else {
      // !!!! Most often, this is the path the code takes
      NEHotspotNetwork.fetchCurrent { current in
        if let ssid = current?.ssid, ssid == self.ssid {
          log("✅✅ 1st attempt: connected to \(self.ssid)")
          KiniStatusBanner.shared.dismiss()
          self.presentCaptivePortal(from: presentingVC, activationCode: activationCode)
        } else {
          // Dev forums talked about giving things a bit of time to settle and then try again
          DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            NEHotspotNetwork.fetchCurrent { current in
              if let ssid = current?.ssid, ssid == self.ssid {
                log("✅✅✅ 2nd attempt: connected to \(self.ssid)")
                KiniStatusBanner.shared.dismiss()
                self.presentCaptivePortal(from: presentingVC, activationCode: activationCode)
              } else {
                log("❌❌❌ 2nd attempt: Failed to connect: \(self.ssid)")
                KiniStatusBanner.shared.update(text: "Could not join Kini network. Try again.")
                KiniStatusBanner.shared.dismiss(after: 2.5)
                self.cleanupHotspot()
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                  print("cleanup again")
                  self.cleanupHotspot()
                }
              }
            }
          }
          log("❌❌ 1st attempt: Failed to connect: \(self.ssid)")
          KiniStatusBanner.shared.update(text: "Could not join Kini network. Try again.")
          KiniStatusBanner.shared.dismiss(after: 2.5)
          self.cleanupHotspot()
        }

As you can see, one can't just use NEHotspotConfigurationManager.shared.apply and has to double-check to make sure that it actually succeeds, by checking to see if the SSID desired, matches the one that the device is using.

Ok, but about 50% of the time, the call to NEHotspotNetwork.fetchCurrent gives me this error:

NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request

Well, there is a workaround for that randomness too. At some point before calling this code, one can:

let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()

That eliminates the NEHotspotNetwork nehelper sent invalid result code [1] for Wi-Fi information request

BUT... three issues.

  1. The user is presented with an authorization alert: Allow "Kini" to use your location? This app needs access to you Wi-Fi name to connect to your Kini device. Along with a map with a location pin on it. This gives my users a completely wrong impression, especially for a device/app where we promise users not to track their location. They actually see a map with their location pinned on it, implying something that would freak out anyone who was expecting no tracking. I understand why an authorization is normally required, but since all we are getting is our own IoT's SSID, there should be no need for an authorization for this, and no map associated with the request. Again, they are accessing my IoT's network, NOT their home/location Wi-Fi SSID. My app already knows and specifies that network, and all I am trying to do is to work around a bug that makes it look like I have a successful return from NEHotspotConfigurationManager.shared.apply() when in fact the network I was looking for wasn't even on.

  2. Not only do I get instances where the network doesn't connect, and result codes show no errors, but I also get instances where I get an alert that says that the network is unreachable, yet my IoT shows that the app is connected to its Wi-Fi. On the iOS device, I go to the Wi-Fi settings, and see that I am on the IoT's network. So basically, sometimes I connect, but the frameworks says that there is no connection, and sometimes it reports a connection when there is none.

  3. As you can see in the code, I call cleanupHotspot() to make the iOS device get off of my temp Wi-Fi SSID. This is the code:

func cleanupHotspot() {
   NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: ssid)
    }

That code gets called by the above code when things aren't as I expect and need to cleanup. And I also call it when the user dismisses the viewcontroller that is attempting to make the connection.

It doesn't always work. I get stuck on the tempo SSID, unless I go through this whole thing again: try to make the connection again, this time it succeeds quickly, and then I can disconnect.

Any ideas?

I'm on iOS18.5, and have tried this on multiple iPhones including 11, 13 and 16.

Lemme start you out with Working with a Wi-Fi Accessory. That defines a set of accessory categories. Which category does your accessory fall in to?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

NEHotspotNetwork headaches
 
 
Q