Xamarin.iOS Google Maps Directions

In an earlier post I showed how to use the new MKDirections API in iOS 7 to display directions. This post will show how to use Google’s Directions API to plot directions based on locations tapped by the user. Using Google maps for directions can be beneficial for those who are located where Apple directions are unavailable.

The source code for this example is available on GitHub.

First, you’ll need to generate a new API key. Instructions on getting started as well as generating a new API key can be found here .

Next, you will need to add a reference to googlemaps. I’ve found that the easiest way to do this is through the component store.

We’ll also be using jSON.net to parse the route data. This can be found in the component store as well.

Be sure and set the identifier under the iOS Application tab in the project properties. This should be the same name you used when generating the API key.

In your AppDelegate you will need to provide your API key to the Map Services.

MapServices.ProvideAPIKey(ApiKey);

This example has a single view that is a map. The map has one button to clear the contents of the map (Markers and Polylines), and one button to zoom to the users current location.

The following is added to the ViewDidLoad of the main view controller.

base.ViewDidLoad();
View.Frame = UIScreen.MainScreen.Bounds;
 View.BackgroundColor = UIColor.White;
 View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
SetupMap();
//Setup button to clear map
 var btn = new UIButton(new RectangleF(0, 0, 100, 50));
 btn.Layer.BorderWidth = 1;
 btn.Layer.CornerRadius = 5;
 btn.Layer.BorderColor = UIColor.Black.CGColor;
 btn.SetTitle(@"Clear Map", UIControlState.Normal);
 btn.BackgroundColor = UIColor.Gray;
 btn.TouchUpInside += (s, e) =>
 {
 _mapView.Clear();
 _mapDelegate.Lines.Clear();
 _mapDelegate.Locations.Clear();
 };

 _mapView.AddSubview(btn);

And the SetupMap Method…

private void SetupMap()
 {
//Init Map wiht Camera
 var camera = new CameraPosition(new CLLocationCoordinate2D(36.069082, -94.155976), 15, 30, 0);
 _mapView = MapView.FromCamera(RectangleF.Empty, camera);
 _mapView.MyLocationEnabled = true;
//Add button to zoom to location
 _mapView.Settings.MyLocationButton = true;
_mapView.MyLocationEnabled = true;
 _mapView.MapType = MapViewType.Hybrid;
 _mapView.Settings.SetAllGesturesEnabled(true);
//Init MapDelegate
 _mapDelegate = new MapDelegate(_mapView);
 _mapView.Delegate = _mapDelegate;
View = _mapView;
 }

_mapDelegate is an instance of a class that inherits from MapViewDelegate. We’ve called our class MapDelegate. This class will override the DidTapAtCoordinate method. In this method we will add a new marker to the map. If there are at least two markers we will begin building a url to get directions from the Directions API.

public override void DidTapAtCoordinate(MapView mapView, CLLocationCoordinate2D coordinate)
{
//Create/Add Marker 
 var marker = new Marker { Position = coordinate, Map = mapView };
 Locations.Add(coordinate);
 if (Locations.Count > 1)
 {
   SetDirectionsQuery();
 }
}

Our example uses the first tap as the origin, the second as the destination, and everything in between is a waypoint. We request that google returns the directions in jSON format. I’ve created the classes below to represent the route data that’s returned.

public class Northeast
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Southwest
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Bounds
{
 public Northeast northeast { get; set; }
 public Southwest southwest { get; set; }
}
public class Distance
{
 public string text { get; set; }
 public int value { get; set; }
}
public class Duration
{
 public string text { get; set; }
 public int value { get; set; }
}
public class EndLocation
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class StartLocation
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Distance2
{
 public string text { get; set; }
 public int value { get; set; }
}
public class Duration2
{
 public string text { get; set; }
 public int value { get; set; }
}
public class EndLocation2
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Polyline
{
 public string points { get; set; }
}
public class StartLocation2
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Step
{
 public Distance2 distance { get; set; }
 public Duration2 duration { get; set; }
 public EndLocation2 end_location { get; set; }
 public string html_instructions { get; set; }
 public Polyline polyline { get; set; }
 public StartLocation2 start_location { get; set; }
 public string travel_mode { get; set; }
 public string maneuver { get; set; }
}
public class Leg
{
 public Distance distance { get; set; }
 public Duration duration { get; set; }
 public string end_address { get; set; }
 public EndLocation end_location { get; set; }
 public string start_address { get; set; }
 public StartLocation start_location { get; set; }
 public List<Step> steps { get; set; }
 public List<object> via_waypoint { get; set; }
}
public class OverviewPolyline
{
 public string points { get; set; }
}
public class Route
{
 public Bounds bounds { get; set; }
 public string copyrights { get; set; }
 public List<Leg> legs { get; set; }
 public OverviewPolyline overview_polyline { get; set; }
 public string summary { get; set; }
 public List<object> warnings { get; set; }
 public List<object> waypoint_order { get; set; }
}
public class RootObject
{
 public List<Route> routes { get; set; }
 public string status { get; set; }
}

The SetDirectionsQuery method is where we will spin through the locations associated with each marker, and begin building our URL. We then Deserialize the returned string into an object. We’ll spin through the collection of routes returned, and for each route use the points property in overview_polyline on each route to produce a path, which is then used to create a polyline. The polylines map property is set, and this places it on the map.

private async void SetDirectionsQuery()
 {
 //Clear Old Polylines
 if (Lines.Count > 0)
 {
 foreach (var line in Lines)
 {
 line.Map = null;
 }
 Lines.Clear();
 }
//Start building Directions URL
 var sb = new System.Text.StringBuilder();
 sb.Append(KMdDirectionsUrl);
 sb.Append(Locations[0].Latitude.ToString(CultureInfo.InvariantCulture));
 sb.Append(",");
 sb.Append(Locations[0].Longitude.ToString(CultureInfo.InvariantCulture));
 sb.Append("&");
 sb.Append("destination=");
 sb.Append(Locations[1].Latitude.ToString(CultureInfo.InvariantCulture));
 sb.Append(",");
 sb.Append(Locations[1].Longitude.ToString(CultureInfo.InvariantCulture));
 sb.Append("&sensor=true");
//If we have more than 2 locations we'll append waypoints
 if (Locations.Count > 2)
 {
 sb.Append("&waypoints=");
 for (var i = 2; i < Locations.Count; i++)
 {
 if (i > 2)
 sb.Append("|");
 sb.Append(Locations[i].Latitude.ToString(CultureInfo.InvariantCulture));
 sb.Append(",");
 sb.Append(Locations[i].Longitude.ToString(CultureInfo.InvariantCulture));
 }
 }
//Get directions through Google Web Service
 var directionsTask = GetDirections(sb.ToString());
var jSonData = await directionsTask;
//Deserialize string to object
 var routes = JsonConvert.DeserializeObject<RootObject>(jSonData);
foreach (var route in routes.routes)
 {
 //Encode path from polyline passed back
 var path = Path.FromEncodedPath(route.overview_polyline.points);
//Create line from Path
 var line = Google.Maps.Polyline.FromPath(path);
 line.StrokeWidth = 10f;
 line.StrokeColor = UIColor.Red;
 line.Geodesic = true;
//Place line on map
 line.Map = _map;
 Lines.Add(line);
}
}
private async Task<String> GetDirections(string url)
 {
 var client = new WebClient();
 var directionsTask = client.DownloadStringTaskAsync(url);
 var directions = await directionsTask;
return directions;
}
Google Maps Directions

Google Maps Directions

Advertisements

Single Row GridView using MvvmCross, Infragistics NucliOS, and Xamarin.iOS

MvvmCross is a fantastic framework create by Stuart Lodge. It’s my go-to framework for cross platform development, and works great for any app, regardless of size. Here is a link to the projects GitHub page. Stuart has an excellent series (N+1) on his YouTube Channel as well.

The following example will demonstrate how to create a Core portable project that contains our Models, and ViewModels, as well as an iOS project that uses an Infragistics Grid. MvvmCross is extremely flexible, and allows us to create and register custom bindings, so we can create our own ‘magic’. The source code for this example can be found on GitHub.

Step 1.) Create a new C# PCL Project. We’ll be displaying a collection of Monkey Images, so we’ll name our solution MonkeysList. Since this is the Core project, we’ll call it MonkeysList.Core.

Create new PCL Project

Step 2.) Choose your target platforms for the PCL project. We’ll chose to target Windows Phone 7.5 and higher, Mono for Android, and Monotouch.

2

Step 3.)  Add MvvmCross References. One of the easiest ways to do this is by using Nuget. Search for MvvmCross, and select MvvmCross (It should be the first one in the list).

3

Step 4.) Create Model. Adding the Nuget Packages generates some free code for us! The first thing we’ll do is delete the class.cs file that was created when the PCL project was first created. Next create a folder named Models, and a class Named Monkey, that has a single string type property called ImageUrl.

public class Monkey
{
    public string ImageUrl { get; set; }
}

Step 5.) Create a service that generates a Monkey. This is trivial. First, we need to create a folder called Services. The first that goes in this folder is an Interface, with a single method that creates a Monkey. Next, we need to create a class that Implements this implements this interface.

public interface IMonkeyGenerator
{
     Monkey GenerateMonkey();
}
public class MonkeyGeneratorService : IMonkeyGenerator
{
   public Monkey GenerateMonkey()
   {
     return new Monkey {ImageUrl = string.Format("http://placemonkey.heroku.com/{0}/{0}", Random(20) + 500)};
   }private readonly Random _random = new Random();
 protected int Random(int count)
 {
   return _random.Next(count);
 }
}

Step 6.) Add Monkeys to ViewModel. One of the items created when installing the Nuget packages is FirstViewModel. This comes with an example property. We’ll delete the example, and new list of Monkey. Next, we’ll use MvvmCross to resolve our MonkeyGeneratorService, and use it to create our list of Monkeys. This is done in the class constructor.

public class FirstViewModel 
 : MvxViewModel
 {
 public FirstViewModel()
 {
  //Resolve IMonkeyGenerator from Ioc
  var svc = Mvx.Resolve<IMonkeyGenerator>();
  var monkeys = new List<Monkey>();
 for (int i = 0; i < 20; i++)
 {
   //Generate Monkeys!
   monkeys.Add(svc.GenerateMonkey());
 }
  Monkeys = monkeys;
}
 private List<Monkey> _monkeys;
 public List<Monkey> Monkeys
 {
  get { return _monkeys; }
  set
  {
   _monkeys = value;
   RaisePropertyChanged(() => Monkeys);
  }
 } 
}

That’s it for the PCL project! Below is a picture of what all you should have so far.

4

Step 7.) Create a new Empty iOS project named MonkeysList.Touch
Step 8.) Use Nuget to add MvvmCross to iOS project.
Step 9.) Add a reference to your Core project.
Step 9.) Follow the instructions in ToDo-MvvmCross folder, and replace AppDelegate.cs with the following Code.

 [Register ("AppDelegate")]
 public partial class AppDelegate : MvxApplicationDelegate
 {
   UIWindow _window;
   public override bool FinishedLaunching(UIApplication app, NSDictionary options)
  {
     _window = new UIWindow(UIScreen.MainScreen.Bounds);
     var setup = new Setup(this, _window);
     setup.Initialize();
     var startup = Mvx.Resolve<IMvxAppStart>();
     startup.Start();
    _window.MakeKeyAndVisible();
    return true;
  }
}

Step 11.) Create GridView using Infragistics.

[Register("SingleRowGridView")]
 public class SingleRowGridView : UIView
 {
 //GridView that is displayed
 public IGGridView GridView;
//Column Definition of GridView's column
 private IGGridViewImageColumnDefinition _col;
//Datasource Helper to assist with databinding
 public IGGridViewSingleRowSingleFieldDataSourceHelper Ds;
public SingleRowGridView()
 {
 Initialize();
 }
public SingleRowGridView(RectangleF bounds)
 : base(bounds)
 {
 Initialize();
 }
void Initialize()
 {
BackgroundColor = UIColor.Black;
//Create a Gridview that's a bit farther down than center
 //This Grid has a single row of 200x200 cells
 GridView = new IGGridView(new RectangleF(0f, Frame.Size.Height / 2 - 100, Frame.Size.Width, 200f), IGGridViewStyle.IGGridViewStyleDefault)
 {
 AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleBottomMargin | UIViewAutoresizing.FlexibleTopMargin,
 SelectionType = IGGridViewSelectionType.IGGridViewSelectionTypeCell,
 HeaderHeight = 0f,
 EmptyRows = false,
 RowSeparatorHeight = 0f,
 AllowHorizontalBounce = true,
 AlwaysBounceVertical = false,
 RowHeight = 200f,
 ColumnWidth = IGColumnWidth.CreateNumericColumnWidth(200f),
 Theme = new IGGridViewLightTheme()
 };
AddSubview(GridView);
  //Create Image Column definition for Image property that gets the image from a URL
  _col = new IGGridViewImageColumnDefinition(@"Image",
 IGGridViewImageColumnDefinitionPropertyType
 .IGGridViewImageColumnDefinitionPropertyTypeStringUrl);
   //Create a new Datasource helper that is initialized with the column we just created
   Ds = new IGGridViewSingleRowSingleFieldDataSourceHelper(_col);

   //set the Grid's datasource to the one that we created
   GridView.DataSource = Ds;
   GridView.ReloadData();
  }
}

Step 12.) To bind data to an Infragistics datasource helper, the binding class must inherit from NSObject. This is one of the reasons that we have to implement a custom binding solution in MvvmCross.

//The DataSource helper's data property must be of type NSObject
 [Register("SingleRowData")]
 public class SingleRowData : NSObject
 {
    public SingleRowData(string image)
    {
      Image = image;
    }
   [Export("Image")]
    public string Image { get; set; }
 }

Step 13.) Create Target Binding for our GridView. First, create a new folder named Target. Add a class named SingleRowDataSourceTargetBinding. This class will Inherit from MvxTargetBinding, and override SetValue, TargetType, and DefaultMode. SetValue gets called when the Property Change notification is fired, and is where

//Inherit from MvxTarget binding to perform custom binding 
 public class SingleRowDataSourceTargetBinding : MvxTargetBinding
 {
 //Return View as User Defined View
 protected SingleRowGridView View
 {
     get { return Target as SingleRowGridView; }
 }
public SingleRowDataSourceTargetBinding(SingleRowGridView view)
 : base(view)
 {
    if (view == null)
    {
      MvxBindingTrace.Trace(MvxTraceLevel.Error, "Error - GridViewSource is null in SingRowDataSourceTargetBinding");
    }
 }
//Override SetValue to perform binding
 public override void SetValue(object value)
 {
   var view = View;
   if (view == null)
     return;

   var monkeys = (List<Monkey>)value;   //Grab items from list that was passed in, and create new NSObject[]
 if (monkeys != null)
 {
    View.Ds.Data = monkeys.Select(t => new SingleRowData(t.ImageUrl)).ToArray();
  //Must Call ReloadData 
    View.GridView.ReloadData();
    }
  }
//Set the target type
 public override Type TargetType
 {
    get { return typeof(NSObject[]); }
 }
//Set the Default binding Mode
 public override MvxBindingMode DefaultMode
 {
    get { return MvxBindingMode.OneWay; }
 }
}

Step 14.) Register the Target Binding just created. In the Setup.cs class that was generated from adding MvvmCross from Nuget, override the FillTargetFactories method, and register the target binding.

//Set up Target Bindings
 protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
 {
    registry.RegisterCustomBindingFactory<SingleRowGridView>("SingleRowData", source => new SingleRowDataSourceTargetBinding(source));
    base.FillTargetFactories(registry);
 }

Step 15.) Edit FirstView, set up bindings, and profit! Delete the code that was generated by MvvmCross for binding to the example HelloWorld string. Add a new SingleRowGridView to FirstView, and create the bindings.

[Register("FirstView")]
 public class FirstView : MvxViewController
 {
    private SingleRowGridView _gridView;
   public override void ViewDidLoad()
   {

     base.ViewDidLoad();
    //Initialize new SingleRowGridView
    _gridView = new SingleRowGridView(View.Bounds);
   // ios7 layout
   if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
       EdgesForExtendedLayout = UIRectEdge.None;

   //Bind GridView 
   var set = this.CreateBindingSet<FirstView, Core.ViewModels.FirstViewModel>();
   set.Bind(_gridView).For("SingleRowData").To(vm => vm.Monkeys);
   set.Apply();
  Add(_gridView);
 }
}

Here’s what the end result looks like.

iOS Simulator Screen shot Oct 14, 2013 11.26.48 PM

Thanks for stopping by! Enjoy!

Xamarin.iOS Stopwatch control using Infragistics Nuclios

At work I have the pleasure of using some awesome Infragistic controls for winform applications. At local .Net users group meeting I won a license for the NetAdvantage Ultimate Suite! Awesomesauce! Included with this suite is a set of controls for iOS. They’ve even compiled dll’s for monotouch! Included in their sample projects is an awesome Stopwatch that uses a couple of IGGaugeViews. All of the samples shipped with nucliOS are all in objective-c. With a bit of help from my friends at Infragistics, I was able to get the Stopwatch control Xamafied. The sample code can be downloaded on Github . You’ll need to install nuclios. A free trial can be found here . Below is the Stopwatch UIView…

    public class StopWatch : UIView
    {
        private IGGaugeView _backgroundGauge;
        private UIImageView _glassImage;
        public IGGaugeView HoursGauge;
        private UIImageView _imageView;

        public StopWatch()
        {
            Setup();
        }

        public string ImageName
        {
            get { return @"Clock2.png"; }
        }

        public override RectangleF Frame
        {
            get { return base.Frame; }
            set
            {
                base.Frame = value;

                if (_backgroundGauge == null)
                    return;

                float val = .11f;

                if (Frame.Size.Height > Frame.Size.Width)
                {
                    val = .09f;
                }

                if (UIScreen.MainScreen.PreferredMode.Size == new SizeF(640, 1136))
                {
                    val = .070f;
                }

                _backgroundGauge.CenterY = HoursGauge.CenterY = .48f + val;
            }
        }

        public void Setup()
        {
            if (HoursGauge == null)
            {
                _imageView = new UIImageView(new RectangleF(0, 0, Frame.Size.Width, Frame.Size.Height))
                {
                    AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight,
                    ContentMode = UIViewContentMode.ScaleAspectFit,
                    Image = UIImage.FromBundle(ImageName)
                };
                AddSubview(_imageView);

                // Initialize all Gauges with the same exact frame
                HoursGauge = new IGGaugeView
                {
                    Frame = new RectangleF(0, 0, Frame.Size.Width, Frame.Size.Height),
                    AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
                };
                _backgroundGauge = new IGGaugeView
                {
                    Frame = new RectangleF(0, 0, Frame.Size.Width, Frame.Size.Height),
                    AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
                };

                // Add all of our subviews to the visual tree
                AddSubview(_backgroundGauge);
                AddSubview(HoursGauge);

                // Hide the 0 label
                _backgroundGauge.DuplicateLabelOmissionStrategy =
                    IGGaugeDuplicateLabelOmissionStrategy.IGGaugeDuplicateLabelOmissionStrategyOmitFirst;

                // Hide the other gauge labels
                HoursGauge.Font = UIFont.SystemFontOfSize(1);

                _backgroundGauge.NeedlePivotShape = IGGaugePivotShape.IGGaugePivotShapeNone;

                // Hide elements from our top level gauges by setting the brush color to clear
                _backgroundGauge.NeedleOutline =
                    HoursGauge.MinorTickBrush =
                    HoursGauge.TickBrush =
                    _backgroundGauge.ScaleBrush =
                    HoursGauge.BackingBrush =
                    _backgroundGauge.NeedleBrush =
                    HoursGauge.ScaleBrush = HoursGauge.BackingOutline = new IGBrush(UIColor.Clear);

                HoursGauge.BackingStrokeThickness = 0;

                // Set the Start and End Angles of the scale - All gauges must have the same values
                _backgroundGauge.ScaleStartAngle = HoursGauge.ScaleStartAngle = 270;
                _backgroundGauge.ScaleEndAngle = HoursGauge.ScaleEndAngle = 270 + 360;

                // Since we don't want to see the major tick marks, lets set the internval to a larger number to limit how many tick marks try to render.
                HoursGauge.Interval = 60;

                // Set the Max Values
                _backgroundGauge.MaximumValue = HoursGauge.MaximumValue = 60;

                // Since we don't want to see these tick marks, lets set the minor tick marks count to zero so no tick marks try to render.
                HoursGauge.MinorTickCount = 0;

                // Setup the Tick marks for the background
                _backgroundGauge.Interval = 1;
                _backgroundGauge.MinorTickCount = 4;

                SetupStyling();

                HoursGauge.Value = 60;
            }
        }

        public void SetupStyling()
        {
            _glassImage = new UIImageView(new RectangleF(0, 0, Frame.Size.Width, Frame.Size.Height))
            {
                AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight,
                ContentMode = UIViewContentMode.ScaleAspectFit,
                Image = UIImage.FromBundle(@"Clock2Shadow.png")
            };
            AddSubview(_glassImage);

            bool iPhone = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone;

            // Make the Number labels render inside of the tickmarks as opposed to outside of them
            _backgroundGauge.LabelExtent = .50f;

            // Move the TickMarks further out from the center of the clock
            _backgroundGauge.TickStartExtent = .59f;
            _backgroundGauge.TickEndExtent = .64f;
            _backgroundGauge.MinorTickStartExtent = .59f;
            _backgroundGauge.MinorTickEndExtent = .64f;

            _backgroundGauge.MaximumValue = 60f;
            _backgroundGauge.Interval = 5f;

            // Set the font for our labels
            _backgroundGauge.Font = UIFont.FromName(@"Verdana", (iPhone) ? 20f : 40f);

            // Move the Backing out and set its colors and stroke thickness
            _backgroundGauge.BackingOuterExtent = .93f;
            _backgroundGauge.BackingStrokeThickness = 0f;
            _backgroundGauge.BackingOutline = _backgroundGauge.BackingBrush = new IGBrush(UIColor.Clear);

            // Make the hours hand smaller
            HoursGauge.NeedleEndExtent = .6f;
            HoursGauge.NeedlePointFeatureExtent = .55f;

            // Change the thickness of the minutes and hours hands.
            HoursGauge.NeedleStartWidthRatio = .03f;
            HoursGauge.NeedleEndWidthRatio = .03f;
            HoursGauge.NeedlePointFeatureWidthRatio = .02f;

            HoursGauge.NeedlePivotWidthRatio = .001f;

            HoursGauge.NeedlePivotBrush = new IGBrush(UIColor.Black);
            HoursGauge.NeedlePivotOutline = new IGBrush(UIColor.White);

            HoursGauge.NeedleShape = IGGaugeNeedleShape.IGGaugeNeedleShapeTriangle;

            HoursGauge.NeedlePivotShape = IGGaugePivotShape.IGGaugePivotShapeCircleUnderlayWithHole;

            var range = new IGGaugeRange
            {
                Outline = new IGBrush(UIColor.Black),
                StrokeThickness = 2,
                StartValue = 0,
                EndValue = 60,
                InnerStartExtent = .59f,
                InnerEndExtent = .59f,
                OuterEndExtent = .59f,
                OuterStartExtent = .59f
            };
            _backgroundGauge.AddRange(range);

            Frame = Frame;
        }
    }

And the usage..

    public class MyViewController : UIViewController
    {
        //declare StopWatch UIView
        private StopWatch _stopWatch;

        //Timer to adjust watch hand
        private NSTimer _timer;

        //Tick count to set value for stopwatch
        private float _tickCount;

        public MyViewController()
        {
        }

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

            View.Frame = UIScreen.MainScreen.Bounds;
            View.BackgroundColor = UIColor.White;
            View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;

            //Init tickcount
            _tickCount = 60;

            //Init StopWatch
            _stopWatch = new StopWatch
            {
                Frame = new RectangleF(0, 0, this.View.Frame.Size.Width, this.View.Frame.Size.Height),
                AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight,
            };

            View.AddSubview(_stopWatch);

            //Let the countdown begin!
            _timer = NSTimer.CreateRepeatingScheduledTimer(TimeSpan.FromSeconds(.1d), () =>
                {
                    if (_tickCount > 0)
                    {
                        _tickCount -= .1f;
                    }
                    else
                    {
                        _timer.Invalidate();
                        _timer.Dispose();
                    }

                    //The value property of IGGauge determines where the gauge value is set
                    _stopWatch.HoursGauge.Value = _tickCount;
                });
        }    }

Xamarin iOS 7 MapKit SnapShot

A new addition to the MapKit API in iOS 7 is the MKMapSnapShotter class. This class can be used to take a snap shot of map based images. In the following example I’ll show you how to create a snap shot, and save the image to the devices photo album.

Since we covered creating a new map in an earlier post, we’ll skip that part, and dive right into the goodness!

First, you’ll need to create a new instance of MKMapSnapshotOptions, and set the region, scale, and size properties.

      using (var snapShotOptions = new MKMapSnapshotOptions())
      {
          snapShotOptions.Region = _map.Region;
          snapShotOptions.Scale = UIScreen.MainScreen.Scale;
          snapShotOptions.Size = _map.Frame.Size;

Next, we’ll create a new instance of MKMapSnapshotter, and initialize it with the MKMapSnapshotOptions we just created.

using (var snapShot = new MKMapSnapshotter(snapShotOptions))

Finally, we need to call the start method and handle the completion handler. The handler gives us an MKSnapShop, which has an Image property. We can call the SaveToPhotosAlbum method on the Image property to store it to the device’s album.

snapShot.Start((snapshot, error) =>
              {
                  if (error == null)
                  {
                      snapshot.Image.SaveToPhotosAlbum(
                          (uiimage, imgError) =>
                              {
                                  if (imgError == null)
                                  {
                                      new UIAlertView("Image Saved", "Map View Image Saved!", null, "OK", null).Show();
                                  }

ImageImage

The image should then be available in the device’s photo album. I have updated the MapKitDirections Sample on GitHub to include these new changes.

Receiving Files through AirDrop using Xamarin.iOS

Receiving Files

In order to receive files from AirDrop, two steps must be taken.

  1. Register your application as a handler for a specific file type.
  2. Actually handle the AirDrop action.

The first item can be done by adding the correct UTI in info.plist file. It’s worth noting that there are some system level UTIs, such as images (public.jpeg, public.png), that cannot be used/handled within an app. Below is an example for handling PDF files.

<key>CFBundleVersion</key>
<string></string>
<key>CFBundleTypeName</key>
<string>PDF</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.adobe.pdf</string>
</array>

Item 2 is taken care of by overriding the OpenUrl method in the AppDelegate. AirDrop places the file in your applications /Documents/Inbox . This directory is read-only, so if the file needs to be modified it must be copied off somewhere.

        public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
        {
            new UIAlertView("Airdrod File Received!", "File " + url + " received!", null, "OK", null).Show();
            return true;
        }

I have update the sample on GitHub to include these changes.