如何在iPad上用SwiftUI编写一个简单的AR程序

文章目录

前期准备

本次分享的是如何在iPad上进行AR程序的编写!

本次使用到的工具是iPad Air第五代,软件是Swift Playground 4.2.1版本。

废话不多说 先来看看我们的实现效果吧:

演示视频

可以看到我们本次要做的是通过点击屏幕添加AR物体到现实世界中。

代码实现

一、创建项目

打开在iPad上打开Swift Playground软件,点开左下角的App,这样就可以构建一个我们的swiftpm文件了!(Swift Student Challenge也是提交这个文件类型)

左边的窗口是文件目录区,中间的是代码区,右边的是预览区,我们编写的程序会在右边实时渲染出来的,方便预览调试。

二、实现逻辑

罗列清单

本次这个程序实现的逻辑如下所示

  1. 实现ARView的调用
  2. 点击屏幕将屏幕中的二维坐标换为三维坐标(即获取AR世界中的三维坐标)
  3. 添加物体的方法实现
  4. 代码完善

我们将要实现的步骤以注释的方式写入代码区中

import SwiftUI

struct ContentView: View {
    var body: some View {
      VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world")
        }
    }
}

//实现ARView的调用
//实现点击屏幕方法的编写
//添加AR物体
//代码完善

1、实现ARView的调用

在这里Swift UI中的每个View我们可以理解为一个个组件,将这些组件构建好了之后可以直接用在我们的ContentView中。

实现ARView的调用,我们需要引入RealityKitARKit这两个框架

具体代码如下

import SwiftUI
import RealityKit
import ARKit

struct ContentView: View {
    var body: some View {
       return ARViewContainer()
        
    }
}

//实现ARView的调用
struct ARViewContainer: UIViewRepresentable {
    
    typealias UIViewType = ARView
    
    func makeUIView(context: Context) -> ARView {
         let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)

        arView.enableTapGesture()
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {
        
    } 
}

这里按照这样填写后还是会出现报错,但是我们先别急,不用担心,后面会解决这些报错的。现在来解释一下类型吧

UIViewRepresentable:UIKit视图的包装器,你用来将视图集成到你的SwiftUI视图层次结构中。创建和更新过程与SwiftUI视图的行为并行,

就是说我们用UIKit写的一些UIView可以通过这个包装成SwiftUI可以调用的类型。

这里我们调用了arView中的enableTapGesture()方法,该方法是点击事件的方法,下面我们就来完善一下这个方法

2、实现点击事件的撰写

我们要实现的是点击屏幕并且能够实现将模型添加到我们的现实世界中,那么首先就是一个坐标的转换过程

extension ARView {
  //实现点击事件
    func enableTapGesture() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
        
        self.addGestureRecognizer(tapGestureRecognizer)
    }
    
   @objc func handleTap(recognizer: UITapGestureRecognizer) {
        //获取Tap的位置
       let tapLocation = recognizer.location(in: self)
       
       //确保有触碰的物体
       guard let rayResult = self.ray(through: tapLocation) else { return }
       
       let results = self.scene.raycast(origin: rayResult.origin, direction: rayResult.direction)
       
       if let firstResult = results.first {
           //模型的堆叠
           
       } else {
           let results = self.raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .any)
           
           if let firstResult = results.first {
               let position = simd_make_float3(firstResult.worldTransform.columns.3)
               placeCube(at: position)
           }
       }
    }
    
  //实现模型的添加
    
}

通过对ARView进行拓展,我们创建了点击方法。该方法实现的是单击屏幕能够能够将我们的坐标转变成AR世界中的坐标。为此我们用了ray方法来**计算AR场景中与屏幕上的点对应的射线。以及使用ARView中scene属性中的raycast方法来实现确保能够点击到AR物体**

3、AR物体的添加

现在我们能够通过屏幕点击到AR世界了,现在就差最后一步,就能实现我们的AR交互小功能啦!

我们在“实现模型的添加”这个注释下面添加如下代码:

 func placeCube(at  position: SIMD3<Float>) {
        let mesh = MeshResource.generateBox(size: 0.3)
        
        let material = SimpleMaterial(color: UIColor.randomColor(), roughness: 0.3, isMetallic: true)
        
        let modelEntity = ModelEntity(mesh: mesh, materials: [material])
        
        modelEntity.generateCollisionShapes(recursive: true)
        
        let anchorEntity = AnchorEntity(world: position)
        
        anchorEntity.addChild(modelEntity)
        
        self.scene.addAnchor(anchorEntity)
        
    }
    

extension UIColor {
    class func randomColor() -> UIColor {
        let colors: [UIColor] = [.white,.red,.blue,.yellow,.orange,.green]
        let randomIndex = Int(arc4random_uniform(UInt32(colors.count)))
        
        return colors[randomIndex]
    }
}

这里我们通过MeshResource这个类中的generateBox方法来创建的AR Box物体;然后通过调用SimpleMaterial这个结构体来为我们的Box贴上基础材质

之后将我们创建的物体放入到ModelEntity装载到模型的容器中;

之后通过AnchorEntity确定添加到AR世界中的锚点位置

最后通过再通过下面这两个代码,将我们创建的模型添加到AR世界中

anchorEntity.addChild(modelEntity)
        
self.scene.addAnchor(anchorEntity)

注解:

MeshResource:表示用于渲染的网格资源

SimpleMaterial:定义渲染网格的基本材质

ModelEntity表示呈现和可选模拟的模型

AnchorEntity表示可以在AR场景中锚定的实体。

4、代码完善

最后我们将物体叠加那块儿的代码进行完善:

 var position = firstResult.position
           //这里设置0.3的原因是因为我们之前设置box的size为0.3,保证能够堆叠到上一个物体的上面
 position.y += 0.3 / 2
           
 placeCube(at: position)

到此为止我们的程序就写得差不多了。可以运行一下程序测试一下吧!

希望大家可以通过这个基础小教程,将这个程序进行二创,做出更多好玩的东西来!

最终全部代码展示

##
import SwiftUI
import RealityKit
import ARKit


struct ContentView: View {
    var body: some View {
       return ARViewContainer()
        
    }
}


struct ARViewContainer: UIViewRepresentable {

    typealias UIViewType = ARView

    func makeUIView(context: Context) -> ARView {
        let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)

        arView.enableTapGesture()
        return arView
    }

    func updateUIView(_ uiView: ARView, context: Context) {

    }

}


//拓展方法
extension ARView {
    func enableTapGesture() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
        
        self.addGestureRecognizer(tapGestureRecognizer)
    }
    
   @objc func handleTap(recognizer: UITapGestureRecognizer) {
        //获取Tap的位置
       let tapLocation = recognizer.location(in: self)
       
       //确保有触碰的物体
       guard let rayResult = self.ray(through: tapLocation) else { return }
       
       let results = self.scene.raycast(origin: rayResult.origin, direction: rayResult.direction)
       
       if let firstResult = results.first {
           //物体堆叠
           var position = firstResult.position
           
           position.y += 0.3 / 2
           
           placeCube(at: position)
           
       } else {
           let results = self.raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .any)
           
           if let firstResult = results.first {
               let position = simd_make_float3(firstResult.worldTransform.columns.3)
               placeCube(at: position)
           }
       }
    }
    
    func placeCube(at  position: SIMD3<Float>) {
        let mesh = MeshResource.generateBox(size: 0.3)
        
        let material = SimpleMaterial(color: UIColor.randomColor(), roughness: 0.3, isMetallic: true)
        
        let modelEntity = ModelEntity(mesh: mesh, materials: [material])
        
        modelEntity.generateCollisionShapes(recursive: true)
        
        let anchorEntity = AnchorEntity(world: position)
        
        anchorEntity.addChild(modelEntity)
        
        self.scene.addAnchor(anchorEntity)
        
    }
}

extension UIColor {
    class func randomColor() -> UIColor {
        let colors: [UIColor] = [.white,.red,.blue,.yellow,.orange,.green]
        let randomIndex = Int(arc4random_uniform(UInt32(colors.count)))
        
        return colors[randomIndex]
    }
}

参考资料: https://www.youtube.com/watch?v=mPJiRtNzIHw&t=1s

5 个赞

照着做了一遍,蛮简单的