Chapter 22: Shared AR Experiences
前言
這章的內容之前在hack day曾經玩過,就是多人分享AR體驗。以這個app為例,某個人在某地留個塗鴉,另外一個人可以在同個地方看到這個塗鴉。
大綱
Getting started
本章重點就是實作share button, 讓多人可以互動進行留下塗鴉在不同地方。 ------
Establishing a peer connection
The Multipeer Connectivity framework on iOS uses either Wi-Fi or Bluetooth to transport data.
建立連線有兩種方式
利用
MCNearbyServiceBrowser
,讓app可以邀請別人來加入一個session中。利用
MCBrowserViewController
,這個方式會提供內建原生UI,讓app尋找可用的session建立連線。(這一章是利用這個方式)
@IBAction func shareWorldMap(_ sender: Any) {
// 利用MCBrowserViewController尋找附近可用的session
let mcBrowserVC = MCBrowserViewController(serviceType: PeerSession.serviceType, session: peerSession.mcSession)
mcBrowserVC.delegate = self
self.present(mcBrowserVC, animated: true, completion: nil)
}
}
extension ViewController: MCBrowserViewControllerDelegate {
func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
browserViewController.dismiss(animated: true, completion: nil)
}
func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
browserViewController.dismiss(animated: true, completion: nil)
}
}
// MARK: - Shared AR
extension ViewController {
func handleReceivedData(_ data: Data, from peer: MCPeerID) {
}
}
MCAdvertiserAssistant
object to tell the peers that your app can join a multipeer session of a particular service type. The advertiserAssistant also provides a standard user interface for the local peer to accept or decline an invitation to connect to a session.
import MultipeerConnectivity
class PeerSession: NSObject, MCAdvertiserAssistantDelegate {
private let peerID = MCPeerID(displayName: UIDevice.current.name)
static let serviceType = "arsketchsession"
private(set) var mcSession: MCSession!
private var advertiserAssistant: MCAdvertiserAssistant!
private let receivedDataHandler: (Data, MCPeerID) -> Void
init(receivedDataHandler: @escaping (Data, MCPeerID) -> Void) {
// 在呼叫init之前,要把這個class中宣告let的參數都進行初始化
self.receivedDataHandler = receivedDataHandler
super.init()
mcSession = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
mcSession.delegate = self
// 透過這個MCAdvertiserAssistant進行連線處理
advertiserAssistant = MCAdvertiserAssistant(serviceType: PeerSession.serviceType, discoveryInfo: nil, session: self.mcSession)
advertiserAssistant.delegate = self
advertiserAssistant.start()
}
}
extension PeerSession: MCSessionDelegate {
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
}
// 目前這個app並不支援stream的資料傳輸,只支援data傳輸
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
fatalError("This service does not send/receive streams.")
}
// 目前這個app並不支援resource的資料傳輸,若有支援則progress會提供目前傳輸的進度
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
fatalError("This service does not send/receive resources.")
}
// 當支援resource的資料傳輸,且傳輸完畢,則會提供localURL告訴目前resource的位置
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
fatalError("This service does not send/receive resources.")
}
}
Sending data to peers
傳送資料
func sendToAllPeers(_ data: Data) {
do {
try mcSession.send(data, toPeers: mcSession.connectedPeers, with: .reliable)
} catch {
print("""
error sending data to peers:
\(error.localizedDescription)
""")
}
}
func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
sceneView.session.getCurrentWorldMap { (worldMap, error) in
guard let map = worldMap else {
print("Error: \(error!.localizedDescription)")
return
}
// 取得產生AR物件的環境截圖,這樣對方才知道在哪個地方會出現AR物件
guard let snapshotAnchor = SnapshotAnchor(capturing: self.sceneView) else {
fatalError("Can't take snapshot")
}
map.anchors.append(snapshotAnchor)
// 將資料進行encode
guard let data =
try? NSKeyedArchiver.archivedData(withRootObject: map,
requiringSecureCoding: true)else {
fatalError("can't encode map")
}
self.peerSession.sendToAllPeers(data)
}
browserViewController.dismiss(animated: true, completion: nil)
}
Receiving data and relocalizing
接收資料,這裡要注意的地方,須先取得worldmap才可以把ar object添加上去
extension ViewController {
func handleReceivedData(_ data: Data, from peer: MCPeerID) {
do {
// parse data
if let worldMap = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data) {
DispatchQueue.main.async {
// restore virtual content to the screen
self.displaySnapshotImage(from: worldMap)
}
// configure the AR session using the world map
configureARSession(for: worldMap)
// set mapProvider to remember the sender
mapProvider = peer
return
}
} catch {
print("can't decode data received from \(peer)")
}
// 表示之前已經有取得ARWorldMap
if !isRelocalizingMap {
do {
// 等待ARLineAnchor的資料
if let anchor = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARLineAnchor.self, from: data) {
sceneView.session.add(anchor: anchor)
}
} catch {
print("unknown data received from \(peer)")
}
}
}
}
PreviousChapter 21: World Tracking & Persistent AR ExperiencesNextSection VII: Image Tracking, Object Detection & AR Quick Look
Last updated
Was this helpful?