Because there is no API that directly returns your local network access state you can use next approach with publishing your Bonjour service and it returns the right result if access to local network was already set for your app (on app start e.g.). The approach causes the alert to appear as well but returns false
before you select any button so to get the right result you should put this check to applicationDidBecomeActive
and it will give the correct state after local network alert is disappeared and you return to your app.
class getLocalNetworkAccessState : NSObject {
var service: NetService
var denied: DispatchWorkItem?
var completion: ((Bool) -> Void)
@discardableResult
init(completion: @escaping (Bool) -> Void) {
self.completion = completion
service = NetService(domain: "local.", type:"_lnp._tcp.", name: "LocalNetworkPrivacy", port: 1100)
super.init()
denied = DispatchWorkItem {
self.completion(false)
self.service.stop()
self.denied = nil
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: denied!)
service.delegate = self
self.service.publish()
}
}
extension getLocalNetworkAccessState : NetServiceDelegate {
func netServiceDidPublish(_ sender: NetService) {
denied?.cancel()
denied = nil
completion(true)
}
func netService(_ sender: NetService, didNotPublish errorDict: [String : NSNumber]) {
print("Error: \(errorDict)")
}
}
How to use:
getLocalNetworkAccessState { granted in
print(granted ? "granted" : "denied")
}
NOTE: Don't forget to set NSLocalNetworkUsageDescription
and add "_lnp._tcp." to NSBonjourServices
in your Info.plist.
UPDATE
There is the second approach that works similar the code from above but can wait for an user's answer by checking an application state and then returns a valid access state for Local Network Privacy:
class LocalNetworkPrivacy : NSObject {
let service: NetService
var completion: ((Bool) -> Void)?
var timer: Timer?
var publishing = false
override init() {
service = .init(domain: "local.", type:"_lnp._tcp.", name: "LocalNetworkPrivacy", port: 1100)
super.init()
}
@objc
func checkAccessState(completion: @escaping (Bool) -> Void) {
self.completion = completion
timer = .scheduledTimer(withTimeInterval: 2, repeats: true, block: { timer in
guard UIApplication.shared.applicationState == .active else {
return
}
if self.publishing {
self.timer?.invalidate()
self.completion?(false)
}
else {
self.publishing = true
self.service.delegate = self
self.service.publish()
}
})
}
deinit {
service.stop()
}
}
extension LocalNetworkPrivacy : NetServiceDelegate {
func netServiceDidPublish(_ sender: NetService) {
timer?.invalidate()
completion?(true)
}
}
// How to use
LocalNetworkPrivacy().checkAccessState { granted in
print(granted)
}
ObjC
You can use swift code without rewriting to ObjC and to do that just add swift file to your project and call checkAccessState
directly (the function must be marked with @objc
):
#import "YourProjectName-Swift.h" // import swift classes to objc
...
LocalNetworkPrivacy *local = [LocalNetworkPrivacy new];
[local checkAccessStateWithCompletion:^(BOOL granted) {
NSLog(@"Granted: %@", granted ? @"yes" : @"no");
}];