Implementing AR in SwiftUI… without Storyboards
AR is usually implemented with Storyboards, but it doesn’t have to be. This tutorial is for my fellow devs who want to pair SwiftUI with AR, without the hassle of Storyboards.
Let’s Start
Go to your existing SwiftUI project; for this demo, I created a new Single View SwiftUI project.
Good news! For this integration, there’s no messing with AppDelegate.swift
or SceneDelegate.swift
required. The only files we’ll be looking at are ContentView.swift
and ARView.swift
(which you’ll create next).
Add the AR View
Create your ARView.swift file as a regular Swift file.
ARView.swift is a blank slate. We need to import ARKit and declare the ARView class with UIViewController and ARSCNViewDelegate protocols.
import Foundation
import ARKitclass ARView: UIViewController, ARSCNViewDelegate {}
Now we need to actually create an ARView. This is usually where an AR project using storyboards has an IBAction outlet.
Instead of using this, we’ll add the following code (bolded):
import Foundation
import ARKitclass ARView: UIViewController, ARSCNViewDelegate { var arView: ARSCNView {
return self.view as! ARSCNView
} override func loadView() {
self.view = ARSCNView(frame: .zero)
}}
Now let’s load, assign a delegate, and create a scene for this new arView
.
import Foundation
import ARKitclass ARView: UIViewController, ARSCNViewDelegate {
var arView: ARSCNView {
return self.view as! ARSCNView
} override func loadView() {
self.view = ARSCNView(frame: .zero)
} override func viewDidLoad() {
super.viewDidLoad()
arView.delegate = self
arView.scene = SCNScene()
}}
Next, we’ll add the final bits to our ARView
class; these are standard AR view and session handling functions.
import Foundation
import ARKitclass ARView: UIViewController, ARSCNViewDelegate {
var arView: ARSCNView {
return self.view as! ARSCNView
} override func loadView() {
self.view = ARSCNView(frame: .zero)
} override func viewDidLoad() {
super.viewDidLoad()
arView.delegate = self
arView.scene = SCNScene()
} // MARK: - Functions for standard AR view handling
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
} override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
} override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
arView.session.run(configuration)
arView.delegate = self
} override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
arView.session.pause()
} // MARK: - ARSCNViewDelegate
func sessionWasInterrupted(_ session: ARSession) {}
func sessionInterruptionEnded(_ session: ARSession) {} func session(_ session: ARSession, didFailWithError error: Error)
{} func session(_ session: ARSession, cameraDidChangeTrackingState
camera: ARCamera) {}}
At this point, make sure to add camera access to the Info.plist
, otherwise your AR view won’t be allowed to present and cause a nasty SIGABRT error.
Tie it Together
To transition between this ARView
and SwiftUI, we need to add some ViewIndicators to ARView.swift
and Content.swift
.
Starting with ARView.swift
, add the following above the ARView
class declaration.
... //other imports
import SwiftUI// MARK: - ARViewIndicator
struct ARViewIndicator: UIViewControllerRepresentable {
typealias UIViewControllerType = ARView
func makeUIViewController(context: Context) -> ARView {
return ARView()
} func updateUIViewController(_ uiViewController:
ARViewIndicator.UIViewControllerType, context:
UIViewControllerRepresentableContext<ARViewIndicator>) { }
}... //ARView class declaration and rest of code
Switching over to ContentView.swift, you’ll want to add a similar ViewIndicator but with a different name.
// MARK: - NavigationIndicatorstruct NavigationIndicator: UIViewControllerRepresentable {
typealias UIViewControllerType = ARView func makeUIViewController(context: Context) -> ARView {
return ARView()
} func updateUIViewController(_ uiViewController:
NavigationIndicator.UIViewControllerType, context:
UIViewControllerRepresentableContext<NavigationIndicator>) { }
}... //ContentView Struct
Final Touches
So we have a Storyboard-free ARView class and ViewIndicators in ARView.swift
and ContentView.swift
that seamlessly transition between the two views.
We still need a way to get to the ARView from the ContentView home screen though. Let’s add a stack structure and a couple buttons to ContentView.swift
to prompt the ARView; this method of calling the views will be the same even in a more complex context.
... //NavigationIndicatorstruct ContentView: View {
@State var page = "Home"
var body: some View {
VStack {
if page == "Home" {
Button("Switch to ARView") {
self.page = "ARView"
}
} else if page == "ARView" {
ZStack {
NavigationIndicator()
VStack {
Spacer()
Spacer()
Button("Home") {
self.page = "Home"
}.padding()
.background(RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color.white).opacity(0.7))
}
}
}
}
}
}
Running the project should show something like this:
Check out the full code on Github.