Part 3: ARKit Wall and Plane Detection for iOS 11.3

Place objects in the scene and orient them to an ARPlaneAnchor

In Part 1 of this series on ARKit wall and plane detection we ended with the ability to detect planes. (Final Part 1 Project File). And in Part 2 we added a grid to visualize the planes. (Final Part 2 Project File)

Now, in Part 3 we’re going to place an object at the plane’s position. When I pulled the master, I had some fun and added the ability to shoot a “blunt object” from your screen and have it bounce off the 3D objects ;)

Here’s what we’re building today.


As a note, if you’re starting with Part 3, download the starter project here.

The first thing we need to do is add a tap gesture to the view. In viewDidLoad() add this to the bottom.

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapScene(_:)))

Creating the action

Now let’s create our didTapScene(_:) action. Add this below viewDidLoad().

@objc func didTapScene(_ gesture: UITapGestureRecognizer) {
        switch gesture.state {
        case .ended:
            let location = gesture.location(ofTouch: 0,
                                            in: sceneView)

            let hit = sceneView.hitTest(location,
                                        types: .existingPlaneUsingGeometry)
            if let hit = hit.first {

1) First, convert the tap location to ARSCNView.

2) ARSCNView has a hitTest method that checks if a point in a scene intersects another object, defined by the types parameter. In this step we use .existingPlaneUsingGeometry because we are using planes that have already been found. Check out the other options of ARHitTestResult.ResultType as there are some neat things in there. For instance, estimatedVerticalPlane speeds up the placement of objects by not waiting for an ARPlaneAnchor to be found first. As planes are located, you can update the position of your objects to align more precisely.

3) Find the first plane that was tapped, and call placeBlockOnPlaneAt(_ hit: ARHitTestResult) passing in our ARHitTestResult.

Defining the method

Now we’re going to define that method. Add this below didTapScene.

func placeBlockOnPlaneAt(_ hit: ARHitTestResult) {
        let box = createBox()
        position(node: box, atHit: hit)

1) Here we need to call a helper method to create and position a 3D box node. We will define that later.

2) Add the box to the scene’s rootNode.

Defining the helper methods

This is where we define the helper methods.

private func createBox() -> SCNNode {
        let box = SCNBox(width: 0.15, height: 0.20, length: 0.02, chamferRadius: 0.02)
        let boxNode = SCNNode(geometry: box)

        return boxNode

Creating the box

Here we create a box and round its edges.

private func position(node: SCNNode, atHit hit: ARHitTestResult) {
        node.transform = SCNMatrix4(hit.anchor!.transform)
        node.eulerAngles = SCNVector3Make(node.eulerAngles.x + (Float.pi / 2), node.eulerAngles.y, node.eulerAngles.z)
        let position = SCNVector3Make(hit.worldTransform.columns.3.x + node.geometry!.boundingBox.min.z, hit.worldTransform.columns.3.y, hit.worldTransform.columns.3.z)
        node.position = position

1) Transform the box to match the ARPlaneAnchor’s transform.

2) Next we want to rotate our box so it lies long ways on top of our plane. Adjust its eulerAngles by rotating it 90 degrees along its x axis.

3) Now we need to figure out the position by using the ARHitTestResult’s columns property’. (Figuring out how to place something in a 3D world can be quite confusing!) And we just take into account the height of the node so it looks like it’s resting on top/in front of the surface.

Testing your app

Finally, run your app to see if you can tap on a plane and see a box plopped at that position.

If all is working correctly, you’ll notice that once you place an object, the plane itself will continue to adjust as it learns more about its surface, which may make your box object no longer look as its sitting directly on the plane.

The boxes are completely white, so let’s quickly add a light to the scene to give them some dimension. After that, we can call it good!

Open up ARSceneManager.swift and add self.sceneView?.autoenablesDefaultLighting = true inside attach(to sceneView: ARSCNView). Run again and you should see some definition to your boxes!

A look at the current version.


Photo of Ben Lambert

Ben is an iOS developer and designer. He has a passion for building rich and intuitive UI /UX experiences for mobile. In his free time, he enjoys creating mobile app games.


Add a Comment

Hmm...that didn't work.

Something went wrong while adding your comment. If you don't mind, please try submitting it again.

Comment Added!

Your comment has been added to this post. Please refresh this page to view it.

Optional. If added, we will display a link to the website in your comment.
Optional. Never shared or displayed in your comment.