iOS 14 如何触发本地网络对话框并检查用户回答?

vpo*_*ave 20 networking ios swift

我看过这个 Q/A What triggers,但这不是我想要的。我也阅读了这个网络隐私权限检查,但没有答案。我还在此处搜索可以帮助我的任何方法或类:Network,但再次没有运气。

本地网络授权有一个新对话框,用户可以在其中允许/不允许“查找并连接到本地网络上的设备”。

在此处输入图片说明

但是我正在努力寻找任何 API 来如何触发此弹出窗口以及如何检查是否授予访问权限(例如,在 中AVCapture,我可以检查 的授权状态AVMediaType)。

谢谢!

小智 43

我找到了一种方法来触发提示,接收用户选择的回调,并检测用户之前是否允许或拒绝提示(如果提示已经出现)。为了触发权限,我们使用服务发现 API。当用户拒绝或之前拒绝时,我们会收到错误。它并不表明是否授予了权限,因此我们还发布了一个网络服务,如果授予了权限则返回成功。通过将两者组合成一个组件,我们可以触发提示并获得批准或拒绝的指示:在我们从网络服务收到成功或从服务发现收到错误之前,我们假设权限仍处于待处理状态。

import Foundation
import Network

@available(iOS 14.0, *)
public class LocalNetworkAuthorization: NSObject {
    private var browser: NWBrowser?
    private var netService: NetService?
    private var completion: ((Bool) -> Void)?
    
    public func requestAuthorization(completion: @escaping (Bool) -> Void) {
        self.completion = completion
        
        // Create parameters, and allow browsing over peer-to-peer link.
        let parameters = NWParameters()
        parameters.includePeerToPeer = true
        
        // Browse for a custom service type.
        let browser = NWBrowser(for: .bonjour(type: "_bonjour._tcp", domain: nil), using: parameters)
        self.browser = browser
        browser.stateUpdateHandler = { newState in
            switch newState {
            case .failed(let error):
                print(error.localizedDescription)
            case .ready, .cancelled:
                break
            case let .waiting(error):
                print("Local network permission has been denied: \(error)")
                self.reset()
                self.completion?(false)
            default:
                break
            }
        }
        
        self.netService = NetService(domain: "local.", type:"_lnp._tcp.", name: "LocalNetworkPrivacy", port: 1100)
        self.netService?.delegate = self
        
        self.browser?.start(queue: .main)
        self.netService?.publish()
    }
    
    private func reset() {
        self.browser?.cancel()
        self.browser = nil
        self.netService?.stop()
        self.netService = nil
    }
}

@available(iOS 14.0, *)
extension LocalNetworkAuthorization : NetServiceDelegate {
    public func netServiceDidPublish(_ sender: NetService) {
        self.reset()
        print("Local network permission has been granted")
        completion?(true)
    }
}

Run Code Online (Sandbox Code Playgroud)

如何使用:

  1. 将 LocalNetworkAuthorization 类添加到您的项目中
  2. 打开 .plist 文件并添加“_bonjour._tcp”、“_lnp._tcp.”作为“Bonjour services”下的值
  3. 调用 requestAuthorization() 触发提示或获取授权状态(如果已批准/拒绝)

  • 谢谢我能找到的唯一干净的答案。我添加了异步替代方案,并将其变成引用您的要点 https://gist.github.com/doozMen/0b5fc54c765bccb7c13792caa4eaa51c (3认同)

iUr*_*rii 11

由于没有直接返回本地网络访问状态的 API,因此您可以使用下一种方法来发布 Bonjour 服务,并且如果已经为您的应用程序设置了对本地网络的访问(例如在应用程序启动时),它会返回正确的结果。该方法也会导致警报出现,但false在您选择任何按钮之前返回,因此为了获得正确的结果,您应该进行此检查applicationDidBecomeActive,并且在本地网络警报消失并且您返回到应用程序后,它将给出正确的状态。

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)")
    }
}
Run Code Online (Sandbox Code Playgroud)

如何使用:

getLocalNetworkAccessState { granted in
    print(granted ? "granted" : "denied")
}
Run Code Online (Sandbox Code Playgroud)

注意:不要忘记设置NSLocalNetworkUsageDescription并添加“_lnp._tcp”。到NSBonjourServices您的 Info.plist 中。

更新

第二种方法的工作原理与上面的代码类似,但可以通过检查应用程序状态来等待用户的回答,然后返回本地网络隐私的有效访问状态:

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)
}
Run Code Online (Sandbox Code Playgroud)

对象C

您可以使用 swift 代码,而无需重写 ObjC,只需将 swift 文件添加到您的项目中并checkAccessState直接调用即可(该函数必须用 标记@objc):

#import "YourProjectName-Swift.h" // import swift classes to objc

...

LocalNetworkPrivacy *local = [LocalNetworkPrivacy new];
[local checkAccessStateWithCompletion:^(BOOL granted) {
    NSLog(@"Granted: %@", granted ? @"yes" : @"no");
}];
Run Code Online (Sandbox Code Playgroud)

  • @iUrii 为什么你选择使用 2 秒作为时间间隔? (2认同)

vpo*_*ave 8

我确实打开了 DTS 请求,并与 Apple 支持团队进行了转换。这是我在下面列出的一些重要部分。

如何检查是否授予访问权限

来自支持团队:

要知道,没有这样的 API 来检查用户权限。

来自支持团队:

如果用户拒绝,则连接失败。它究竟如何失败取决于您使用的网络 API 以及您如何使用该 API。

  • 默认情况下,连接将失败并显示NSURLErrorNotConnectedToInternet.
  • 如果你waitsForConnectivity在会话配置上设置,请求会等待事情的改善。在这种情况下,您将收到-URLSession:taskIsWaitingForConnectivity:委托回调来告诉您这一点。如果用户改变主意并启用本地网络访问,则连接将通过。

不幸的是,没有直接的方法来确定这种行为是本地网络隐私限制还是其他一些网络故障的结果。


如何触发此弹出窗口

来自支持团队:

这里的问题是本地网络权限警报是由传出流量触发的,您不会生成任何传出流量。解决此问题的唯一方法是生成一些虚拟传出流量以触发此警报。

我见过其他开发人员在这种情况下,没有直接的 API 来触发本地网络权限警报是很烦人的。我鼓励你提交一个关于这个的错误。

我一直在与本地网络隐私团队讨论这个问题,我们目前对您的情况下的应用程序(即想要接收广播但不发送任何本地网络流量的应用程序)的建议如下:

  • 系统应该更好地处理这个问题。我们正在将其作为错误进行跟踪rdar://problem/67975514。这在当前的 iOS 14.2b1 版本中并未修复,但您应该在 iOS 测试版种子发布后继续对其进行测试。

  • 同时,您可以通过发送消息来强制显示本地网络隐私警报。我们特别建议您发送与您尝试接收的消息大致相同的消息,因此在您的情况下,这意味着发送 IPv4 UDP 广播。

更新

对于iOS 14.2 - 收到入站流量提示已修复。因此,您不需要下面的示例来模拟流量以触发提示。


这是虚拟传出流量模拟的类: 示例

该流量永远不会离开 iOS 设备,因此,即使接口处于睡眠状态,也不会唤醒它。即使它确实唤醒了接口,其成本也微不足道,因为您不会一遍又一遍地这样做,只需一次即可触发本地网络隐私警报。


Rov*_*val 7

就我而言,它正在访问此变量以获取一些内部设备统计信息:

ProcessInfo.processInfo.hostName
Run Code Online (Sandbox Code Playgroud)

访问此变量会导致出现警报。如果它不涵盖您的情况,也许您可​​以搜索源代码以获取本地网络/主机周围的一些参考。

  • 这很好,但不幸的是它没有回调......但它确实会导致弹出窗口出现。 (3认同)

Sim*_*kup 5

Apple 已(2020 年 9 月下旬)发布了本地网络隐私常见问题解答,回答了这个问题,尽管似乎确实可能会做出进一步的更改以简化此操作。

\n

SwiftObjective-C代码示例,说明如何通过解决方法触发提示:

\n
\n

目前无法显式触发本地网络\n隐私警报 (r. 69157424)。但是,您可以通过向本地网络地址发送虚拟流量\n来隐式启动它。下面的代码\n显示了执行此操作的一种方法。它查找与支持广播的网络接口关联的所有 IPv4 和 IPv6 地址,并向每个地址发送 UDP 数据报。这应该会触发本地网络隐私\nalert,假设警报尚未为您的应用程序显示\xe2\x80\x99t。

\n
\n

至于如何检查结果,请留意此常见问题解答,其中显示:

\n
\n

如果您的目标是使用 NWConnection 连接到本地网络地址,则从 iOS 14.2 Beta 开始,您可以使用不满意原因属性。

\n
\n