Sharing Data With Handoff in iOS 8 and Xamarin

Handoff is a feature in iOS 8 that really gets me excited, it is a feature that gives a user the feeling that something magical just happened. Without any effort on the users part state is passed from one device to another enabling them to carry on where they left off. If i did not know any better i would honestly believe this could only be attributed to pure magic. Well, we do know better so let’s get onto how we build this into our own Xamarin apps.

Project Setup

To begin we need to add a new entry into the info.plist, they key is NSUserActivityTypes with and array as the value. you can the add a reverse domain url for each activity type you wish to be shared between apps.

Info Plist

Sharing Data

In our view controller we are now able to setup our state data which can be shared to other devices when in range. To do this we use the NSUserActivity class which is available through the UserActivity property on all UIResponder elements which most things in your view hierarchy derive from. We create the UserActivity we instantiate it by passing in the activity type like so:

public override void ViewDidLoad ()
{
   base.ViewDidLoad ();
			
   this.UserActivity = new NSUserActivity ("com.handOff.sample.text");
   this.UserActivity.Title = "Sample Text HandOff";

}

To set the state we wish to share we only have to override the UpdateUserActivityState method which again resides on UIResponder. This method is called periodically by the OS but can be requested by setting the NeedsSave flag to true on the UserActivity object. Ideally we should only update the state in this method as prior to being called the OS will clean out the UserInfo dictionary;

[Export ("textFieldDidEndEditing:")]
public void EditingEnded (MonoTouch.UIKit.UITextField textField)
{
   this.UserActivity.NeedsSave = true;
}

public override void UpdateUserActivityState (NSUserActivity activity)
{
   base.UpdateUserActivityState (activity);
   this.UserActivity.AddUserInfoEntries(new NSDictionary (new NSString("SharedText"), new NSString(this.SharedTextField.Text)));
}

Continuing An Activiy

In our AppDelegate we have two touch points to consider, overriding WillContinueUserActivity gives us the chance to decide if we are able to continue the incoming activity. If this is something the app can handle we can show the user a loading spinner while the activity is populated.

Secondly we must override ContinueUserActiviy, this gives us access to the fully populated user activity object and a completion handler which accepts an array of NSObject. We use this completion handler to pass in all of the UI elements or view controllers wish to handle the activity which has been passed. These will then have RestoreUserActivityState called on them.

AppDelegate

public override bool WillContinueUserActivity (UIApplication application, string userActivityType)
{
   //Decide if we can handle the activity type
   //Show the user a loading spinner
   return true;
}

public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
   completionHandler (new [] { this.Window.RootViewController });
   return true;
}

ViewController

public override void RestoreUserActivityState (NSUserActivity activity)
{
   base.RestoreUserActivityState (activity);
   this.SharedTextField.Text = ((NSString)activity.UserInfo ["SharedText"]);
}

As usual you can pick up the sample code on GitHub.

Have fun!

Advertisements

Having Fun With UIVisualEffectViews in iOS 8 & Xamarin

As you have probable seen by now, UIVisualEffectView in iOS 8 gives us an easy way to apply a blur or vibrancy effect to UI elements. While alone this effect is pretty cool, I wanted to see what we could do if we had a bit of fun with it.

Blurred MapView

The concept here is to force the focus on the location you wish the user to be looking. To do this we blur out the entire map but leave a circular transparent section in the centre. To do this we set up our view like this:


private UIVisualEffectView blurView;
private const int CIRCLE_RECT_SIZE = 250;

public override void ViewDidLoad ()
{
   base.ViewDidLoad ();
			
   this.blurView = new UIVisualEffectView (UIBlurEffect.FromStyle (UIBlurEffectStyle.Light));
   this.blurView.Frame = this.View.Frame;
   //Allows the map underneath to be interacted with
   this.blurView.UserInteractionEnabled = false;
   this.View.Add (this.blurView);

   //Create a masking layer to cut out a section of the blur
   var maskLayer = new CAShapeLayer ();
   var maskPath = new CGPath ();
   maskPath.AddRect (this.blurView.Bounds);
   maskPath.AddEllipseInRect (new RectangleF (((this.blurView.Bounds.Width - CIRCLE_RECT_SIZE) / 2),   ((this.blurView.Bounds.Height - CIRCLE_RECT_SIZE) / 2), CIRCLE_RECT_SIZE, CIRCLE_RECT_SIZE));
   maskLayer.Path = maskPath;
   maskLayer.FillRule = CAShapeLayer.FillRuleEvenOdd;
   this.blurView.Layer.Mask = maskLayer;
}

Effect Button

Following on with the circle theme, here is a button which contains a UIEffectView clipped to a circle. This gives it an interesting look different from a usual UIButton.


public CircularEffectButton (PointF location, float size, UIVisualEffect effect, UIColor backgroundColor, string title, UIColor titleColor)
: base(UIButtonType.Custom)
{
   this.Frame = new RectangleF(location, new SizeF(size, size));
   this.effectView = new UIVisualEffectView (effect);
   this.effectView.Frame = new RectangleF(new PointF(0,0), this.Frame.Size);
   
   //Allows for the button to be tappable
   this.effectView.UserInteractionEnabled = false;
   this.Add (this.effectView);
   
   //Set a background color with a small Alpha (gives a hint of color)
   this.BackgroundColor = backgroundColor.ColorWithAlpha(0.2f);
   this.SetTitle (title, UIControlState.Normal);
   
   //Get a color which contrast the background for the title text
   var contrastingColor = this.ContrasingColor (backgroundColor);
   this.SetTitleColor (contrastingColor, UIControlState.Normal);
   this.TitleLabel.AdjustsFontSizeToFitWidth = true;
   this.TitleLabel.TextAlignment = UITextAlignment.Center;
   this.Layer.CornerRadius = size / 2;
   this.Layer.BorderWidth = 1;
   this.Layer.BorderColor = contrastingColor.CGColor;
   this.ClipsToBounds = true;
}

Bringing this all together gives us something like this!



Obviously this amount of dynamic effects takes a significant amount of processing power. Always remember “WITH GREAT POWER THERE MUST ALSO COME–GREAT RESPONSIBILITY!”

Get the sample on GitHub here.

Custom Modal Transitions in iOS 8 & Xamarin

With the advent of iOS 8 and the inclusion of the UIPresentationController we now have a free reign in terms of presenting and transitioning to modal views in iOS.

To create a custom modal transition we are required to use 3 classes:

  • UIPresentationController
  • UIViewControllerAnimatedTransitioning
  • UIViewControllerAnimatedTransitioningDelegate

Apart from the excessively long names, these classes are really easy to use and setting up your awesome modal presentation transition will be a doddle.

UIPresentationController

This class is responsible for being the container of the view being presented, in my example we create a dimmed view to overlay the view being transitioned from. The required overriden methods / properties are:

  • FrameOfPresentedViewInContainerView: returns a frame which the presented view will be displayed in.
  • PresentationTransitionWillBegin: apply any visual configuration / animations to the container view before the presenting transition.
  • DissmissalTransitionWillBegin: apply any visual configuration / animations to the container view before the dismissing transition.
public override RectangleF FrameOfPresentedViewInContainerView {
   get {
           var containerBounds = this.ContainerView.Bounds;

           var presentedViewFrame = RectangleF.Empty;
           presentedViewFrame.Size = new SizeF (300, 300);
           presentedViewFrame.X = (containerBounds.Width / 2) - (presentedViewFrame.Width / 2);
           presentedViewFrame.Y = (containerBounds.Height / 2) - (presentedViewFrame.Height / 2);

           this.PresentedView.Layer.CornerRadius = presentedViewFrame.Size.Width / 2;
           this.PresentedView.ClipsToBounds = true;

           return presentedViewFrame;
	}
}

public override void PresentationTransitionWillBegin ()
{
   this.dimmingView.Frame = this.ContainerView.Bounds;
   this.dimmingView.Alpha = 0;

   this.ContainerView.InsertSubview (this.dimmingView, 0);
   var coordinator = this.PresentedViewController.GetTransitionCoordinator ();
   if (coordinator != null) {
      coordinator.AnimateAlongsideTransition((context) => 
      {
         this.dimmingView.Alpha = 1;
      }, 
      (context) => 
      {});
   } else {
      this.dimmingView.Alpha = 1;
   }
}

public override void DismissalTransitionWillBegin ()
{
   var coordinator = this.PresentedViewController.GetTransitionCoordinator ();
   if (coordinator != null) {
      coordinator.AnimateAlongsideTransition((context) => 
      {
         this.dimmingView.Alpha = 0;
      }, 
      (context) => {});
   } else {
      this.dimmingView.Alpha = 0;
   }
}

UIViewControllerAnimatedTransitioning

The main animation to display the presented view is handled here, there are only two required methods to override:

  • TransitionDuration: returns (as per the name) the duration of the transition based on the transitioning context.
  • AnimateTransition: create you animation transition between your two view controllers here.
public override double TransitionDuration (IUIViewControllerContextTransitioning transitionContext)
{
   return 0.5;
}

public override void AnimateTransition (IUIViewControllerContextTransitioning transitionContext)
{
   var fromVC = transitionContext.GetViewControllerForKey (UITransitionContext.FromViewControllerKey);
   var fromView = fromVC.View;
   var toVC = transitionContext.GetViewControllerForKey (UITransitionContext.ToViewControllerKey);
   var toView = toVC.View;
   var containerView = transitionContext.ContainerView;

   var isPresentation = this.IsPresentation;

   if (isPresentation) {
      containerView.AddSubview (toView);
   }

   var animatingVC = isPresentation ? toVC : fromVC;
   var animatingView = animatingVC.View;

   var appearedFrame = transitionContext.GetFinalFrameForViewController (animatingVC);
   var dismissedFrame = this.startFrame;

   var initialFrame = isPresentation ? dismissedFrame : appearedFrame;
   var finalFrame = isPresentation ? appearedFrame : dismissedFrame;
   animatingView.Frame = initialFrame;

   UIView.AnimateNotify (0.5f, 
      0, 
      300.0f, 
      5.0f, 
      UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState,
      () => { animatingView.Frame = finalFrame;	},  
      new UICompletionHandler((bool finished) => 
      {
         if(!isPresentation){
            fromView.RemoveFromSuperview();
         }
         transitionContext.CompleteTransition(true);
      }));
}

UIViewControllerAnimatedTransitioningDelegate

The glue that holds all of this awesomeness together, responsible for creating the UIPresentationController and marshalling the UIViewControllerAnimatedTransitioning instances for dismissal and presentation.

public override UIPresentationController GetPresentationControllerForPresentedViewController (UIViewController presentedViewController, UIViewController presentingViewController, UIViewController sourceViewController)
{
   if (this.awesomePresentationController == null) {
      this.awesomePresentationController = new AwesomePresentationController (presentedViewController, presentedViewController);
   }
   return this.awesomePresentationController;
}
			
public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForDismissedController (MonoTouch.UIKit.UIViewController dismissed)
{
   var transitioning = this.AnimationTransitioning;
   transitioning.IsPresentation = false;
   return transitioning;
}

public override IUIViewControllerAnimatedTransitioning PresentingController (UIViewController presented, UIViewController presenting, UIViewController source)
{
   var transitioning = this.AnimationTransitioning;
   transitioning.IsPresentation = true;
   return transitioning;
}

Implementing The Presentation

this is done in a few lines of code as follows:


partial void TransitionPressed (MonoTouch.Foundation.NSObject sender)
{
   var overlayVC = (OverlayViewController)this.Storyboard.InstantiateViewController("OverlayVC");

   this.transitioningDelegate = new AwesomeTransitioningDelegate (((UIButton)sender).Frame);

   overlayVC.ModalPresentationStyle = UIModalPresentationStyle.Custom;
   overlayVC.TransitioningDelegate = this.transitioningDelegate;

   this.PresentViewControllerAsync(overlayVC, true);
}

Get the sample on GitHub here.

Core Motion in iOS 8 and Xamarin

Core Motion is having an update with the introduction of iOS 8 and we get several new classes to play with.

CMPedometer

The first of these being CMPedometer, this looks to take the place of CMStepCounter which came about with iOS 7. What do we get over and above the old step counter? Here is the new data broken down:

  • Number of steps
  • Distance travelled
  • Speed (Based on calculating from distance over time)
  • Floors ascended (Requires information about the building to be available)
  • Floors descended (Requires information about the building to be available)

The implementation of this is really simple and looks a little like this:


public override async void ViewDidLoad ()
{
   base.ViewDidLoad ();

   this.pedometer = new CMPedometer ();
   this.pedometer.StartPedometerUpdates (new NSDate (), this.UpdatePedometerData);

   var data  = await this.pedometer.QueryPedometerDataAsync (DateTime.SpecifyKind (DateTime.Now.AddHours(-24), DateTimeKind.Utc), DateTime.Now);
   this.UpdatePedometerData(data, null);
}

private void UpdatePedometerData(CMPedometerData data, NSError error)
{
   if (error == null) {
      this.InvokeOnMainThread (() => {
         this.StepsLabel.Text = string.Format ("You have taken {0:0.00} steps", data.NumberOfSteps);
         this.DistanceLabel.Text = string.Format ("You have travelled {0:0.00} meters", data.Distance.DoubleValue);
         
         var endTime = DateTime.SpecifyKind(data.EndDate, DateTimeKind.Utc);
         var startTime = DateTime.SpecifyKind(data.StartDate , DateTimeKind.Utc);
         var time = endTime.Subtract(startTime);
         var speed = data.Distance.DoubleValue / time.TotalSeconds;
         this.SpeedLabel.Text = string.Format ("Your speed was {0:0.00} meters a second", speed);
         
         this.FloorsAscendedLabel.Text = string.Format("You have ascended {0} floors", data.FloorsAscended);
         this.FloorsDescendedLabel.Text = string.Format("You have descended {0} floors", data.FloorsDescended);
      });
   }
}

CMAltimeter

The second class brand new to iOS 8 is CMAltimeter which seems to point to the new hardware to be made available with the next iteration of the iPhone / iPad. The main function here is to get the relative altitude of the device but this is currently not supported on any available hardware.

Here is a look at the implementation though as it stands:


public override void ViewDidLoad ()
{
   base.ViewDidLoad ();
   if (CMAltimeter.IsRelativeAltitudeAvailable) {
      this.altimeter = new CMAltimeter ();
      this.altimeter.StartRelativeAltitudeUpdates (NSOperationQueue.MainQueue, this.UpdateAltitudeData);
   } else {
      this.AltitudeLabel.Text = "Your device does not have required hardware for altitude..";
   }
}

private void UpdateAltitudeData(CMAltitudeData data, NSError error)
{
   this.InvokeOnMainThread (() => {
      this.AltitudeLabel.Text = string.Format ("Your relative altitude is {0}", data.RelativeAltitude);
   });
}

I think just based on these 2 new classes we can tell the context is going to be huge moving forward, whether you think it is an invasion of privacy or an innovation that your mobile device knows how many flights of stair you have claimed that day is a question that only you can answer. All i can say is that as app developers we have lots of options moving forward.

Get the source over at GitHub.

Adding Touch ID Authentication in iOS 8

It has now become very easy to ensure the person using your app is the owner of the iOS device it is running on. To add this takes only a few lines of code.

TouchId

partial void LoginPressed (UIButton sender)
{
   var context = new LAContext ();

   var error = new NSError ();
   if (context.CanEvaluatePolicy (LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error)) {
      var replyHandler = new LAContextReplyHandler((success, err) => {
         this.InvokeOnMainThread(() => {
            if(success){
               Console.WriteLine("You Logged in");
            } else {
               var errorAlertView = new UIAlertView("Login Error", err.LocalizedDescription, null, "Close");
               errorAlertView.Show();
            }
         });
      });
      context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "You need to login", replyHandler);
   }
}

That’s all there is to it! The power and security of fingerprint authentication all wrapped up in a simple API.

you can grab the same project here