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

上篇文章说道,审核被拒了,这两天就忙着把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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容

  • 之前的文章介绍了列表视图和无限加载瀑布流,今天的主要内容是讲在列表视图上方增加一个搜索输入框,并跳转到搜索页面完成...
    LazyGunner阅读 2,377评论 0 0
  • SwiftUI 是一种非常简单的创新方法,可以利用 Swift 的强大能力在所有苹果设备平台上构建用户界面。通过 ...
    腾飞Tenfay阅读 2,087评论 0 1
  • SwiftUI 是一种非常简单的创新方法,可以利用 Swift 的强大能力在所有苹果设备平台上构建用户界面。通过 ...
    王大妈啊阅读 82,575评论 26 51
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,536评论 28 53
  • 人工智能是什么?什么是人工智能?人工智能是未来发展的必然趋势吗?以后人工智能技术真的能达到电影里机器人的智能水平吗...
    ZLLZ阅读 3,778评论 0 5