美文网首页使用SwiftUI开发一个APP
使用SwiftUI开发一个APP - 增加AR海报功能

使用SwiftUI开发一个APP - 增加AR海报功能

作者: LazyGunner | 来源:发表于2021-09-04 17:11 被阅读0次

上篇文章说道,审核被拒了,这两天就忙着把AR的功能加进去,再提交一版。中间还尝试了一下Facebook的Spark AR Studio,还有抖音的像塑,快手的Beyond Effect。三者都是社交媒体的特效制作工具,回头我再把其他几个的使用体验也都写成文章,敬请期待。

0. ARKit vs RealityKit

ARKit还是RealityKit 其实描述的不准确,其实应该在SceneKit 还是 RealityKit之间进行选择,当然我们会选择未来的版本即RealityKit。就因为是未来的版本,所以现在RealityKit的文章很少,文档也不是很健全,中间不免会踩不少的坑。没关系,早就踩坑踩习惯了!当然这里同时又激发了写RealityKit文档翻译的欲望。。

1. 通过UIViewRepresentable自定义View

首先创建一个文件PosterARView.swift

struct PosterARView: UIViewRepresentable { //1
    var arView: ARView! // 2
    var imageUrl: String = ""

    init(imageUrl url: String) {
        self.arView = ARView()  // 3
        self.arView.addCoaching()
        self.imageUrl = url
    }

    func makeCoordinator() -> ARViewCoordinator {
        return ARViewCoordinator(arView: self.arView, imageUrl: self.imageUrl)
    }  //4 

    func makeUIView(context: Context) -> ARView {  //5

        let arConfiguration = ARWorldTrackingConfiguration()
        arConfiguration.planeDetection = .vertical
        arView.session.run(arConfiguration)  // 6
        arView.session.delegate = context.coordinator // 7

        let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(sender:)))
        arView.addGestureRecognizer(tapGesture) // 8
        return arView
    }
    func updateUIView(_ uiView: UIViewType, context: Context) {

    }
}
  1. UIViewRepresentable是一个UIKit视图的包装器,用于将该视图集成到SwiftUI的视图层次中。(参考链接1)

  2. 我们在这里定义了一个ARView

  3. 初始化 ARView,并给这个ARView添加引导层

  4. coordinator比较关键,是ARView和包裹其的SwiftUI中View通讯的桥梁。我们在这里初始化了自定义的ARViewCoordinator,这个对象我会在后面介绍

  5. makeUIView 是 UIViewRepresentable协议必须要实现的的方法,我们在这个方法中初始化ARView,并返回ARView。

  6. 我们可以看到在这个项目中我初始化的AR配置是世界追踪,并设置了追踪的屏幕是垂直的,因为我只希望用户将海报贴在垂直的墙面上。

  7. 将ARView 中 session的delegate设置到context的coodinator,也就是 4 中返回的对象。

  8. 这里给ARView添加一个 点击手势识别的方法。 (参考链接2)

实现ARCoachingOverlayView

extension ARView: ARCoachingOverlayViewDelegate {

    func addCoaching() {

        let coachingOverlay = ARCoachingOverlayView()
        coachingOverlay.delegate = self
        coachingOverlay.session = self.session
        coachingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        coachingOverlay.goal = .anyPlane
        self.addSubview(coachingOverlay)
    }

    public func coachingOverlayViewDidDeactivate(_ coachingOverlayView: ARCoachingOverlayView) {
        //Ready to add entities next?

    }
}

这里通过 实现 addCoaching 方法来完成ARView的引导层设置,这里我们设置引导层结束并退出的目标是 识别到任意屏幕。

2. 定义一个Poster对象

class Poster: Entity, HasModel, HasAnchoring, HasCollision { // 1

    required init() {
        super.init()

    }

    convenience init(_ imageUrl: String) {
        self.init()
        let cache = Environment(\.imageCache).wrappedValue // 2
        var image: UIImage?

        // Create a temporary file URL to store the image at the remote URL.
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)

        image = cache[URL(string: imageUrl)!]

        let data = image?.pngData()
        if data == nil {
            return
        }

        // Write the image Data to the file URL.
        try! data!.write(to: fileURL) // 3

        let mesh = MeshResource.generatePlane(width: 0.4, height: 0.6) // 4
        // 不受AR环境中光的影响
        var material = UnlitMaterial()
        material.tintColor = UIColor.white.withAlphaComponent(1) // 5

        do {
            // Create a TextureResource by loading the contents of the file URL.
            let texture = try TextureResource.load(contentsOf: fileURL)
            material.baseColor = MaterialColorParameter.texture(texture)
            self.components[ModelComponent] = ModelComponent(
                mesh: mesh,
                materials: [material]
            ) // 6
        } catch {
            print(error.localizedDescription)
        }
    }

}
  1. 主要继承了Entity 和 HasAnchoring对象,用于后续将entity添加到session中,这里参考一下官方文档的图
image
  1. 这里我使用了前面文章提到的 AsyncImage 中定义的图片缓存,也就是图片下载过之后,在这里就不需要重复下载了,只需要根据URL去缓存中找图片对象就可以了。

  2. 这里需要将内存中的数据保存在磁盘上,才能供后续texture调用。

  3. 因为我们这里要将海报贴在墙上,所以要创建一个 plane类型的mesh,并设置mesh的宽和高为0.4米和0.6米。这里参数的单位就是“米”,因为这个单位,我还找了半天,最终在文档中找到了。(参考链接3)

  4. 如果使用参考链接中的方法使用 SimpleMaterial 会使得图片很暗,所以这里使用 UnlitMaterial 使得texture不受AR环境光影响

  5. 最终生成海报图片的Entity。参考链接4

3. 协调器-实现ARViewCoordinator

class ARViewCoordinator: NSObject, ARSessionDelegate {
    var arView: ARView
    var imageUrl: String = ""

    init(arView: ARView, imageUrl url: String) {
        self.arView = arView
        self.imageUrl = url

        super.init()
    }

    @objc func handleTap(sender: UITapGestureRecognizer) { // 1
        let tapLocation: CGPoint = sender.location(in: arView) // 2
        let estimatedPlane: ARRaycastQuery.Target = .existingPlaneInfinite
        let alignment: ARRaycastQuery.TargetAlignment = .vertical

        let result: [ARRaycastResult] = arView.raycast(from: tapLocation, allowing: estimatedPlane, alignment: alignment) // 3

        guard let rayCast: ARRaycastResult = result.first
        else {
            print("no result")
            return
        } // 4

        guard let planeAnchor = rayCast.anchor as? ARPlaneAnchor
        else {
            print("not a plane")
            return
        }

        if planeAnchor.alignment != .vertical {
            print("not a vertical plane")
            return
        }

        let poster = Poster(imageUrl) // 7

        let radians = -90.0 * Float.pi / 180.0

        // 根据raycast 设置位置
        poster.setTransformMatrix(rayCast.worldTransform, relativeTo: nil)
        // 根据X轴旋转 90°
        poster.transform.rotation *= simd_quatf(angle: radians, axis: SIMD3<Float>(1,0,0)) // 8
        // 在场景中添加该entity
        arView.scene.addAnchor(poster) // 9
    }

    func session(_ session: ARSession, didUpdate frame: ARFrame) {

    }
}
  1. 在上文我们提到了,为ARView添加手势,这里是手势action的处理函数

  2. 获取到点击手势的屏幕位置点

  3. 通过Reality Kit的raycast方法,获取屏幕点击位置发送射线所到达的真实世界的点。(参考链接5)

  4. 判断如果射线没有返回任何值,则返回。

  5. 判断如果射线到达的结果不是一个平面,则返回。

  6. 判断如果射线锁到达的平面不是垂直的,则返回。

  7. 通过从资源详情页带过来的imageUrl,创建海报对象,这个对象是一个Entity

  8. 当我们添加Entity到ARView的Scene中的时候,平面默认是面向下的,因此我们需要对屏幕进行翻转。这里同时可以看一下ARView中坐标系,如下图。(参考链接6)

image
  1. 最终将海报Entity添加到ARView的Scene中。

4. 创建SwiftUI View包裹 ARView

因为ARView是一个 UIViewRepresentable 的实现,无法直接继承到 SwiftUI 的 View中,因此我们需要先创建一个 View将其包裹起来,再供其他View使用

//
//  ResourceDetailPosterView.swift
//  FoloPro
//
//  Created by GUNNER on 2021/8/31.
//

import SwiftUI

struct ResourceDetailPosterView: View {
    var imageUrl: String = ""
    var body: some View {
        PosterARView(imageUrl: imageUrl).navigationTitle("AR海报")
    }
}

struct ResourceDetailPosterView_Previews: PreviewProvider {
    static var previews: some View {
        ResourceDetailPosterView(imageUrl: "https://cdn1.paranoidsqd.com/2021-08-11-151936.png")
    }
}

5. 在原有详情页跳转

我们在原有详情页的海报图片上包裹一层 NavigationLink 来实现 页面跳转

VStack (alignment:.center) {
            HStack(alignment:.top) {
                NavigationLink(
                    destination: ResourceDetailPosterView(imageUrl: resource.poster)) {

                AsyncImage(url: URL(string: resource.poster)!,
                           placeholder: { Text("Loading ...") },
                           image: {
                            Image(uiImage: $0).resizable()
                             })
                    .scaledToFit()
                    .frame(width: 156, height: 240)
                }
                ....

6. 再次被拒

心想这次增加了这个屌炸天的功能之后,不会再说我功能少了吧?结果还是被拒了,被拒的原因如下:

image

GuideLine 2.3 - Performance - Accurate Metadata

意思大概就是,在详情页里面提到了可以追剧,但是实际功能并不能追剧。所以要么增加追剧的功能,要么把详情页的介绍去掉。

OK,那我先简单点做,把详情页改一下吧。

image

Guideline 4.0 - Design

主要是提到了页面的UI的问题,有的页面太拥挤了,一些文字看不全。

我大概知道一些地方了,主要是类目页和详情页。那我就先改一版UI再提交吧。 等我的好消息!

参考链接:

  1. https://heartbeat.fritz.ai/introduction-to-realitykit-on-ios-entities-gestures-and-ray-casting-8f6633c11877

  2. https://betterprogramming.pub/how-to-use-uiviewrepresentable-in-swiftui-1b9a0a7c1358

  3. https://developer.apple.com/documentation/realitykit/meshresource/3244420-generateplane

  4. https://stackoverflow.com/questions/61854324/add-uiimage-as-texture-to-a-plane-in-realitykit

  5. https://developer.apple.com/documentation/realitykit/arview/3282007-raycast

  6. https://developer.apple.com/forums/thread/658620

相关文章

网友评论

    本文标题:使用SwiftUI开发一个APP - 增加AR海报功能

    本文链接:https://www.haomeiwen.com/subject/zzbrwltx.html