Friday, November 8, 2013

iOS loading view

Xamarin nicely provides a loading view to be used while the app is doing background processing.  I found this and was quite happy I didn't have to figure out how to do it myself.  I then proceeded to translate it to F# like so:

type LoadingOverlay(frame: RectangleF) as this  =
    inherit UIView(frame)
    let labelHeight = 22.0f
    let labelWidth = frame.Width - 20.0f
    let centerX = frame.Width / 2.0f
    let centerY = frame.Height / 2.0f
    let activitySpinner = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.WhiteLarge)
    let loadingLabel = new UILabel(new RectangleF (centerX - (labelWidth / 2.0f),
                                                   centerY + 20.0f ,
                                                   labelWidth ,
                                                   labelHeight))
    do this.BackgroundColor <- UIColor.Black
       this.Alpha <- 0.75f
       this.AutoresizingMask <- UIViewAutoresizing.FlexibleDimensions
       activitySpinner.Frame <- new RectangleF (centerX - (activitySpinner.Frame.Width / 2.0f) ,
                                                centerY - activitySpinner.Frame.Height - 20.0f ,
                                                activitySpinner.Frame.Width ,
                                                activitySpinner.Frame.Height)
       activitySpinner.AutoresizingMask <- UIViewAutoresizing.FlexibleMargins
       this.AddSubview (activitySpinner)
       activitySpinner.StartAnimating ()
       loadingLabel.BackgroundColor <- UIColor.Clear
       loadingLabel.TextColor <- UIColor.White
       loadingLabel.Text <- "Loading Data..."
       loadingLabel.TextAlignment <- UITextAlignment.Center
       loadingLabel.AutoresizingMask <- UIViewAutoresizing.FlexibleMargins
       this.AddSubview (loadingLabel)
    
    member public this.Hide () =
        UIView.Animate(0.5, (fun() -> this.Alpha <- 0.0f), fun() -> this.RemoveFromSuperview())

And then tried it out:



This looks very nice until you try to reuse it.  It only works once… After looking at the Hide code, it doesn't take long to realize that the the Alpha is getting set to 0 and never changed thereafter (it's initially set to 0.75 in the constructor).  This is simple to fix, just add this.Alpha <- 0.75f to the callback passed to UIView.Animate.  Voila, now I have a LoadingOverlay which works multiple times.

Next, I rotated the iPad simulator and tried again.


Oops, still not quite right.  When the view is added to the parent view (in the Xamarin example), the bounds of the parent are passed to the view (loadingOverlay = new LoadingOverlay (UIScreen.MainScreen.Bounds); ) and then the frame of the LoadingOverlay is set with to it, so the size is appropriately set initially.  Also, as is needed, the AutoresizingMask of the view is set like this: this.AutoresizingMask <- UIViewAutoresizing.FlexibleDimensions so that shouldn't be a problem.   What then is the issue?

The problem lies here: fun() -> this.RemoveFromSuperview() .  After the view is dismissed, it is removed from the parent view's hierarchy, so when the parent view rotates, it doesn't rotate the LoadingOverlay because it doesn't own it any longer.  This is easy to fix, simply use this.Hidden instead of removing the view from it's parent.  That way when the parent resizes, the LoadingOverlay is  still part of the hierarchy.  The final class for the view looks like this:

type LoadingOverlay(frame: RectangleF) as this  =
    inherit UIView(frame)
    let labelHeight = 22.0f
    let labelWidth = frame.Width - 20.0f
    let centerX = frame.Width / 2.0f
    let centerY = frame.Height / 2.0f
    let activitySpinner = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.WhiteLarge)
    let loadingLabel = new UILabel(new RectangleF (centerX - (labelWidth / 2.0f),
                                                   centerY + 20.0f ,
                                                   labelWidth ,
                                                   labelHeight))
    do this.Hidden <- true
       this.BackgroundColor <- UIColor.Black
       this.AutoresizingMask <- UIViewAutoresizing.FlexibleDimensions
       activitySpinner.Frame <- new RectangleF (centerX - (activitySpinner.Frame.Width / 2.0f) ,
                                                centerY - activitySpinner.Frame.Height - 20.0f ,
                                                activitySpinner.Frame.Width ,
                                                activitySpinner.Frame.Height)
       activitySpinner.AutoresizingMask <- UIViewAutoresizing.FlexibleMargins
       this.AddSubview (activitySpinner)
       activitySpinner.StartAnimating ()
       loadingLabel.BackgroundColor <- UIColor.Clear
       loadingLabel.TextColor <- UIColor.White
       loadingLabel.Text <- "Loading Data..."
       loadingLabel.TextAlignment <- UITextAlignment.Center
       loadingLabel.AutoresizingMask <- UIViewAutoresizing.FlexibleMargins
       this.AddSubview (loadingLabel)
    
    member public this.Hide () =
        UIView.Animate(0.5, (fun() -> this.Alpha <- 0.0f), fun() -> this.Hidden <- true)
    
    member public this.Show () =
       this.Hidden <- false
       UIView.Animate(0.5, (fun() -> this.Alpha <- 0.75f))

        

To use it, create the LoadingOverlay in the contractor of the parent, keep a reference to it, add it to the parent view in ViewDidAppear and then use Show/Hide to show and hide the view.

Wednesday, October 30, 2013

MonoTouch.Dialog From F#

I'm currently evaluation Xamarin as a replacement for a Phonegap application.  The first thing I'm evaluating is building UIs in code.  Since most of the example code is in C#, I initially built my test UI with it.  In poking through the Xamarin documentation I came across the MonoTouch.Dialog toolkit (hereafter MT.D).  MT.D is a declarative framework for creating iOS UIs.  Here is the code for the MasterViewController of a UISplitView (this is a minor modification to the code found in the Xamarin documentation):

using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using MonoTouch.Dialog;
using System.Linq;

namespace CSharpTabbed
{
    public partial class MasterViewController : DialogViewController {
        Section testSection;
        public MasterViewController () : base (null)
        {
            Root = new RootElement ("Items") {
                new Section(){
                    new StringElement("Section 1"),
                    from num in Enumerable.Range(1,10)
                        select new ImageElement(new UIImage("first.png"))
                },
                new Section(){
                    new StringElement("Section 2"),
                    new Section(){
                        from num in Enumerable.Range(1,10)
                            select new StringElement("Item" + num})
                    }
                },
                new Section(){
                    new StringElement("Section 3"),
                    new Section(){
                        from num in Enumerable.Range(1,10)
                            select new StringElement("Item" + num)
                    }
                }
            };
        }

        public override bool ShouldAutorotateToInterfaceOrientation
            (UIInterfaceOrientation toInterfaceOrientation)
        {
            return true;
        }
    }
}


And the resulting UI:



Since this requires the construction of a lot of collections I thought I would try it in F#.  Here is my direct conversion from C# to F#:

 1: namespace GoFormz
 2: 
 3: open System
 4: open System.Drawing
 5: open MonoTouch.Foundation
 6: open MonoTouch.UIKit
 7: open MonoTouch.Dialog
 8: open System.Linq
 9: 
10: [<Register ("MasterViewController")>]
11: type MasterViewController (window:UIWindow) as this =
12:     inherit DialogViewController (new RootElement("Items"))
13: 
14:     do this.Root.Add [new Section(Elements = new ResizeArray<Element>( [yield new StringElement("Section1") :> Element;
15:                                                                         for i in 1..10 -> new StringElement("num"+i.ToString()) :> Element]));
16:                       new Section(Elements = new ResizeArray<Element>( [yield new StringElement("Section1") :> Element;
17:                                                                         for i in 1..10 -> new StringElement("num"+i.ToString()) :> Element]));
18:                       new Section(Elements = new ResizeArray<Element>( [yield new StringElement("Section1") :> Element;
19:                                                                         for i in 1..10 -> new StringElement("num"+i.ToString()) :> Element]))];
20: 
21:     override this.ShouldAutorotateToInterfaceOrientation toInterfaceOrientation =
22:         true
namespace GoFormz
namespace System
namespace System.Drawing
namespace System.Linq
type MasterViewController =
  class
    inherit obj
    new : window:'a -> MasterViewController
    override ShouldAutorotateToInterfaceOrientation : toInterfaceOrientation:'a -> 'b
  end

Full name: GoFormz.MasterViewController
val window : 'a
val this : MasterViewController
type ResizeArray<'T> = Collections.Generic.List<'T>

Full name: Microsoft.FSharp.Collections.ResizeArray<_>

  type: ResizeArray<'T>
  implements: Collections.Generic.IList<'T>
  implements: Collections.Generic.ICollection<'T>
  implements: seq<'T>
  implements: Collections.IList
  implements: Collections.ICollection
  implements: Collections.IEnumerable
override MasterViewController.ShouldAutorotateToInterfaceOrientation : toInterfaceOrientation:'a -> 'b

Full name: GoFormz.MasterViewController.ShouldAutorotateToInterfaceOrientation

This isn't really any better than the C# due to all the casting and the need to interface with Collections.Generics.List.  It would also be nice if I could use any collection type I want.  To address these issues, I created a library which allows more idiomatic F# usage of MT.D.  Usage looks like this:

1: do this.Root.Add [createSection [yield createStringElement "Section1";
2:                                  for i in 1..10 -> createStringElement ("num"+i.ToString())];
3:                   createSection [yield createStringElement "Section2";
4:                                  for i in 1..10 -> createStringElement ("num"+i.ToString(), "value")];
5:                   createSection [yield createStringElement "Section3";
6:                                  for i in 1..10 -> createStringElement ("num"+i.ToString(), fun ()->Console.WriteLine "test")]]


This gives compile time checking without having to use a lot of type annotations while still maintaining type safety.

In progress library here.