ARKit 世界地图 – 如何使用自定义 UTI 文件类型保存/访问?

Ant*_*wis 5 xcode ios swift arkit arworldmap

我找不到任何专门列出 ARKit 将其世界地图保存为的文件类型的文档。据我所知,它是一个.arexperience文件。本质上,我正在尝试修改文档浏览器,以便能够有选择地选择.arexperience要加载的文件。

我尝试启用支持的文档类型,特别是public.arexperience将 LSHandlerRank 作为类型字符串和值作为替代,以及将 CFBundleTypeRole 作为类型字符串和值作为查看器。

我还在info.plist Additional document type properties中将 Supports Document Browser 设置为YES

9.7.18 编辑:我现在收到以下错误。对此有何想法?我已经清理了构建文件夹,关闭了 XCode,卸载并重新安装了项目,并重新启动了我的计算机,但没有任何变化。命令 CompileSwift 失败,退出代码非零

9.13.18 编辑:此错误是由全新安装 XCode 10 Beta(包括 Swift 4.2)和您的示例项目引起的。知道发生了什么吗?研究表明 SIGABRT 错误是未使用的插座的结果,但使用让我查看连接的助理编辑器并没有显示明显的问题......在此处输入图片说明

Bla*_*orz 2

这似乎是一个有趣的问题,所以我想我应该尝试回答并提供一个完整的示例,可以为您和其他人未来的开发奠定基础。

这可能不是正确的方法,但希望它会有用。

话虽如此,我并不完全确定为什么要使用文档浏览器来允许用户选择ARWorldMaps。一种更简单的方法可能是将它们简单地存储在 中CoreData,并允许在UITableView例如中进行选择。或者将下面的逻辑合并到类似的内容中,例如当打开自定义文件时,将其保存到CoreData,并以这种方式显示所有收到的文件。

不管怎样,这里有一些东西可以让你开始更详细地探索这个主题。虽然,请注意,这并不是优化的方式,尽管它应该足以为您指明正确的方向^______^。

供你参考:

ARWorldMap 符合 NSSecureCoding 协议,因此您可以使用 NSKeyedArchiver 和 NSKeyedUnarchiver 类将世界地图与二进制数据表示形式相互转换。

由于我们想使用 aCustom UTI来保存ARWorldMap我们首先需要在我们的文件中进行设置,info.plist并将UTI类型设置为public.data.

在此输入图像描述

在项目编辑器中看起来像这样:

在此输入图像描述

有关执行此操作的更多信息,请参阅Ray Wenderlich提供的一个很好的教程提供的一个很好的教程。

完成此操作后,我们当然需要保存ARWorldMap并允许其导出。我创建了一个我们保存数据的方式,例如(我们的)typealias的键值String和值:DataARWorldMap

typealias BMWorlMapItem = [String: Data]

/// Saves An ARWorldMap To The Documents Directory And Allows It To Be Sent As A Custom FileType
@IBAction func saveWorldMap(){

    //1. Attempt To Get The World Map From Our ARSession
    augmentedRealitySession.getCurrentWorldMap { worldMap, error in

        guard let mapToShare = worldMap else { print("Error: \(error!.localizedDescription)"); return }

        //2. We Have A Valid ARWorldMap So Save It To The Documents Directory
        guard let data = try? NSKeyedArchiver.archivedData(withRootObject: mapToShare, requiringSecureCoding: true) else { fatalError("Can't Encode Map") }

        do {

            //a. Create An Identifier For Our Map
            let mapIdentifier = "BlackMirrorzMap"

            //b. Create An Object To Save The Name And WorldMap
            var contentsToSave = BMWorlMapItem()

            //c. Get The Documents Directory
            let documentDirectory = try self.fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)

            //d. Create The File Name
            let savedFileURL = documentDirectory.appendingPathComponent("/\(mapIdentifier).bmarwp")

            //e. Set The Data & Save It To The Documents Directory
            contentsToSave[mapIdentifier] = data

            do{
                let archive = try NSKeyedArchiver.archivedData(withRootObject: contentsToSave, requiringSecureCoding: true)
                try archive.write(to: savedFileURL)

                //f. Show An Alert Controller To Share The Item
                let activityController = UIActivityViewController(activityItems: ["Check Out My Custom ARWorldMap", savedFileURL], applicationActivities: [])
                self.present(activityController, animated: true)

                print("Succesfully Saved Custom ARWorldMap")

            }catch{

                print("Error Generating WorldMap Object == \(error)")
            }

        } catch {

            print("Error Saving Custom WorldMap Object == \(error)")
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

这还将数据保存到Documents Directory用户设备上,以便我们可以检查一切是否按预期运行,例如:

在此输入图像描述

保存数据后,我们将向用户呈现一个,UIActivityAlertController以便用户可以将文件发送到他们的email等。

由于我们现在可以导出数据,因此当我们选择如何使用自定义处理程序打开数据时,我们需要处理如何接收数据:

在此输入图像描述

我们的处理方式AppDelegate如下:

//---------------------------
//MARK: - Custom File Sharing
//---------------------------

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

    //1. List Our Custom File Type Which Will Hold Our ARWorldMap
    guard url.pathExtension == "bmarwp" else { return false }

    //2. Post Our Data
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "MapReceived"), object: nil, userInfo: ["MapData" : url])

    return true
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,当我们的自定义文件通过发送的AppDelegateaNotification被接收时,我们将在 ViewController 中注册,viewDidLoad例如:

 NotificationCenter.default.addObserver(self, selector: #selector(importWorldMap(_:)), name: NSNotification.Name(rawValue: "MapReceived"), object: nil)
Run Code Online (Sandbox Code Playgroud)

现在我们已经完成了所有这些设置,我们当然需要提取数据以便可以使用它。这是这样实现的:

/// Imports A WorldMap From A Custom File Type
///
/// - Parameter notification: NSNotification)
@objc public func importWorldMap(_ notification: NSNotification){

    //1. Remove All Our Content From The Hierachy
    self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (existingNode, _) in existingNode.removeFromParentNode() }

    //2. Check That Our UserInfo Is A Valid URL
    if let url = notification.userInfo?["MapData"] as? URL{

        //3. Convert Our URL To Data
        do{
            let data = try Data(contentsOf: url)

            //4. Unarchive Our Data Which Is Of Type [String: Data] A.K.A BMWorlMapItem
            if let mapItem = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! BMWorlMapItem,
               let archiveName = mapItem.keys.first,
               let mapData = mapItem[archiveName] {

                //5. Get The Map Data & Log The Anchors To See If It Includes Our BMAnchor Which We Saved Earlier
                if  let unarchivedMap = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [ARWorldMap.classForKeyedUnarchiver()], from: mapData),
                    let worldMap = unarchivedMap as? ARWorldMap {

                    print("Extracted BMWorldMap Item Named = \(archiveName)")

                    worldMap.anchors.forEach { (anchor) in if let name = anchor.name { print ("Anchor Name == \(name)") } }

                    //5. Restart Our Session
                    let configuration = ARWorldTrackingConfiguration()
                    configuration.planeDetection = .horizontal
                    configuration.initialWorldMap = worldMap
                    self.augmentedRealityView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
                }

            }

        }catch{

            print("Error Extracting Data == \(error)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们的数据已提取,我们只需要重新配置我们的Session并加载地图即可。

您会注意到,我正在记录AnchorNames, 作为检查过程是否成功的一种方法,因为我创建了一个自定义ARAnchor名称BMAnchor,我使用UITapGestureRecognizer如下方式创建:

//------------------------
//MARK: - User Interaction
//------------------------

/// Allows The User To Create An ARAnchor
///
/// - Parameter gesture: UITapGestureRecognizer
@objc func placeAnchor(_ gesture: UITapGestureRecognizer){

    //1. Get The Current Touch Location
    let currentTouchLocation = gesture.location(in: self.augmentedRealityView)

    //2. Perform An ARSCNHiteTest For Any Feature Points
    guard let hitTest = self.augmentedRealityView.hitTest(currentTouchLocation, types: .featurePoint).first else { return }

    //3. Create Our Anchor & Add It To The Scene
    let validAnchor = ARAnchor(name: "BMAnchor", transform: hitTest.worldTransform)
    self.augmentedRealitySession.add(anchor: validAnchor)

}
Run Code Online (Sandbox Code Playgroud)

提取后,我会通过以下方式生成一个模型,这ARSCNViewDelegate对于检查我们的过程是否成功非常有用:

//-------------------------
//MARK: - ARSCNViewDelegate
//-------------------------

extension ViewController: ARSCNViewDelegate{

    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {

        //1. Check We Have Our BMAnchor
        if let name = anchor.name, name == "BMAnchor" {

            //2. Create Our Model Node & Add It To The Hierachy
            let modelNode = SCNNode()

            guard let sceneURL = SCNScene(named: "art.scnassets/wavingState.dae") else { return nil }

            for childNode in sceneURL.rootNode.childNodes { modelNode.addChildNode(childNode) }

            return modelNode

        }else{

            return SCNNode()
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

希望这能为您指明正确的方向......

这是一个完整的工作示例,供您和其他人尝试并适应您的需求:共享 ARWorldMaps

您需要做的就是等待会话开始运行,放置模型,然后按“保存”。当出现警报时,通过电子邮件将其发送给您自己,然后检查您的电子邮件,然后单击.bmarwp文件,该文件将在应用程序中自动加载 ^_________^,

在一个Document Based App,您可以非常轻松地使用自定义文件类型。

以下是您的 info.plist 的要求:

(a) 文件类型:

在此输入图像描述

(b) 导出型尿路感染:

在此输入图像描述

项目的信息部分如下所示:

在此输入图像描述

因此,您最终会看到不同的屏幕,如下所示:

在此输入图像描述

它既适用于我创建的基于基本文档的应用程序,也适用于Apple Files

希望能帮助到你...