August 20, 2014
Shortening a UUID / GUID in Swift
Jeff Atwood has a helpful article on the topic. Spoiler alert: his conclusion is that ASCII85 encoding can be used to compress a UUID down to 20 characters.
I implemented a base64 solution as an excuse to get better acquainted with Swift. The downside of using base64 is that it yields a 22 character compressed UUID. The upside is that a base64 implementation is built into Cocoa / Cocoa Touch. If you go with ASCII85 you'll have to roll your own implementation.
Please keep in mind that additional changes will need to be made to this solution if the intent is to pass the compressed UUID as part of a url string (eg. '+' and '/' characters, etc will need to be dealt with). Try it out for yourself in a Swift playground!
July 10, 2013
Integrating Flurry Analytics into your iOS App
Flurry Analytics is a great service for keeping tabs on mobile app usage. They help over 100,000 companies monitor 300,000 apps that run on a variety of platforms (iOS, Android, Windows Phone and more). Best of all, it's free to use Flurry Analytics in your own apps.
My goal with this blog entry isn't to come across like a sales pitch. If you have a different analytics service that fits your needs, then more power to you. The point is to use something. Measuring unique users, sessions, new users and then visualizing these attributes over the course of time can reveal some powerful trends. The results might help you prioritize certain features or bug fixes. Maybe the information will help you plan the timing of your next sale. The data adds value as an anonymous source of feedback from your users that'll enable you to be an even better steward of your app.
So what do you need to do to get started with Flurry? First go to their website and sign up (or log in if you already have an account). In the "Applications" tab click the "Add a New Application" link and choose a platform. On the next page name the app and assign a category. You'll be greeted with a "Unique Application Key". Save this key for later and then click the button to download the SDK.
The code sample in this post will illustrate how to use the iOS SDK to integrate Flurry into your app. For MonoTouch (aka Xamarin.iOS) developers I'd advise taking a look at the FlurryAnalytics folder in the monotouch-bindings project. For other platforms, visit Flurry's "Getting Started" documentation.
Continuing with our objective-c / iOS example, add the Flurry lib (found in the SDK you downloaded) to your project. You will also need to link your project against the SystemConfiguration.framework. Now move into your app delegate code, replacing YOUR_API_KEY with the Application Key Flurry assigned to you:
#import "Flurry.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[Flurry startSession:YOUR_API_KEY];
// Other code here...
}
This illustrates the bare minimum to get you started - very little code in exchange for some powerful analytics...
September 26, 2011
Slide to Unlock Control in MonoTouch
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
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
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
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
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;
}
}