了解Nearby Interaction

了解Nearby Interaction,第1张

iOS14苹果推出了NearbyInteraction 框架, 用于感知和连接具有U1芯片的设备。其主要目的是空间感知(近距离定位)

1.Nearby Interaction 主要提供了两种信息, 距离(Distance)和方位(Direction)。 当两个设备通过Nearby Interaction 互相连接时, 他们会不断发送距离和方位信息, 这样就能互相定位了。 并且同一个设备能够和周围的多个设备建立连接,互不干扰

1),导入框架

import NearbyInteraction

2),创建session

let session = NISession()

// token

print(session.discoveryToken)

session有一个discoveryToken 属性, 两台设备互相交换 token 才可以进行连接。 创建session后,我们读取token, 然后通过MultipeerConnectivity 框架进行token的交换。

3). 一个完整的流程启用session

       let session = NISession()

        session.delegate = self

        let myDiscoveryToken = session.discoveryToken

        

        // 发送token到需要连接的设备上

        sendDiscoverTokenToMyPeer(peer, myDiscoveryToken)

        

        // 使用接收到的token

        let peerDiscoverToken = "xxxxx"

        

        // 配置

        let config = NINearbyPeerConfiguration(peerToken: peerDiscoverToken)

        

        // 启动session

        session.run(config)

3. Delegate回调

数据更新都是通过此方法回调

    optional func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject])

2. 当设备没有一段时间没有更新或者对方断开连接会收到此回调,需要注意的是这个回调并不是肯定会被调用,系统只会尽可能进行通知

optional func session(_ session: NISession, didRemove nearbyObjects: [NINearbyObject], reason: NINearbyObject.RemovalReason)

3. 生命周期相关回调

// 比如app进入后台,这个方法会回调

optional func sessionWasSuspended(_ session: NISession)

// 我们只有等此方法回调了才能继续使用session

optional func sessionSuspensionEnded(_ session: NISession)

// 此方法回调后我们只能重新创建session进行连接

optional func session(_ session: NISession, didInvalidateWith error: Error)

当连接多个设备的时候,我们需要为每一个连接的设备创建session

4. 了解NINearbyObject

open class NINearbyObject : NSObject, NSCopying, NSSecureCoding {

    // token

    @NSCopying open var discoveryToken: NIDiscoveryToken { get }

    // 距离单位是米

    public var distance: Float? { get }

    // 方位

    public var direction: simd_float3? { get }

}

5. 交互范围

检测距离大约是一个锥形,在这个方位内距离和方位都能正常更新。 超过方位距离会更新,但是方位可能不会更新。 然后我们需要确保手机是竖向的。

ViewController.swift

//

//  ViewController.swift

//  NearbyDemo

//

//  Created by dwladmin on 2022/3/22.

//

import UIKit

import NearbyInteraction

import os.log

enum MessageId: UInt8 {

    // Messages from the accessory.

    case accessoryConfigurationData = 0x1

    case accessoryUwbDidStart = 0x2

    case accessoryUwbDidStop = 0x3

    

    // Messages to the accessory.

    case initialize = 0xA

    case configureAndStart = 0xB

    case stop = 0xC

}

class ViewController: UIViewController {

    @IBOutlet weak var connectionStateLabel: UILabel!

    @IBOutlet weak var uwbStateLabel: UILabel!

    @IBOutlet weak var infoLabel: UILabel!

    @IBOutlet weak var distanceLabel: UILabel!

    @IBOutlet weak var actionButton: UIButton!

    var niSession = NISession?.none

    var dataChannel = DataCommunicationChannel()

    var configuration: NINearbyAccessoryConfiguration?

    var accessoryMap = [NIDiscoveryToken: String]()

    var accessoryConnected = false

    var connectedAccessoryName: String?

    let logger = os.Logger(subsystem: "com.nearby.demo.NearbyDemo", category: "ViewController")

    override func viewDidLoad() {

        super.viewDidLoad()

        dataChannel = DataCommunicationChannel()

        niSession?.delegate = self

//        print("uuidString:",UIDevice.current.identifierForVendor!.uuidString)

        //准备好数据通信通道。

        dataChannel.accessoryConnectedHandler = accessoryConnected

        dataChannel.accessoryDisconnectedHandler = accessoryDisconnected

        dataChannel.accessoryDataHandler = accessorySharedData

        dataChannel.start()

        

        updateInfoLabel(with: "Scanning for accessories")

        // Do any additional setup after loading the view.

    }

    @IBAction func buttonAction(_ sender: Any) {

        updateInfoLabel(with: "Requesting configuration data from accessory")

        let msg = Data([MessageId.initialize.rawValue])

        sendDataToAccessory(msg)

    }

  

    func accessorySharedData(data: Data, accessoryName: String) {

        // The accessory begins each message with an identifier byte.

        // Ensure the message length is within a valid range.

        if data.count < 1 {

            updateInfoLabel(with: "Accessory shared data length was less than 1.")

            return

        }

        

        // Assign the first byte which is the message identifier.

        guard let messageId = MessageId(rawValue: data.first!) else {

            fatalError("\(data.first!) is not a valid MessageId.")

        }

        

        // Handle the data portion of the message based on the message identifier.

        switch messageId {

        case .accessoryConfigurationData:

            // Access the message data by skipping the message identifier.

            assert(data.count > 1)

            let message = data.advanced(by: 1)

            setupAccessory(message, name: accessoryName)

        case .accessoryUwbDidStart:

            handleAccessoryUwbDidStart()

        case .accessoryUwbDidStop:

            handleAccessoryUwbDidStop()

        case .configureAndStart:

            fatalError("Accessory should not send 'configureAndStart'.")

        case .initialize:

            fatalError("Accessory should not send 'initialize'.")

        case .stop:

            fatalError("Accessory should not send 'stop'.")

        }

    }

    

    func accessoryConnected(name: String) {

        accessoryConnected = true

        connectedAccessoryName = name

        actionButton.isEnabled = true

        connectionStateLabel.text = "Connected"

        updateInfoLabel(with: "Connected to '\(name)'")

    }

    

    func accessoryDisconnected() {

        accessoryConnected = false

        actionButton.isEnabled = false

        connectedAccessoryName = nil

        connectionStateLabel.text = "Not Connected"

        updateInfoLabel(with: "Accessory disconnected")

    }

    

    // MARK: - Accessory messages handling

    

    func setupAccessory(_ configData: Data, name: String) {

        updateInfoLabel(with: "Received configuration data from '\(name)'. Running session.")

        do {

            configuration = try NINearbyAccessoryConfiguration(data: configData)

        } catch {

            // Stop and display the issue because the incoming data is invalid.

            // In your app, debug the accessory data to ensure an expected

            // format.

            updateInfoLabel(with: "Failed to create NINearbyAccessoryConfiguration for '\(name)'. Error: \(error)")

            return

        }

        

        // Cache the token to correlate updates with this accessory.

        cacheToken(configuration!.accessoryDiscoveryToken, accessoryName: name)

        niSession!.run(configuration!)

    }

    

    func handleAccessoryUwbDidStart() {

        updateInfoLabel(with: "Accessory session started.")

        actionButton.isEnabled = false

        self.uwbStateLabel.text = "ON"

    }

    

    func handleAccessoryUwbDidStop() {

        updateInfoLabel(with: "Accessory session stopped.")

        if accessoryConnected {

            actionButton.isEnabled = true

        }

        self.uwbStateLabel.text = "OFF"

    }

}

extension ViewController {

//    func xpc_connection_set_target_queue(connection: xpc_connection_t,

//                                         targetq: DispatchQueue?){

//

//    }

   

    func updateInfoLabel(with text: String) {

        self.infoLabel.text = text

        self.distanceLabel.sizeToFit()

        logger.info("\(text)")

    }

    func sendDataToAccessory(_ data: Data) {

        do {

            try dataChannel.sendData(data)

        } catch {

            updateInfoLabel(with: "Failed to send data to accessory: \(error)")

        }

    }

    func shouldRetry(_ accessory: NINearbyObject) -> Bool {

        if accessoryConnected {

            return true

        }

        return false

    }

    

    func cacheToken(_ token: NIDiscoveryToken, accessoryName: String) {

        accessoryMap[token] = accessoryName

    }

    func handleSessionInvalidation() {

        updateInfoLabel(with: "Session invalidated. Restarting.")

        // Ask the accessory to stop.

        sendDataToAccessory(Data([MessageId.stop.rawValue]))

        // Replace the invalidated session with a new one.

        self.niSession = NISession()

        self.niSession?.delegate = self

        // Ask the accessory to stop.

        sendDataToAccessory(Data([MessageId.initialize.rawValue]))

    }

    func handleUserDidNotAllow() {

        // Beginning in iOS 15, persistent access state in Settings.

        updateInfoLabel(with: "Nearby Interactions access required. You can change access for NIAccessory in Settings.")

        

        // Create an alert to request the user go to Settings.

        let accessAlert = UIAlertController(title: "Access Required",

                                            message: """

                                            NIAccessory requires access to Nearby Interactions for this sample app.

                                            Use this string to explain to users which functionality will be enabled if they change

                                            Nearby Interactions access in Settings.

                                            """,

                                            preferredStyle: .alert)

        accessAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

        accessAlert.addAction(UIAlertAction(title: "Go to Settings", style: .default, handler: {_ in

            // Navigate the user to the app's settings.

            if let settingsURL = URL(string: UIApplication.openSettingsURLString) {

                UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)

            }

        }))

        // Preset the access alert.

        present(accessAlert, animated: true, completion: nil)

    }

    

}

extension ViewController: NISessionDelegate {

    func session(_ session: NISession, didGenerateShareableConfigurationData shareableConfigurationData: Data, for object: NINearbyObject) {

        guard object.discoveryToken == configuration?.accessoryDiscoveryToken else { return }

        // Prepare to send a message to the accessory.

        var msg = Data([MessageId.configureAndStart.rawValue])

        msg.append(shareableConfigurationData)

        let str = msg.map { String(format: "0x%02x, ", $0) }.joined()

        logger.info("Sending shareable configuration bytes: \(str)")

        let accessoryName = accessoryMap[object.discoveryToken] ?? "Unknown"

        // Send the message to the accessory.

        sendDataToAccessory(msg)

        updateInfoLabel(with: "Sent shareable configuration data to '\(accessoryName)'.")

    }

    func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject]) {

        guard let accessory = nearbyObjects.first else { return }

        guard let distance = accessory.distance else { return }

        guard let name = accessoryMap[accessory.discoveryToken] else { return }

        self.distanceLabel.text = String(format: "'%@' is %0.1f meters away", name, distance)

        self.distanceLabel.sizeToFit()

    }

    func session(_ session: NISession, didRemove nearbyObjects: [NINearbyObject], reason: NINearbyObject.RemovalReason) {

        // Retry the session only if the peer timed out.

        guard reason == .timeout else { return }

        updateInfoLabel(with: "Session with '\(self.connectedAccessoryName ?? "accessory")' timed out.")

        // The session runs with one accessory.

        guard let accessory = nearbyObjects.first else { return }

        // Clear the app's accessory state.

        accessoryMap.removeValue(forKey: accessory.discoveryToken)

        // Consult helper function to decide whether or not to retry.

        if shouldRetry(accessory) {

            sendDataToAccessory(Data([MessageId.stop.rawValue]))

            sendDataToAccessory(Data([MessageId.initialize.rawValue]))

        }

    }

    func sessionWasSuspended(_ session: NISession) {

        updateInfoLabel(with: "Session was suspended.")

        let msg = Data([MessageId.stop.rawValue])

        sendDataToAccessory(msg)

    }

    func sessionSuspensionEnded(_ session: NISession) {

        updateInfoLabel(with: "Session suspension ended.")

        // When suspension ends, restart the configuration procedure with the accessory.

        let msg = Data([MessageId.initialize.rawValue])

        sendDataToAccessory(msg)

    }

    func session(_ session: NISession, didInvalidateWith error: Error) {

        switch error {

        case NIError.invalidConfiguration:

            // Debug the accessory data to ensure an expected format.

            updateInfoLabel(with: "The accessory configuration data is invalid. Please debug it and try again.")

        case NIError.userDidNotAllow:

            handleUserDidNotAllow()

        default:

            handleSessionInvalidation()

        }

    }

}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/990808.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存