Stretching Views with Auto Layout on iOS

I am busy porting my Windows Phone app to Android and iOS, and everything seems to be going quite well. I am using MvvmCross with Xamarin Platform Tools and there is much code reuse.

This is a simple app, literally two screens that could actually be merged into a single screen: A ID number entry form and a status page with a few lines of text. However an app is an app and an Android, iOS and Windows Phone app is still just awesome to have.

This is my first attempt at creating an iOS app from scratch. I have done a couple of Android apps for various companies, but iOS is still ultra new for me. So far as I can see, Apple has created a huge API with many great interfaces. The iOS API is more mature than the Android API, and this shows in many areas.

One of the awesome features that I came across is the Auto Layout features of creating interfaces. My app’s start screen has a view that contains the controls and an image view underneath. The controls view is of a fixed size as it just contains a text box and a button, but the image scaled to fit the remaining space.

On Windows Phone, I was just using a Grid with an expandable row and set the image to fill it. On Android, I placed the image view inside a LinearLayout and set the weight to 1. On iOS, I wasn’t sure… I recalled from my previous dabbling that there was some Autoresizing options, but after reading around, there seems to be something better, Auto Layout.

Eventually, I discovered that this is quite powerful, but a little more complex. What I needed to do was set the image view to be “anchored” at various points. In this case, the image was to expand to fill the space, so I naturally set the top, left bottom and right sides to be pinned at a nice space from the edge of the screen.

I expected the view, if pinned at the top and at the bottom, would grow as the points moved apart, unfortunately for me, there was something that I missed. The height. When I looked around, there was a little icon showing that there was a problem with my constraints. It picked up that there was a problem with the heights of my views, but I wasn’t sure what to do exactly.

My first solution to the problem, but actually wasn’t a solution was to see if there was a way to tell the image view to be expandable. For some reason it did not occur to me that if it was growing horizontally and not vertically, I needed to check to see what was different there. But anyway, I found a little option called “Intrinsic Size” and if I set this to “Placeholder” all the errors went away, but sadly for me, this has little to do with my problem. Intrinsic Size is for when there is shadows or other decoration features that the layout engine should ignore. Even though there were no errors, the actual app on the device just failed and the image was stretched over everything. Sad.

So, it finally occurred to me that if my image was anchored below the controls view, I may actually need to specify a height for the controls. This is not exactly intuitive as it should use the designer’s size automatically, but that’s just me. Anyway, after giving the controls view a height, the running app now correctly stretched my image from the bottom of the view to the bottom of the screen, or, filled the space. Yay!

Because I am cool with images, and Balsamiq Mockups, I have prepared an image for the viewers:

Auto Layout on iOS

Returning with the iOS Keyboard

Almost all mobile apps have some sort of text entry. One of the main ways to do this is to simply add a text box. Adding a text box is both simple and easy to do, but we can take this a little further and improve the user experience.

Hiding the Keyboard

When the keyboard is shown by tapping a text box, the natural thing to do in order to hide it would be to tap outside the text box. However, this is not the case by default. But, this is easy to add by overriding the TouchesBegan method of the ViewController:

    public override void TouchesBegan (NSSet touches, UIEvent evt)
    {
        // hide the keyboard from all views
        View.EndEditing (true);

        base.TouchesBegan (touches, evt);
    }

Enabling/Disabling the Return Key

There is a very simple way to let the user know that some text is required: disable the return key if no text is provided. This can be done in both the code and in the designer. In the code we set the EnablesReturnKeyAutomatically property:

    textField.EnablesReturnKeyAutomatically = true;

There is a checkbox in the designer labeled “Auto-enable Return Key”.

It is important to note that this does not apply if the keyboard type is NumberPad as there is no return key for this keyboard type.

Changing the Keyboard Type

Another way to provide a basic restriction of the UI is to select a keyboard type. By selecting an appropriate keyboard type, the user can know what is expected to be entered as well as make the available characters easier to reach. If we only want numbers, we use the NumberPad as this removes the other keys. If we are providing an entry for an email address, we use the EmailAddress type which makes the @ and the . key more available:

    textField.KeyboardType = UIKeyboardType.NumberPad;

There is a dropdown in the designer labeled “Keyboard” which provides all the available options.

Keyboard Types on iOS

Changing the Return Key Type

Another nice user experience is to change the text for the “return” key, which provides additional hints as to what is going to happen with the input. There are several options that are available, such as Done, Search or Next:

    textField.ReturnKeyType = UIReturnKeyType.Done;

There is a dropdown in the designer labeled “Return Key” which provides all the available options.

Adding an Action to the Return Key

When the user presses the return key on the keyboard, we would expect something to happen, especially if it is a single line textbox. But, on iOS nothing happens, so we attach a delegate that can hide the keyboard and maybe do some more work. This is easy to add by assiging a delegate to the ShouldReturn property of the UITextFied:

    textField.ShouldReturn = delegate {
        // hide the keyboard
        textField.ResignFirstResponder ();

        // the "return" logic here

        return true;
    };

Adding “Return” to the Number Pad

As the NumberPad keyboard type does not have a return key, there is no default way to hide the keyboard, we have to manually add a handler to hide the keyboard when another view is tapped. Another way we can do this is to add a “Cancel” or a “Done” button accessory to the keyboard:

    // create the toolbar
    UIToolbar numberToolbar = new UIToolbar ();
    numberToolbar.BarStyle = UIBarStyle.Default;
    numberToolbar.Translucent = true;
    // set the items
    numberToolbar.Items = new [] { 
        // the cancel button
        new UIBarButtonItem (UIBarButtonSystemItem.Cancel, delegate {
            // hide the keyboard
            idEntryTextField.ResignFirstResponder ();
        }),
        // the space to separate the buttons
        new UIBarButtonItem (UIBarButtonSystemItem.FlexibleSpace, null),
        // the "continue" button
        new UIBarButtonItem ("Login"), UIBarButtonItemStyle.Done, delegate {
            // hide the keyboard
            idEntryTextField.ResignFirstResponder ();

            // the "return" logic here
        })
    };
    // fill the screen width
    numberToolbar.SizeToFit ();

    // set the accessory
    textField.InputAccessoryView = numberToolbar;

Bing Maps iOS SDK

The Bing Maps iOS SDK is a control for embedding maps directly into native iOS apps. With a rich, gesture-based UI, the SDK provides support for adding pushpins and other overlays to the maps. The SDK also allows your device location to be displayed and provides the ability to retrieve contextual information for a given map location.

This project provides a managed binding of the native library for Xamarin.iOS.

Sadly, after getting all this nice stuff going, I found the actual native library to be very buggy and often caused the app to crash. For example, if the push pins were animated, tapping them caused the app to crash. As a result, I won’t be pushing this to the stores.

Getting Started

Before you can use the map control, you will need a Bing Maps Key from the Bing Maps Portal. The process is described in the MSDN documentation, Getting a Bing Maps Key, and is very simple with only four steps.

A map key is not required for the maps to appear, but will display a popup reminder every time a map view is displayed.

Using the Map View

Once you have obtained a key, the next step is to add the map view to your app. You can obtain the control from the 3.

Once you have added the assembly to your app, you then need to add your Bing Maps Key to the Info.plist file in your project. This is a string key-value item with the key, BingMapsKey, with the value being the key you obtained from the portal.

Now, all that is left to do is to add the map view to your view. This can be done in the code or in the designers (storyboards or interface files).

If we are going to use the designers, then you can just add a UIView to your app, and then change the Class to be BMMapView. When the app is launched, a map should appear. If the code is used, then this snippet is all that is needed:

    // create the map view
    BMMapView mapView = new BMMapView (UIScreen.MainScreen.Bounds);
    // add to some View
    View.AddSubview (mapView);
    // show the user's location on the map (optional)
    mapView.ShowsUserLocation = true;

Binding Flurry Analytics with Xamarin

Over the last few days I have been creating a Xamarin.iOS and Xamarin.Android binding for Flurry Analytics SDKs. Flurry Analytics is a neat little library that allows for tracking of your app usage and various user stats.
And, because I really enjoyed my time doing this, I thought that I will share just a bit of the fun times and the not-so-fun times.

I have split this project up into four parts:

  1. Introduction and Pre-requisites
  2. Xamarin.iOS binding
  3. Xamarin.Android binding
  4. Flurry.Analytics in the wild

Pre-requisites

Before we start any coding, we need to get all our tools and files needed for the actual binding tasks.

I was using both Windows 8, with Visual Studio, and Mac OS X with Xamarin Studio. You can bind the Android library on either platform, but for iOS, it is easier to use the Mac. In this case, I will use the Mac for both iOS and Android.

Some of my very excellent sources for this process were the actual Xamarin documentation:

But, along with this info, there are just a few things that you may need. Firstly, it is good to update your Xamarin Studio to the latest stable release as this makes sure that all potential problems are minimized from the start.

Then, the next cool tool to get hold of is, Objective Sharpie, for binding iOS libraries. This tool is quite nifty for quickly generating the .NET interfaces from the Objective-C header files. It uses the header files not the actual compiled native library. The Xamarin docs on Objective Sharpie has a brief walkthrough on how to use the tool, so I will assume you know how to use it.

After we have all our tools ready to go, we need those SDKs from Flurry.

What I did was to create a free account by Flurry, and then created an Android and iOS Application using their dashboard. You have to have an application before they will let you download their SDKs. This step is pretty straight forward, so I won’t go into it here right now. Once you have created and downloaded the SDKs, you can extract them to a nice location for reference.

Now to get started with the real work…

But, as this is quite a long process, I will be splitting this article into this intro, the iOS binding and the Android binding. So, enjoy!

Binding Flurry Analytics with Xamarin.iOS

This week, I needed to use Flurry Analytics in my Xamarin.iOS and my Xamarin.Android apps, but there was no .NET SDK for these platforms. Flurry did provide a SDK for each platform, but it was the native libraries, which cannot be used directly in .NET. Given this, I decided to bind those native libraries using Xamarin.iOS and Xamarin.Android.

I have split this project up into four parts:

  1. Introduction and Pre-requisites
  2. Xamarin.iOS binding
  3. Xamarin.Android binding
  4. Flurry.Analytics in the wild

The first thing we are going to bind is the iOS SDK for Flurry Analytics. This task needs two things from the downloaded SDK:

  • [iOS-sdk-path]/Flurry-iOS-5.2.0/Flurry/Flurry.h
  • [OS-sdk-path]/Flurry-iOS-5.2.0/Flurry/libFlurry_5.2.0.a

Of course, the version numbers may change for later releases. The header file (.h) is going to be used to generate the .NET interfaces and enums. The library (.a) is going to be used in the project.

Just before we do the cool things, we should just create our C# solution for the Xamarin.iOS binding. Just create a new project/solution and select the “iOS Binding Project” template. For my project name I used Flurry.Analytics.iOS, but you could use anything.

The default project has a few files in it, we will have a look at each one in depth as we go through the binding steps:

  • ApiDefinition.cs (this is for the generated .NET interfaces from Objective Sharpie)
  • AssemblyInfo.cs (the usual assembly information attributes)
  • Extras.cs (this is for any additional changes that are needed for the bound types)
  • StructsAndEnums.cs (this is for any extra types that you wish to include in the final assembly)

Creating the Initial Binding

This step will involve using Objective Sharpie to get us started, but we will almost always have to go in and tweak a few things.

So, first things first, open Objective Sharpie and start the wizard. Select the Flurry.h header file, and I used the namespace Flurry.Analytics. After saving your file somewhere, we can start the tweaks. I don’t save the result into my solution as the amount of tweaks needed is high enough that I like to have a before and after file. Also, this library is very small. But, there is no reason why you couldn’t just save over the ApiDefinition.cs file in you project.

Before we start, here is a snippet from the Flurry.h file:

    // an enum
    typedef enum {
        FlurryLogLevelNone = 0,
        FlurryLogLevelCriticalOnly,
        FlurryLogLevelDebug,
        FlurryLogLevelAll
    } FlurryLogLevel;

    // the main class
    @interface Flurry : NSObject {
    }
    // ...
    + (void)setAppVersion:(NSString *)version;
    + (NSString *)getFlurryAgentVersion;
    + (void)startSession:(NSString *)apiKey;
    // ...
    @end

In this snippet of Objective-C goodness, there is an enum named FlurryLogLevel and an @interface named Flurry. This will translate into a C# enum and class respectively. It is also good to note that all the methods on this particular type will be static.

The generated C# for this snippet is:

    // the enum
    public enum FlurryLogLevel : [unmapped: unexposed: Elaborated] {
        None = 0,
        CriticalOnly,
        Debug,
        All
    }

    // the main class
    [BaseType (typeof (NSObject))]
    public partial interface Flurry {
        // ...
        [Static, Export ("appVersion")]
        [Verify ("ObjC method massaged into setter property", "Flurry/Flurry.h", Line = 61)]
        string AppVersion { set; }

        [Static, Export ("getFlurryAgentVersion")]
        [Verify ("ObjC method massaged into getter property", "Flurry/Flurry.h", Line = 80)]
        string GetFlurryAgentVersion { get; }

        [Static, Export ("startSession:")]
        void StartSession (string apiKey);
        // ...
    }

As you can see, the generated file is similar, but not quite the same.

Tweaking the Generated Binding (ApiDefinition.cs)

After we generated our C# files, there are several things to note:

  • This code doesn’t compile at all
  • A good few of our methods are set to properties, especially note the setter-only ones
  • There is a [Verify(...)] attribute which doesn’t even exist
  • Some [Export] attributes have a value that ends in a colon (:), and others do not
  • The type is not a class but an interface
  • The base type is specified as a [BaseType] attribute
  • The enum has the item prefixes removed

This is quite a list here, but not all are bad. Objective Sharpie can’t generate perfect code as Objective-C is not C#, so there will always be human intervention. In these cases, it will generate what it thinks to be best and then let you know.

The [Verify] attribute is one way that Objective Sharpie lets you know that it changed something. In this instance, it is letting us know that it changed the setAppVersion method into a property. This is because usually there is both a getter and a setter method for a Objective-C property. But in this case, it is actually a method.

As you may note, these properties have a slightly different [Export] attribute in that these members don’t end in a colon. This is valid for Objective-C property setters, but as this is a method, we should add a colon back. It is also valid for methods that take parameters.

So, to fix those properties, remove the [Verify] attribute. We should also fix the exported name, and for the setter property, add the colon back in.

    [BaseType (typeof (NSObject), Name = "Flurry")]
    public partial interface FlurryAgent {

        [Static, Export ("setAppVersion:")]
        void SetAppVersion (string version);

        [Static, Export ("getFlurryAgentVersion")]
        string GetFlurryAgentVersion ();

        [Static, Export ("startSession:")]
        void StartSession (string apiKey);

    }

As you can see here, I corrected the SetAppVersion write-only property by transforming it into a void method that exports to setAppVersion:. Note the full name and colon as this method takes a parameter.

Also, I needed to fix the GetFlurryAgentVersion read-only property by transforming it into a string method that exports to “getFlurryAgentVersion”. Note the lack of colon as this method takes no parameters.

And finally, I decided I wanted my final class to be called FlurryAgent to avoid confusion with the namespace as well as indicate that it was the Agent. In order to do this, All I needed to do was to rename the type and add the Name property in the attribute with a value of "Flurry".

The reason for adding this property is, after changing the type name, the binding no longer will be able to find the underlying Objective-C type. Usually the binding will use the default name if none is provided, the C# class name. After changing it, it no longer reflected the Objective-C type name, hence the new property.

Managing the Structures and Enumerations (StructsAndEnums.cs)

The last thing to fix, in the generated code, is the enum.

This is simple to do, just remove the strange attribute-like bit: [unmapped: unexposed: Elaborated].

One of the nice things of Objective Sharpie is that it removes the enum prefixes and just uses the relevant bits. For example the original item was FlurryLogLevelNone, but this is now just None.

The final enum looks like this after it has been moved to StructsAndEnums.cs:

    public enum FlurryLogLevel {
        None = 0,
        CriticalOnly,
        Debug,
        All
    }

So far, all our generated code has been moved into either, ApiDefinitions.cs (the class), or StructsAndEnums.cs (the enum). We are still not complete yet, but we are nearly there.

Additional Binding Code (Extras.cs)

Once we have the basic binding done, we can always extend what the native library provided with extra features. This is done by adding new source files to the project.

For example, if for some reason you need to add any extra bindings to the library, say to access a private member or even just to make the new API cleaner and more .NET-like.

For example you could do something like this:

    public partial class FlurryAgent : NSObject {
        public static void LogTimedEvent (string eventName, Action action) {
            try {
                FlurryAgent.LogEvent (eventName, true);
                action(); // do our task
            } catch {
                FlurryAgent.EndTimedEvent (eventName, null);
                throw;
            }
        }
    }

What is happening here is that I am adding a new method to the final .NET assembly. This method can make use of the methods in the API definition interface. In this particular example, I am adding a method that can take an Actionand make sure that we fire a ‘starting’ and then an ‘ending’ event to the Flurry servers. This is a new bit of functionality that can be baked right into the binding assembly.

Working with the Native Library (.a)

This step is actually quite easy, just add the native library (.a) to the project, and you are almost done.

When you add the native library to the project, Xamarin Studio will create a new .linkwith.cs file that matches the library name. For example with Flurry, I added the libFlurry_5.2.0.a library to my project and it generated a libFlurry_5.2.0.linkwith.cs.

This new file is the last bit that needs to be done. It is for the linker when using this library in an iOS app. It usually will contain only a single line, a single [LinkWith] attribute. This attribute allows you to specify what targets this library can be used with, what extra iOS frameworks are needed, etc:

    [assembly: LinkWith (
        "libFlurry_5.2.0.a", 
        LinkTarget.Simulator | LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.ArmV7s, 
        ForceLoad = true)]

This is the default line, split for readability, and usually it is all that is needed. What this does is say that the linker must use the libFlurry_5.2.0.a native library as well as this library can be used on the Simulator and all the devices. The ForceLoad is used by the Xamarin.iOS linker, and specifies a linker flag. According to the documentation, this should always be true for now.

But just before we finish up, we need to add any required frameworks. Without this, there will be strange happenings…but different depending the configuration.

If building for Debug, the app will launch, but nothing will happen at all. The methods can be called and everything, but no actions will take place.

If building for Release, the linker will throw an error:

Error MT5211: Native linking failed, undefined Objective-C class: _OBJC_CLASS_\$_Flurry.
If ‘_OBJC_CLASS_\$_Flurry’ is a protocol from a third-party binding, please check that it has the [Protocol] attribute in its api definition file, otherwise verify that all the necessary frameworks have been referenced and native libraries are properly linked in.

This error is giving us lots of information for @protocol, but as we don’t have any in our project (recall the @interface), we can safely ignore the first part of the second message and focus on the ‘frameworks’ part. According to the Flurry documentation, the Security.framework is required and the SystemConfiguration.framework is optional, but recommended.

So, I added both frameworks, using the Frameworksproperty:

    [assembly: LinkWith (
        "libFlurry_5.2.0.a",
        LinkTarget.Simulator | LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.ArmV7s, 
        ForceLoad = true, 
        Frameworks = "SystemConfiguration Security")]

As shown here, the frameworks are space-separated and do not have the .framework extensions.

Finishing Up

So far we have defined our API, added any additional logic or types, added the native library, set up the linker and specified the frameworks. This is all that is needed, so our library should build fine now and we should be able to use it in an Xamarin.iOS app:

    // the methods
    FlurryAgent.StartSession("PQSZJRK4B5BW8Q7YQQXF");

    // the properties that we changed back into methods
    FlurryAgent.GetFlurryAgentVersion ();

    // the extra method that we added
    FlurryAgent.LogTimedEvent ("started", () => {
        // ...
    });

Under the Hood

One thing to know is that the iOS binding project is actually not really compiled directly, but are a series of stages. Each file actually has a different build action.

The first stage to building the binding is to build a reference assembly with the files:

  • ApiDefinition.cs (with build action ObjcBindingApiDefinition)
  • StructsAndEnums.cs (with build action ObjcBindingCoreSource)

Next, this assembly is then reflected to generate the actual binding code. This generated code is then compiled with the rest of the files:

  • Extras.cs (with build action Compile)
  • StructsAndEnums.cs (again with build action ObjcBindingCoreSource)
  • libFlurry_5.2.0.linkwith.cs (with build action Compile)
  • libFlurry_5.2.0.a (with build action ObjcBindingNativeLibrary but is actually just a resource)

An example of the generated code in the second stage, the interface member void StartSession(string) is actually used to generate a big block of code:

    [Export ("startSession:")]
    [CompilerGenerated]
    public static void StartSession (string apiKey)
    {
        if (apiKey == null)
            throw new ArgumentNullException ("apiKey");
        var nsapiKey = NSString.CreateNative (apiKey);

        MonoTouch.ObjCRuntime.Messaging.void_objc_msgSend_IntPtr (class_ptr, selStartSession_Handle, nsapiKey);
        NSString.ReleaseNative (nsapiKey);
    }

This is all the magic that happens under the hood to redirect values from the managed .NET to the underlying native library.