6

How to detect if the user has granted the local network permission in the app for iOS 14? I have to show an error screen if the user has denied permission and redirects to os settings to grant permission. Has Apple provided any way to find out just like location permission?

riyaz
  • 514
  • 4
  • 17
  • 2
    Looking at bunch of threads over in the [Apple Dev Forums](https://developer.apple.com/forums/thread/654916) there does not seem to be a way to do that currently. – Gereon Sep 27 '20 at 16:55
  • It seems like NO api now. We also encounter this issue for IoT devices. – AechoLiu Nov 11 '20 at 02:45
  • I've have a similar question, but I've made some progress in finding a solution. My post is [Bonjour trouble using iOS 15, detecting Local Network setting](https://stackoverflow.com/q/71109322/236415) There appears to be an indirect way to detect the setting, but I need help there. – Jeff Feb 15 '22 at 07:02

1 Answers1

1

I wrote up this class that can be used if you're not on iOS 14.2.

  • This class will prompt user for permission to access local network (first time).
  • Verify existing permission state if already denied/granted.

Just remember this instance has to be kept alive so if you are using this in a function call within another class you need to keep the instance alive outside of the scope of the calling function.

import UIKit
import Network

class LocalNetworkPermissionChecker {
    private var host: String
    private var port: UInt16
    private var checkPermissionStatus: DispatchWorkItem?
    
    private lazy var detectDeclineTimer: Timer? = Timer.scheduledTimer(
        withTimeInterval: .zero,
        repeats: false,
        block: { [weak self] _ in
            guard let checkPermissionStatus = self?.checkPermissionStatus else { return }
            DispatchQueue.main.asyncAfter(deadline: .now(), execute: checkPermissionStatus)
        })
    
    init(host: String, port: UInt16, granted: @escaping () -> Void, failure: @escaping (Error?) -> Void) {
        self.host = host
        self.port = port
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(applicationIsInBackground),
            name: UIApplication.willResignActiveNotification,
            object: nil)
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(applicationIsInForeground),
            name: UIApplication.didBecomeActiveNotification,
            object: nil)
        
        actionRequestNetworkPermissions(granted: granted, failure: failure)
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    /// Creating a network connection prompts the user for permission to access the local network. We do not have the need to actually send anything over the connection.
    /// - Note: The user will only be prompted once for permission to access the local network. The first time they do this the app will be placed in the background while
    /// the user is being prompted. We check for this to occur. If it does we invalidate our timer and allow the user to make a selection. When the app returns to the foreground
    /// verify what they selected. If this is not the first time they are on this screen, the timer will not be invalidated and we will check the dispatchWorkItem block to see what
    /// their selection was previously.
    /// - Parameters:
    ///   - granted: Informs application that user has provided us with local network permission.
    ///   - failure: Something went awry.
    private func actionRequestNetworkPermissions(granted: @escaping () -> Void, failure: @escaping (Error?) -> Void) {
        guard let port = NWEndpoint.Port(rawValue: port) else { return }
        
        let connection = NWConnection(host: NWEndpoint.Host(host), port: port, using: .udp)
        connection.start(queue: .main)
        
        checkPermissionStatus = DispatchWorkItem(block: { [weak self] in
            if connection.state == .ready {
                self?.detectDeclineTimer?.invalidate()
                granted()
            } else {
                failure(nil)
            }
        })
        
        detectDeclineTimer?.fireDate = Date() + 1
    }
    
    /// Permission prompt will throw the application in to the background and invalidate the timer.
    @objc private func applicationIsInBackground() {
        detectDeclineTimer?.invalidate()
    }
    
    /// - Important: DispatchWorkItem must be called after 1sec otherwise we are calling before the user state is updated.
    @objc private func applicationIsInForeground() {
        guard let checkPermissionStatus = checkPermissionStatus else { return }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: checkPermissionStatus)
    }
}

//Can be used like this:
LocalNetworkPermissionChecker(host: "255.255.255.255", port: 4567, granted: {
    //Perform some action here...
},
failure: { error in
    if let error = error {
        print("Failed with error: \(error.localizedDescription)")
    }
})

Michael Wells
  • 586
  • 6
  • 14