Swift UIでSceneKitを使う

個人製作でSceneKitでARを実装することがあったのでメモります

SwiftUIをVIPERとCombineで書いていきます

## CALayerの表示

SceneKitのカメラからの入力を表示するためにCALayerを表示するための `UIViewControllerRepresentable` を用意します

```swift
import SwiftUI

struct CALayerView: UIViewControllerRepresentable {
    var caLayer: CALayer
    let frame: CGRect // frameの大きさ
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<CALayerView>) -> UIViewController {
        let viewController = UIViewController()
        
        viewController.view.layer.addSublayer(caLayer)
        caLayer.frame = frame
        
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<CALayerView>) {
        caLayer.frame = frame
    }
}
```



## セッションの操作

InteractorにSceneKitのセッションのセットアップなどのメソッドを定義します

```swift
import ARKit
import Foundation
import SceneKit
import SwiftUI

final class ArInteractor: NSObject, ARSCNViewDelegate {
    private let sceneView = ARSCNView()
    
    func setupAR() -> CALayer {
        sceneView.scene = SCNScene()
        sceneView.autoenablesDefaultLighting = true
        sceneView.delegate = self
        
        return sceneView.layer
    }
    
    func startSettion() {
        let configuration = ARWorldTrackingConfiguration()
      
      // SceneKitで3Dオブジェクトのdetectionをしたかったのでリソースを読み込ませる
        guard let detectionObjects = ARReferenceObject.referenceObjects(
            inGroupNamed: "AR Resources",
            bundle: nil
        ) else {
            fatalError("Missing expected asset catalog resources.")
        }
        configuration.detectionObjects = detectionObjects
        sceneView.session.run(configuration)
    }
    
    func stopSettion() {
        sceneView.session.pause()
    }
  
  	func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
      // この中で３Dオブジェクトを検知した際の処理を書く
    }
}
```



## ViewとSceneKitを繋ぐ

PresenterでViewとInteractorの繋ぎ込みをして、ViewからSceneKitを操作できるようにします。

```swift
import Combine
import Foundation
import SwiftUI

final class ArPresenter: ObservableObject {
    private let router = ArRouter()
    private let interactor = ArInteractor()
    
    @Published var previewLayer: CALayer {
        willSet {
            objectWillChange.send()
        }
    }
    
    init() {
        previewLayer = interactor.setupAR()
    }
  
    func onAppearArView() {
        interactor.startSettion()
    }
    
    func onDisappearArView() {
        interactor.stopSettion()
    }
}
```

## ARSCNViewを表示させる

最後にViewで先ほど作った`CALayerView`コンポーネントを使ってARSCNViewのCALayerを流し込んで表示させます

```swift
import SwiftUI

struct ArView: View {
    @ObservedObject var presenter: ArPresenter
    
    var body: some View {
        ZStack {
            VStack {
                ZStack {
                    CALayerView(
                        caLayer: presenter.previewLayer,
                        frame: CGRect(x: 0, y: 0, width: 300, height: 300)
                    )
                }
                .onAppear {
                    self.presenter.onAppearArView()
                }
                .onDisappear {
                    self.presenter.onDisappearArView()
                }
            }
        }
    }
}
```

これで表示されたと思います。