This was a simple app that lets the user scan a bar-code, take a photo then upload both the photo and bar-code to a website, it was an interesting first app.

Coming from the world of Xamarin iOS development there was a bit of culture shock with Activities and Fragments compared to view controllers. Plus if you want to access a form control you have to find it. Every control ends up as a constant in a large compiler generated file Resource.Designer.cs and to use objects mapped to them you need code like this. The controls objects are declared elsewhere.

            
            ScanButton = FindViewById<Button>(Resource.Id.barcodeButton);
            Barcode = FindViewById<TextView>(Resource.Id.barcode);
            StatusView = FindViewById<TextView>(Resource.Id.status);
            ImageView = FindViewById<ImageView>(Resource.Id.imageView1);
            PhotoButton = FindViewById<Button>(Resource.Id.photoButton);
            UploadButton = FindViewById<Button>(Resource.Id.uploadButton);
            SettingsButton = FindViewById<ImageButton>(Resource.Id.settingsButton);

Scanning Barcodes

The first big problem was bar-code scanning but the excellent open source Apache Licensed Zxing mobile barcode scanner component solved that. Tips for working with that, get a 5″ screen phone and scan under good light. I’m very impressed with that.

Taking a photo wasn’t rocket science, there’s plenty of source for that around. But photos that are large (typically 3,000 x 2,000 pixels) need to be reduced and converted. I set a maximum largest dimension of 750 and ran this loop to calculate the new size.

                
                float height = App.Height;
                float width = App.Width;
                float ratio = height/width;
                while (height > 750 || width > 750)
                {
                    height -= 100;
                    width = height/ratio;
                }

then this code to rescale the bitmap and compress to a jpg with a quality of 80.

                
                var reducedBitmap = Bitmap.CreateScaledBitmap(App.bitmap, (int)width, (int)height,false);
                StatusView.Text = "Processing Image";
                var stream = new MemoryStream();
                reducedBitmap.Compress(Bitmap.CompressFormat.Jpeg, 80, stream);
                var bitmapData = stream.ToArray();

Leaving me with an array of bytes to upload. Sending a string and an image together in one http post is a little bit harder. Here’s how I did it. I found Brian Grinstead’s post and used his FormUpload class.

                var postParameters = new Dictionary<string, object>();
                postParameters.Add("barcode", Barcode.Text);
                postParameters.Add("file", new FormUpload.FileParameter(bitmapData, "image.jpg", "image/jpg"));

                // Create request and receive response
                var postURL = UploadUrl;
                var userAgent = "Someone";
                var webResponse = FormUpload.MultipartFormDataPost(postURL, userAgent, postParameters);

As easy as that!

XML

Unless you want to write it in code, you’ll find that you use XML for lots of things- the Android Manifest file, the layout of each form, strings, styles and preferences. Plus if you are targetting specific versions, screen sizes, you’ll have versions for that as well. It can add up to a lot of XML files. The layout alone is 65 lines long for just three buttons, one image button, one image view plus a couple of labels.

Screen shot of App

Debugging on Android

You have a choice of running the Xamarin simulator in VirtualBox which is ok but doesn’t really work well for scanning barcodes or taking photos so I found the USB device drivers for two handsets I had and debugged on the phones. For some phones the drivers are easy to find, others not so. Once they’re installed, ADB (Android Debug Bridge) is your friend to get going and after that just plug the phone in and Xamarin picks it up.

I found a slight bug and I’m not sure if it’s Xamarin or the phone’s drivers. I’d make a change do a rebuild and deploy and even though it said the previous version was removed it ran the previous version. Uninstalling the App fixed that but it only happened on three occasions so not a big deal.

Distribution

This wasn’t for the Google Play store but a private client. All I had to do was build for Release then click Export Android Package on the build menu. That creates two .apk files and I uploaded the one that had signed in its name.

Did you know that an .apk file like a java .jar file is actually a zip file? Just rename the extension to .zip and you can see everything in it. Note that the XML files may be XML binary files but there are tools to convert them to text XML files.

I’ve been converting a major App and the Migrate tool works very well but I found a couple of cases, which either it didn’t do or I messed up.

The first is when using a nint as an index in a List. It doesn’t work, as the .NET generics know nothing about nints. So just cast it to an int like this below where _itemsList is a List

Text = _itemsList[(int)row];

Converting NSDate to System.DateTime and vice versa

It’s now a compile error to assign a DateTime to a NSDate or vice versa, the implicit conversion no longer works. I think this is to highlight a subtle bug to do with NSDate is always an UTC time and DateTime is default set to DateTimeKind.Unspecified (when read from database) or DateTimeKind.Locale (when set with DateTime.Today) Source is this stackoverflow answer.

Thankfully the Foundation.NSDate class includes two explicit operators that do the job for you.
NSDate in Xamarin Object Browser


So this works.

            NSDate nDate = (NSDate) DateTime.Now;
            DateTime dDate = (DateTime) nDate;

AVAudioRecorder Changes

The settings used to create an AVAudioRecorder have changed from a NSDictionary to AudioSettings, and the AVAudioRecorder.ToUrl method no longer exists.

Instead create avsettings like this and use the factory method Create to create the AVAudioRecorder.

//Set Settings with the Values and Keys to create the NSDictionary
            avsettings = new AudioSettings(NSDictionary.FromObjectsAndKeys(values, keys));

//Set recorder parameters
            recorder = AVAudioRecorder.Create(url, avsettings, out error);

It’s kind of obvious in retrospect about enabling or disabling animation. It’s part of some code where I’ve added a UIImage onto a MkMapView (Apple’s iOS Controls for displaying a map), that centres the map on your current location when you click it. The button is a gun sight type icon and I’ve located in the bottom right edge of the map.

    private UIButton btnCurrentLoc; // defined at the class level

// in ViewDidLoad
    var ImageCurrentLoc = UIImage.FromBundle("images/currentloc.png");
    ImageCurrentLoc.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);

    btnCurrentLoc = new UIButton() { TintColor = UIColor.Black };
    btnCurrentLoc.SetImage(ImageCurrentLoc, UIControlState.Normal);
    btnCurrentLoc.Frame = new RectangleF(View.Frame.Width-50, View.Frame.Height - 100, ImageCurrentLoc.Size.Width, ImageCurrentLoc.Size.Height);

    btnCurrentLoc.TouchUpInside += (s, e) =>
                {
                    map.SetCenterCoordinate(map.UserLocation.Location.Coordinate,true);  // animated
                    //map.CenterCoordinate = map.UserLocation.Location.Coordinate;       // not animated, moves directly            
                }; 

  View.AddSubview(map);            // has to be this way round! map first then control
  View.AddSubview(btnCurrentLoc);

Note that you add the map first to the view then the button.

As it’s commented if you click the btnCurrentLoc button, it will animate the map so you can see it scroll to your location. If you comment out that line and uncomment the line below, when you click the button it will move there immediately.

iPhone MkMapView with home button

I wanted a RadioButton type of effect with three icons. Click one of the other three and it is set to blue and the other two are coloured red. It’s actually very easy once you know the trick. You have to tell iOS 8 that the each icon image is rendered AlwaysOriginal. UIImageRenderingMode is an enum with three values (AlwaysOriginal, AlwaysTemplate and Automatic).

To display the Icon in a colour, set the RenderingMode to AlwaysOriginal then create a UIButton from it and set the TintColor to whatever you want it to be. Image1 etc are declared at the class level as UIImage, btnOne etc is a UIButton and tbar is a UIToolbar.

  tbar = new UIToolbar(new RectangleF(0, 30, 320, 44)); // hard coded values for toolbar size/location
  View.AddSubview(tbar);

  var Image1 =UIImage.FromBundle("images/image1.png");
  Image1.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
  btnOne = new UIBarButtonItem { Image = Image1, Title = "First Button", TintColor = UIColor.Blue };

// Repeat for button2, 3 etc
//  Add a click handler to each button
   btnOne.Clicked += (s, e) =>
            {
                ClearTints(); // three line method sets all buttons to red
                btnOne.TintColor = UIColor.Blue;
            };
tbar.SetItems(new []{btnOne,btnTwo,btnThree},false );

This shows three icons before and after the second one is selected.

Three icons before and after selection

iOS 8 Deprecated quite a few things- for instance if you did a map search using the Xamarin MapDemo, it uses a UISearchDisplayController but that’s now deprecated and you are meant to start using UISearchController instead.

Knowing which version of iOS your App is running on then becomes important. While I can use the older UISearchDisplayController on iOS 8, I can’t use the newer UISearchController on iOS 7.

Now I’ve been previously using this property to check if the OS is a certain version.

        public bool IsiOS8
        {
            get { return UIDevice.CurrentDevice.CheckSystemVersion(8, 0); }
        }

But I think it’s better to check if a version is >= 7 and for that purposes the MonoTouch.Foundation class provides NSProcessInfo() with it’s OperatingSystemVersion and Major and Minor properties. Note I’ve also changed IsiOS8 from a property to a function.

        public static bool IsiOS8()
{
return (new NSProcessInfo().OperatingSystemVersion.Major >= 8);
}

If you’ve ever wanted a light coloured text status bar, you may have experienced difficulties. If you have the current VS Plugin with current being (in iOS 8.1), Xamarin Studio 5.5.3. My VS plugin 8.4.0.0 and Windows Xamarin 3.7.23.0, then you’ll see you don’t have the choice of picking it in the info.plist editor. The choices are Default, Black Transparent and Black Opaque.

Those last two choices are now deprecated and there should be a Light Content. This changed in iOS 7.0 (see the iOS 7.0 transition guide).

For the fixes, I recommend that you edit info.plist with anything but the VS plugin, at least until they fix it. I use programmers notebook 2 which is excellent.

You need to add the following lines in info.plist:

	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>

and in AppDelegate.cs you need to add the following line. I’ve shown it bold and in my code I’m using Tabs so create a Tab Controller (my own class) rather than the usual UIViewController.

            tabController = new TabController();
    UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, false);
	    UIApplication.SharedApplication.SetStatusBarHidden (false, false);
            window.RootViewController = tabController;
            window.MakeKeyAndVisible();
            return true;

If you have a launch image you’ll probably want the status bar hidden during launch. If you had previously ticked the hide during application launch checkbox, it adds the following into info.plist.

	<key>UIStatusBarHidden</key>
	<true/>

What this also does with the Light Content fix is keep the status bar hidden so add the second line above (SetStatusBarHidden) as well to unhide it.

To try this fix out, download the code from here :Xamarin Sample for Tab Bars.

Next change the TabBarController so a tab or two is displaying dark colours.

			tab1 = new UIViewController();
			tab1.Title = "Gray";
			tab1.View.BackgroundColor = UIColor.Gray;

and add the one or two lines above to AppDelegate.cs

I’ve not mastered AutoLayout, but if you want to resize a label, there’s a few of things you need to do. The image below shows a slider used to control a font size.

 

Resizing a label with a slider

Here I’ve omitted the declarations but fontlabel is a UiLabel, Support.FontSize is a float, and fontSlider is a UISlider. This is the code.

            fontlabel = new UILabel()
            {
                Text = @"Font Size",
                Lines = 1,               
                TextColor = Support.uTextColor,
                BackgroundColor = Support.BackColor,
                LineBreakMode = UILineBreakMode.WordWrap,
                Frame = new RectangleF(10, 320, Frame.Width - 20, 30),
                Font = UIFont.FromName("Helvetica", Support.FontSize)
            };
            fontlabel.SizeToFit();
            AddSubview(fontlabel);
            fontslider = new UISlider(new RectangleF(5, 350, Frame.Width - 5, 30))
            {
                MinValue = 11f,
                MaxValue = 30f,
                Value = Support.FontSize
            };
            fontslider.ValueChanged += HandleFontSliderChanged;

        private void HandleFontSliderChanged(object sender, EventArgs e)
        {
            Support.FontSize = fontslider.Value;
            fontlabel.Font = UIFont.FromName("Helvetica", Support.FontSize);
            fontlabel.SizeToFit();
        }

Allow enough room for the label to fit at its largest size and having changed the font size in the Slider event, call SizeToFit() on the label. Otherwise you’ll find that the text is truncated when the label is made larger.

picture of emailed-log in gmailThis is a screenshot of a log viewed in Gmail.

This is a follow up to the post So I rolled my own logger. In that post I provided a simple logger class.

In this post I use http to send all the logs rolled up into one text file to a php script which emails the file to me. Remember, iOS does not allow you to send email programmatically.

        // helper function to retirn path
        public static string GetLocalFilesPath()
        {
            var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            return Path.Combine(documents, "..", "Library", "Caches");      
        }

    public static void EmailLogs()
    {            
        const string url = "http://example.com/email.php";
        var sb= new StringBuilder();
       
        try
        {
            Support.log.Pause();
            var dirInfo = new DirectoryInfo(Support.GetLocalFilesPath());
            var lstFiles = dirInfo.GetFiles("*.log");
            foreach (var info in lstFiles)
            {
                sb.AppendLine("-------------------------");
                var fInfo = info; // avoids closure bug
                sb.AppendLine(fInfo.Name);
                using (var sr = new StreamReader(fInfo.FullName))
                {
                    sb.Append(sr.ReadToEnd());
                }
            }
            var msg = WebUtility.UrlEncode(sb.ToString());
            var fullMsg = "z=xyz23&mid="+UNIT.MobileID+@"&alphasid="+UNIT.SystemID+@"&lui="+msg;
            Task.Factory.StartNew(() => HttpPost(url, fullMsg), TaskCreationOptions.LongRunning);
           
            foreach (var info in lstFiles)
            {
                info.Delete();
            }            
        }
        catch (Exception)
        {

        }
        finally
        {
            
        }            
        return; 
    }

Support is a class that includes a static instance of the logger class. This is a wrapper round a StreamWriter so the pause method closes the StreamWriter otherwise we’d be tring to read from a file that[‘s open in the StreamWriter. After the logs have been sent off the logger is restarted.

This rolls up all the logs it finds into one file using StringBuilder to append the file name and then each line in that log then url encodes it and calls the function httppost below which I found on Microsoft’s site

Note that for security I add in a few parameters. (MobileId and SystemID are static properties from another unit; I’ve changed that to UNIT as my unit name would be meaningless.) Once again I use fire and forget for calling httppost. Ie run it in another Task using the TPL. I’m deliberately not checking any error messages.

    public static string HttpPost(string url, string parameters)
    {
        try
        {
            //Create a WebRequest
            var req = (HttpWebRequest)WebRequest.Create(url);

            //Set the content type and method
            req.ContentType = "application/x-www-form-urlencoded";
            req.Method = "POST";

            //Get the total size of the post parameters and set the content length
            var bytes = System.Text.Encoding.UTF8.GetBytes(parameters);
            req.ContentLength = bytes.Length;

            //Write the data to the request stream
            Stream os = req.GetRequestStream();
            os.Write(bytes, 0, bytes.Length);
            os.Close();

            //Get the response
            WebResponse resp = req.GetResponse();
            if (resp == null) return null;

            //Get the response stream and read the response
            var sr = new StreamReader(resp.GetResponseStream());
            string result = sr.ReadToEnd().Trim();

            //Close the streams
            sr.Close();
            resp.Close();

            return result;
        }
        catch (Exception)
        {
            //Epic fail...
            return null;
        }
        finally
        {
            Support.log = new Logger();
        }
    }

Finally I have a small PHP script setup on a domain hosted on a shared server. Note the use of security parameters to stop spammers using this script to bombard you with spam. The parameters below should match the parameters in this line from above:

var fullMsg = "zvf=xyz23&mid="+UNIT.MobileID+@"&alphasid="+UNIT.SystemID+@"&lui="+msg;

This is the PHP script below, after validating that it’s a genuine request, it takes the three parameters, appends the log file to a local text file then emails it to the specified email address.

I have the error messages commented out as they’re only there for debugging purposes.

There are plenty of logging systems about for .NET. This site although it’s an advert for a .NET logging system has links and comparisons with many.  But sometimes all you need is a very simple logger, something as simple as appending a string to a text file. So here’s a 64 line logger that creates a dailyfile – for today it’s 20131004.log.

using System;
using System.IO;
using System.Threading.Tasks;

namespace TMRTaxi
{
    class Logger : IDisposable
    {
        public string FileName { get; set; }
        private StreamWriter sw;

        public Logger()
        {
            var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            var cache = Path.Combine(documents, "..", "Library", "Caches");
            var logfile = Path.Combine(cache, @"log" + DateTime.Now.ToString("yyyymmdd") + ".log");
            FileName = logfile;
            var str = "Logging Startedn";

            sw = new StreamWriter(FileName, true);
            Log(str);

        }

        public void Pause()
        {
            if (sw != null)
            {
                sw.Flush();
                sw.Close();
                sw = null;
            }            
        }

        public void Log(string msg,string comment="")
        {
            if (sw == null)
                return;
            msg = DateTime.Now.ToString("T") + " : "+msg+(comment != ""?" ["+comment+"]":"");
            Task.Factory.StartNew(()=> sw.WriteLine(msg), TaskCreationOptions.LongRunning);           
        }

        public void Dispose(Boolean disposing)
        {
            if (sw != null)
            {
                sw.Close();
                sw = null;
            }    
        }

        public void Dispose()
        {
            Dispose(true); //i am calling you from Dispose, it's safe
            GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
        }

        ~Logger()
        {
            Dispose(false); //i am *not* calling you from Dispose, it's *not* safe
        }
    }
}

It could probably do with a bit of extra error checking. A try catch round the sw = new StreamWriter(FileName, true); line for instance.

These lines:

var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            var cache = Path.Combine(documents, "..", "Library", "Caches");
            var logfile = Path.Combine(cache, @"log" + DateTime.Now.ToString("yyyymmdd") + ".log");

create the log file in the caches folder.

This line Task.Factory.StartNew(()=> sw.WriteLine(msg), TaskCreationOptions.LongRunning); in Log() does a fire and forget. It runs it using the TPL (Task Parallel library). It seems to work fine.

Just creature a logger with

log = new Logger();
log.Log("App starting up");

Plus there’s an optional comment on the Log method.

Of course it would be nice if you can email the logs to yourself but programmatically that’s a no no. “Apfel verbietet es” as my German teacher might have said. (“Apple forbids it.”).

The workaround is to build a large text file from all the available log files (and remove them afterwards) then post it to a php script on a website and it emails it. I’ll post the C# code for that in a day or two.

Here’s proof- well it works in the simulator anyway!

ios-simulator