Share on Twitter Share on Facebook Share on LinkedIn



Gesture Recognisers give us the ability to interact with our virtual objects in different ways such as moving them (pan), rotating them or scaling them (pinch).

There are a number of built in gestures we can make use of:

  • UITapGestureRecognizer
  • UIPinchGestureRecognizer
  • UIRotationGestureRecognizer
  • UISwipeGestureRecognizer
  • UIPanGestureRecognizer
  • UIScreenEdgePanGestureRecognizer
  • UILongPressGestureRecognizer

In this lesson we will only use UIPanGestureRecognizer, UIPinchGestureRecognizer and UIRotationGestureRecognizer.

I have really struggled to find Xamarin C# implementations using these in ARKit so I am pleased to show you the code below that I have translated from swift.

One caveat with these gestures is that they can only detect movement in 2 dimensions (as your device screen is only 2D so therefore you can only move your finger across the screen in 2 dimensions).
Therefore whilst it is possible to pan an object up and down (y axis) and left and right (x axis) we cannot detect a pan gesture forwards and backwards (z axis).

However there may be times when we may want this restricted movement in 2 dimensions, for example moving something on a plane/surface like a table or a wall.

Whilst the gestures/interactions in the video don't seem remarkable, they allow us to do some more advanced things later.



using System;
using System.Linq;
using ARKit;
using SceneKit;
using UIKit;

namespace XamarinArkitSample
    public partial class ViewController : UIViewController
        private readonly ARSCNView sceneView;

        public ViewController(IntPtr handle) : base(handle)
            this.sceneView = new ARSCNView
                AutoenablesDefaultLighting = true


        public override void ViewDidLoad()

            this.sceneView.Frame = this.View.Frame;

        public override void ViewDidAppear(bool animated)

            var configuration = new ARWorldTrackingConfiguration
                AutoFocusEnabled = true,
                PlaneDetection = ARPlaneDetection.Horizontal,
                LightEstimationEnabled = true,
                WorldAlignment = ARWorldAlignment.Gravity,

            this.sceneView.Session.Run(configuration, ARSessionRunOptions.ResetTracking | ARSessionRunOptions.RemoveExistingAnchors);

            var redMaterial = new SCNMaterial();
            redMaterial.Diffuse.Contents = UIColor.Red;

            var greenMaterial = new SCNMaterial();
            greenMaterial.Diffuse.Contents = UIColor.Green;

            var yellowMaterial = new SCNMaterial();
            yellowMaterial.Diffuse.Contents = UIColor.Yellow;

            var cubeNode = new SCNNode();
            cubeNode.Geometry = SCNBox.Create(0.1f, 0.2f, 0.2f, 0);
            cubeNode.Geometry.Materials = new[] { redMaterial };
            cubeNode.Position = new SCNVector3(0,-0.3f,-0.5f);
            cubeNode.Opacity = 0.9f;

            var cylinderNode = new SCNNode();
            cylinderNode.Geometry = SCNCylinder.Create(0.1f, 0.2f);
            cylinderNode.Geometry.Materials = new[] { greenMaterial };
            cylinderNode.Position = new SCNVector3(-0.5f, -0.3f, -0.5f);
            cylinderNode.Opacity = 0.9f;

            var pyramidNode = new SCNNode();
            pyramidNode.Geometry = SCNPyramid.Create(0.1f, 0.2f, 0.2f);
            pyramidNode.Geometry.Materials = new[] { yellowMaterial };
            pyramidNode.Position = new SCNVector3(0.5f, -0.3f, -0.5f);
            pyramidNode.Opacity = 0.9f;

            var panGesture = new UIPanGestureRecognizer(HandlePanGesture);

            var rotateGesture = new UIRotationGestureRecognizer(HandleRotateGesture);

            var pinchGesture = new UIPinchGestureRecognizer(HandlePinchGesture);


        float currentAngleZ;
        float newAngleZ;

        private void HandlePinchGesture(UIPinchGestureRecognizer sender)
            var areaPinched = sender.View as SCNView;
            var location = sender.LocationInView(areaPinched);
            var hitTestResults = areaPinched.HitTest(location, new SCNHitTestOptions());

            var hitTest = hitTestResults.FirstOrDefault();

            if (hitTest == null)

            var node = hitTest.Node;

            var scaleX = (float)sender.Scale * node.Scale.X;
            var scaleY = (float)sender.Scale * node.Scale.Y;
            var scaleZ = (float)sender.Scale * node.Scale.Z;

            node.Scale = new SCNVector3(scaleX, scaleY, scaleZ);

            sender.Scale = 1;


        private void HandleRotateGesture(UIRotationGestureRecognizer sender)
            var areaTouched = sender.View as SCNView;
            var location = sender.LocationInView(areaTouched);
            var hitTestResults = areaTouched.HitTest(location, new SCNHitTestOptions());

            var hitTest = hitTestResults.FirstOrDefault();

            if (hitTest == null)

            var node = hitTest.Node;

            newAngleZ = (float)(-sender.Rotation);
            newAngleZ += currentAngleZ;
            node.EulerAngles = new SCNVector3(node.EulerAngles.X, node.EulerAngles.Y, newAngleZ);

        private void HandlePanGesture(UIPanGestureRecognizer sender)
            var areaPanned = sender.View as SCNView;
            var location = sender.LocationInView(areaPanned);
            var hitTestResults = areaPanned.HitTest(location, new SCNHitTestOptions());

            var hitTest = hitTestResults.FirstOrDefault();

            if (hitTest == null)

            var node = hitTest.Node;

            if (sender.State == UIGestureRecognizerState.Changed)
                var translate = sender.TranslationInView(areaPanned);

                // Only allow movement vertically or horizontally
                node.LocalTranslate(new SCNVector3((float)translate.X / 10000f, (float)-translate.Y / 10000, 0.0f));

        public override void ViewDidDisappear(bool animated)


Next Step : Virtual table items

After you have mastered this you should try Virtual table items