Showing posts with label Mono. Show all posts
Showing posts with label Mono. Show all posts

September 26, 2011

Slide to Unlock Control in MonoTouch

Unable to display content. Adobe Flash is required.
In a recent MonoTouch project I needed to make something similar to the slide-to-unlock control at the bottom of the iOS lock screen.

The goal was to derive from a UIImageView and trigger an Activate event when a specified slider image got within range of the right end of the control. I found this was fairly straightforward to implement using a pan gesture. Below is the class I came up with and the associated images that were used:

Image


Slider
public class UISlideToActivateImageView : UIImageView
{
  #region Fields and Properties

  public event EventHandler<EventArgs> Activate;

  protected const float DEFAULT_ACTIVATION_RANGE = 20;

  public float ActivationRange { get; set; }

  public static Selector PanSelector
  {
    get
    {
      return new Selector("HandlePan");
    }
  }

  private UIImageView _sliderView = null;
  public UIImage Slider
  {
    get
    {
      return _sliderView.Image;
    }
    set
    {
      if (value != null)
      {
        if (_sliderView != null)
        {
          _sliderView.RemoveFromSuperview();
        }
        _sliderView = new UIImageView(
          new RectangleF(new PointF(0, 0), value.Size));
        _sliderView.Image = value;
        AddSubview(_sliderView);
      }
    }
  }

  protected PointF InitialLocation { get; set; }

  #endregion

  #region Constructors

  public UISlideToActivateImageView(PointF location, UIImage image)
    : base(image)
  {
    ActivationRange = DEFAULT_ACTIVATION_RANGE;
    Frame = new RectangleF(location, image.Size);
    RegisterPanGesture();
  }

  public UISlideToActivateImageView(PointF location, UIImage image,
    UIImage slider) : base(image)
  {
    ActivationRange = DEFAULT_ACTIVATION_RANGE;
    Frame = new RectangleF(location, image.Size);
    Slider = slider;
    RegisterPanGesture();
  }

  #endregion

  #region Events, Overrides and Delegates
        
  [Export("HandlePan")]
  public void HandlePan(UIPanGestureRecognizer panGesture)
  {
    const double EndedAnimationDuration = 0.2d;
    PointF newLocation;
    float adjX;
    if (panGesture != null)
    {
      newLocation = panGesture.LocationInView(this);
      switch (panGesture.State)
      {
        case UIGestureRecognizerState.Began:
          //User first taps the slider
          if ((newLocation.X <= (Frame.X + Slider.Size.Width))
            && (newLocation.X >= 0))
          {
            InitialLocation = newLocation;
          }
          break;
        case UIGestureRecognizerState.Changed:
          //Moved their finger - make slider follow horizontal movements
          adjX = Frame.X + (newLocation.X - InitialLocation.X);
          if ((InitialLocation != PointF.Empty) && (adjX >= 0)
            && (adjX <= (Frame.Width - Slider.Size.Width)))
          {
            UIView.Animate(0d, delegate() {
              _sliderView.Frame = new RectangleF(new PointF(adjX, 0),
                                             _sliderView.Frame.Size);
            });
            //If the Slider comes within ActivationRange of end of this
            //control, fire the Activate event
            if ((Activate != null)
              && (adjX >= 
                (Frame.Width - Slider.Size.Width - ActivationRange)))
            {
              //Moved the slider all the way across the image view
              Activate(this, EventArgs.Empty);
            }
          }
          break;
        case UIGestureRecognizerState.Cancelled:
        case UIGestureRecognizerState.Failed:
        case UIGestureRecognizerState.Ended:
          //Lifted up finger - return slider to original position
          InitialLocation = PointF.Empty;
          UIView.Animate(EndedAnimationDuration, delegate() {
            _sliderView.Frame = new RectangleF(new PointF(0, 0), 
                                             _sliderView.Frame.Size);
          });
          break;
      }
    }
  }

  //Delegate for allowing the pan gesture recognizer to receive touch.
  public class ReceiveTouchGestureRecognizerDelegate
    : UIGestureRecognizerDelegate
  {
    public override bool ShouldReceiveTouch (
      UIGestureRecognizer recognizer,
      UITouch touch)
    {
      return true;
    }
  }

  #endregion

  #region Helper Methods

  protected void RegisterPanGesture()
  {
    UserInteractionEnabled = true;
    UIPanGestureRecognizer pan = new UIPanGestureRecognizer();
    pan.AddTarget(this, PanSelector);
    pan.Delegate = new ReceiveTouchGestureRecognizerDelegate();
    AddGestureRecognizer(pan);
  }

  #endregion
}
To use this control it's simply a matter of assigning images (Image and Slider properties) and adding the class as a sub-view:
UISlideToActivateImageView slideToActivate = 
  new UISlideToActivateImageView(new PointF(31, 214), 
    UIImage.FromFile("slidetoactivate.png"), UIImage.FromFile("slider.png"));
slideToActivate.Activate += delegate(object sender, EventArgs e) {
  UIAlertView alert = new UIAlertView("Congratulations!", 
    "You've engaged the UISlideToActivateImageView!", null, "Okay");
  alert.Show();
};			
View.AddSubview(slideToActivate);

August 30, 2011

Custom Animations for the UINavigationController in MonoTouch

Pushing and popping controllers in a standard UINavigationController is a frequent activity when navigating an iOS app. The transition from controller to controller on the navigation stack may be optionally animated. Unfortunately, there's little choice regarding the animation itself. The developer determines whether or not the default right-to-left (push) or left-to-right (pop) effect is rendered by setting a boolean.

In some areas of my UI I wanted more control over the animations in my navigation stack. This thread on Stack Overflow was a great place to start. I ported some of the Objective-C code I found to C# extension methods as follows:
//Allows a UINavigationController to push using a custom animation transition
public static void PushControllerWithTransition(this UINavigationController 
  target, UIViewController controllerToPush, 
  UIViewAnimationOptions transition)
{
  UIView.Transition(target.View, 0.75d, transition, delegate() {
    target.PushViewController(controllerToPush, false);
  }, null);
}
		
//Allows a UINavigationController to pop a using a custom animation 
public static void PopControllerWithTransition(this UINavigationController 
  target, UIViewAnimationOptions transition)
{
  UIView.Transition(target.View, 0.75d, transition, delegate() {
    target.PopViewControllerAnimated(false);
  }, null);			
}
With these extensions in scope, moving between controllers with a flip animation is now as trivial as this:
//Pushing someController to the top of the stack
NavigationController.PushControllerWithTransition(someController, 
  UIViewAnimationOptions.TransitionFlipFromLeft);

//Popping the current controller off the top of the stack
NavigationController.PopControllerWithTransition(
  UIViewAnimationOptions.TransitionFlipFromRight);

August 19, 2011

Scheduling Local Notifications in MonoTouch

iOS 4 introduced local notifications, allowing apps to communicate brief text messages to users. In particular, a scheduled local notification can reach a user whether the app is running in the foreground, in the background or not running at all. While not as versatile as push notifications, scheduled local notifications can be helpful when an app needs to set-up predetermined alarms or reminders. Each app can have a total of 64 simultaneous scheduled local notifications (use them wisely so as to not annoy your users). Below is an example of how to schedule a UILocalNotification using MonoTouch:
//Schedule one minute from the time of execution with no repeat
UILocalNotification notification = new UILocalNotification{
  FireDate = DateTime.Now.AddMinutes(1),
  TimeZone = NSTimeZone.LocalTimeZone,
  AlertBody = "This is your scheduled local notification!",
  RepeatInterval = 0
};
UIApplication.SharedApplication.ScheduleLocalNotification(notification);
You might notice that scheduled local notifications are automatically displayed when the current date/time surpasses our FireDate and the app is in the background or not running at all. However, when the app is in the foreground local notifications are seemingly ignored. This is by design. If the app is in the foreground you are in charge of responding to scheduled local notifications by overriding the ReceivedLocalNotification method as follows:
public override void ReceivedLocalNotification(UIApplication application, 
  UILocalNotification notification)
{
  //Do something to respond to the scheduled local notification
  UIAlertView alert = new UIAlertView("Notification Test", 
  notification.AlertBody, null, "Okay");
  alert.Show();
}

February 26, 2011

Async Web Service Timout

When using SOAP-based web services (in this case with MonoDevelop / MonoTouch) I found I had to rely on the asynchronous proxy web methods to keep my UI responsive. Pretty common, right? Unfortunately, the asynchronous web methods seem to ignore the TimeOut property on the service. Without a timeout my users could be left waiting indefinitely on an unresponsive server. Not acceptable.

To get around this issue I came up with a way to cancel an asynchronous web method request after a set period of time using a Timer object. Maybe it can help others in a similar predicament? Here's some sample code:

protected void GetServiceData()
{
//Indicates that network activity is going on
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;

//Make the async call
using (MyService service = new MyService())
{
//Timer is set to go off one time after 15 seconds
Timer serviceTimer = new Timer(15000);
serviceTimer.AutoReset = false;
serviceTimer.Elapsed += delegate(object source, ElapsedEventArgs e) {
service.Abort();
throw new WebException("Timeout expired!");
};
serviceTimer.Enabled = true;

//Call the desired web method
service.WebMethodCompleted += ServiceWebMethodCompleted;
service.WebMethodAsync(serviceTimer);
}
}

//The async callback method
protected void ServiceWebMethodCompleted(object sender, WebMethodCompletedEventArgs e)
{
using (NSAutoreleasePool pool = new NSAutoreleasePool())
{
//Disable the timer that would abort this call with an exception
//if the call to this web method took too long
Timer serviceTimer = e.UserState as Timer;
if (serviceTimer != null)
{
serviceTimer.Enabled = false;
serviceTimer.Dispose();
}

if (e.Error != null)
{
if (e.Error is WebException)
{
//An error due to a timeout happened - handle it here
}
else
{
//Handle all other errors here
}
}
else
{
//Async call successful - do something cool with e.Result
}

//Indicates network activity has finished
this.InvokeOnMainThread(delegate() {
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false;
});
}
}

September 18, 2010

Geo Rally for the iPhone

Geo RallyMy side project, Durbinware, released our first iPhone app, Geo Rally, to Apple's App Store back in May. It allows users to create, share and challenge timed point-to-point treasure hunts. If you've seen the movies Rat Race, or Midnight Madness you'll understand the basic concept. The first point, or starting line, is revealed at the beginning of the challenge and then each consecutive waypoint requires users to solve text-based clues or riddles to proceed. Solving a clue and arriving at the next location triggers another clue and so on until the user arrives at the last point, or finish line. Different users can compete against the same course, or Rally, and the app will optionally tweet race completion times for bragging rights. It has the potential to facilitate a great team building experience among youth groups, friends or co-workers. If this MonoTouch based app sounds interesting to you then by all means please download it.

I had a great time working on Geo Rally and was very thankful for MonoTouch. Coming from a .NET background I was a little overwhelmed at the prospect of learning a new language (Objective C) and iOS's multitude of supporting core APIs. MonoTouch eliminated one of these roadblocks so I could hit the ground running in a familiar language and branch out into unknown APIs as needed. In the end I believe this made me more productive. And now, coupled with MonoDroid and Windows Phone 7, there are even more options for code reuse (everything but the UI). It's a good time to be a C# developer!

July 10, 2010

iOS 4 and Map Kit Overlays with MonoTouch

One of the "over 100 new features" in iOS 4 that I've been especially interested in are Map Kit overlays. Previously, developers were limited to using annotations to express points on a map. If you wanted to draw a shape of some sort you were more or less up a creek.

Overlays are a special kind of annotation designed to represent an area on a map. iOS 4's Map Kit comes with some common shapes built in (rectangles, circles, polygons, etc). As I understand it it's also possible to make your own custom shapes.

Each overlay object holds data to represent the shape and has a corresponding view that tells the MKMapView's delegate how to draw the overlay. As an example here's how one might draw a circle overlay with a 100 meter radius around the Empire State Building. The code below shows how to do this in C# via MonoTouch.

CLLocationCoordinate2D empireStBld = new CLLocationCoordinate2D(40.748433, -73.985656);
double radiusInMeters = 100d;
MKCircle circle = MKCircle.Circle(empireStBld, radiusInMeters);
MapView.Delegate = new MapViewDelegate(circle);
MapView.AddOverlay(circle);


The only thing left is that the MapViewDelegate class needs to be set up to give an appropriate view for the overlay:

public class MapViewDelegate : MKMapViewDelegate
{
private MKCircle _circle = null;
private MKCircleView _circleView = null;

public MapViewDelegate(MKCircle circle)
{
_circle = circle;
}

public override MKOverlayView GetViewForOverlay(MKMapView mapView, NSObject overlay)
{
if ((_circle != null) && (_circleView == null))
{
_circleView = new MKCircleView(_circle);
_circleView.FillColor = UIColor.Cyan;
}
return _circleView;
}
}

October 14, 2009

Reading RGB Info from Images on the iPhone using MonoTouch

MonoTouch is an intriguing new software development kit from Novell. It utilizes the open source Mono project to allow developers to write code in C# that can then be statically compiled into native iPhone applications and deployed to devices or even to the App Store. A free evaluation version is available if the product interests you.



I downloaded the evaluation version and decided to take this new development kit for a spin by using it to read RGB color information from all pixels in an image saved on the device. I ended up creating a CGBitmapContext to do this and then marshaling the results into a byte array. Please see the code below for more details:

//_selectedImage refers to the UIImage object of interest
int width = _selectedImage.CGImage.Width;
int height = _selectedImage.CGImage.Height;
//4 color settings for every pixel (RGBA)
int pixelDataCount = width * height * 4;
IntPtr data = Marshal.AllocHGlobal(pixelDataCount);
int bitsPerComponent = 8;
int bytesPerPixel = 4;
int bytesPerRow = bytesPerPixel * width;
//Draw image to an RGB bitmap context
CGBitmapContext imgContext = new CGBitmapContext(data,
width, height, bitsPerComponent, bytesPerRow,
CGColorSpace.CreateDeviceRGB(), CGImageAlphaInfo.PremultipliedLast);
imgContext.DrawImage(new RectangleF(0.0F, 0.0F, width, height),
_selectedImage.CGImage);
byte[] pixelData = new byte[pixelDataCount];
Marshal.Copy(data, pixelData, 0, pixelDataCount);

//Milk the color settings for each pixel in the image
int red;
int green;
int blue;
int currentPixelIndex = 0;
for (int countHeight = 0; countHeight < height; countHeight++)
{
for (int countWidth = 0; countWidth < width; countWidth++)
{
red = pixelData[currentPixelIndex];
currentPixelIndex++;
green = pixelData[currentPixelIndex];
currentPixelIndex++;
blue = pixelData[currentPixelIndex];
currentPixelIndex++;

//Currently ignoring the alpha byte
currentPixelIndex++;

//Probably will want to Cache RGB values here for later use
}
}