Implementing AR in SwiftUI… without Storyboards

Madison Ostermann
Dev Genius
Published in
4 min readJun 13, 2020

--

Photo by freestocks on Unsplash

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.

Create a Single View App.
Choose SwiftUI for User Interface.

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.

Create ARView.swift as a regular swift file.
Overview of all files in project.

ARView.swift is a blank slate. We need to import ARKit and declare the ARView class with UIViewController and ARSCNViewDelegate protocols.

import Foundation
import ARKit
class 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 ARKit
class 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 ARKit
class 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 ARKit
class 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.

Allow camera access.

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.

Enjoy! Follow for more articles & tutorials on iOS dev, anything Swift, and general programming!

--

--