Smart Watch, Dumb Phone?

So, it seems Apple is finally ready to launch the iWatch – I am probably in the market for one, if for no other reason than the geek factor of it, though I can’t help but think that these things keep doing it ass-backwards.

How is it a “Smart” watch if it is nothing but a dumb accessory to the “Smart” phone. For it to be “Smart” it has to be the other way around. The watch has to be the phone, and the phone has to be the accessory, otherwise it’s just one more gadget I need to remember.

Done right, the Smart Watch is a communication hub with just enough functionality for me to be able to leave the increasingly large, clumsy, battery eating brick of a phone at home.

It should:

  1. Tell the time (wee 🙂 )
  2. Do phone calls
  3. Play music
  4. Check/browse e-mail
  5. Follow twitter and other social media updates
  6. Show my calendar
  7. Maybe even basic GPS / location tracking
  8. Possibly simple home automation
  9. (Oh, and the health thing, for those who find that useful 🙂 )

It does not require a massive CPU or GPU or a big screen but it has to have 3G and WiFi. When I want to play a game, browse the internet, write emails and need the screen real-estate, horsepower, or a semi-proper keyboard, I can turn my watch into a hotspot and connect my (3G-less) “phone” to it.

In fact, I’d probably just connect my laptop or my iPad to the watch and ditch the phone entirely. I don’t really need the phone – it’s a silly device – too small to be useful and too big to drag around all the time.

Unity GameCenter Support

I’ve spend the last couple of days implementing GameCenter support in a Unity game and found more than a few pitfalls all of which goes back to a seriously broken Unity API.

So, if you are planning on using the Social API in Unity3D, here’s a couple of landmines you may not want to step on (I’m sure there are others, but these are the ones that I wasted time on) – oh, and just for the record

1) I love Unity – it’s an awesome platform. It’s just their GameCenter implementation of the Social API that could do with a bit of love 🙂

2) These issues were found in Unity 4.5 (but Google suggests they have been there forever, so I would not count on them being fixed anytime soon)

Loading scores

The first thing that bit me is that ILeaderBoard.LoadScores only supports a single request at a time. If you try to call it multiple times, only the last invocation will receive a callback (several times, whatever good that will do you). I implemented the following helper to work around the issue:

  struct LoadScoreRequest
  {
    public string            _id;
    public string[]          _userIds;
    public BoardLoadedEvent  _callback;
  }

  public delegate void BoardLoadedEvent(ILeaderboard board, HighScore[] highScore);
  
  private bool _friendsLoaded;
  private bool _loadScoreRequestPending;
  private List<LoadScoreRequest> _loadScoreRequests = new List<LoadScoreRequest>();

  public void CreateAndLoadLeaderBoard(string id, string[]userids, BoardLoadedEvent ondone)
  {
    lock(_loadScoreRequests)
    {
      _loadScoreRequests.Add( new LoadScoreRequest() { _id=id, _userIds = userids, _callback = ondone }  );
      SendNextScoreRequest();
    }
  }

  private void SendNextScoreRequest()
  {
    LoadScoreRequest req;
    lock(_loadScoreRequests)
    {
      if(!_friendsLoaded)
        return;

      if(_loadScoreRequestPending)
        return;

      if(_loadScoreRequests.Count==0)
        return;

      _loadScoreRequestPending = true;
      req = _loadScoreRequests[0];
      _loadScoreRequests.RemoveAt(0);
    }

    if(req._userIds==null)
    {
      req._userIds = new string[Social.localUser.friends.Length];
      int i=0;
      foreach(IUserProfile friend in Social.localUser.friends)
      {
        req._userIds[i++] = friend.id;
      }
    }
    
    ILeaderboard board = Social.CreateLeaderboard();
    board.id = req._id;
    board.SetUserFilter(req._userIds);

    board.LoadScores( (bool scoresloaded) =>
    {
      req._callback(board);
      lock(_loadScoreRequests)
      {
        _loadScoreRequestPending = false;
        SendNextScoreRequest();
      }
    });
  }

Basically, it queues up all requests until the GameCenter authentication has been completed and the friends list has been loaded. It then starts loading the leaderboards one at a time, making sure each board gets returned to the proper callback.

The authentication code is not shown above, but it’s straight forward and simply sets the _friendsLoaded member to “true” after loading the friends list and then calls SendNextScoreRequest in case any requests got queued up while authenticating.

Saving scores

IScore.ReportScore does not work.

At least, I could not get it to work. Use Social.ReportScore() instead as it seems to work as advertised, and has the added advantage that you don’t need to carry around the ILeaderboard reference. Why the broken method is even in the public API I can only guess at.

Error handling

Basically, there isn’t any.

At least, you can forget about checking the boolean “success” parameter provided in the various callbacks. I’ve never seen this have a value other than “true” despite a myriad of issues, none of which I’d personally characterise as a “success”.

Instead, check that the data you expected is available – for example, that the user- or highscore-lists are not empty.

UPDATE & WARNING:

Be really careful with Social.LoadAchievementDescriptions – it will load the associated images for all achievements and keep them in memory. They will not be released again.

In my case it amounted to 12 images of 1024×1024 pixels which, with some overhead of unknown origin, amounted to 96MB each time the game refreshed the achievements list.

There’s a similar problem with loading user profiles – their profile images are also kept in memory and are not released when you let go of the profile itself.

Throw to hit

Most people probably don’t consider the intricacies of computer-game enemies shooting at them. In many cases rightfully so, since it’s mostly a very simple feature. The game code to send a fast moving projectile towards you rarely involves more than a subtraction of two points to get a direction, and multiplying with whatever speed bullets happen to travel at in the given game. If it’s fast enough, it’ll hit you. Other times, bullets are instant hit and the game simply checks for intersection with a line segment – no chance of escaping that one either.

In the game I’m currently working on, however, most of the enemies don’t shoot bullets; they throw water balloons. Just as in real life, timing and aiming a throw is quite a bit more involved than firing a bullet, and while most of us learn this as kids by trial and error, a game usually solves it with a bit of math.

It really isn’t much more complicated than the two scenarios I just described, but it does involve matching a polynomial with a linear interpolation, so if both of those sound alien to you, and you happen to be faced with this problem, read on :).

The Stage

Before we look at the math, here’s a quick overview of the problem we’re faced with:

Throwing B at P

Throwing B at P

Let’s say we’re the enemy and we’re at location B0, throwing at the player who is currently at P0. What we want is to find the velocity F that our projectile B must travel with so that it will impact with P when the gravity (g) pulls the projectile to the ground. N is the current velocity of the player.

The path of the projectile is given by the second order polynomial

B = B0 + F*t + G*t²

The path of the player is given by the linear equation

P = P0 + N*t

Assuming, of course, that he does not change direction or speed – which is kind of the whole point of the game 🙂

Before we try to solve this, note that the G vector is special in that it only affects one axis. This simplifies matters a great deal since our second-order polynomial then only exist for this axis, and hence, we can separate the motion in the X/Z plane from that of the Y axis. Or more specifically, we can ignore X/Z and pick a velocity in Y that creates a nice curve and gives us a time of impact, and then adjust our projectile speed in X/Z to match the players location at the calculated time.

So let’s look at the equation for Y:

B0.y + F.y*t + G.y*t² = P0.y + N.y*t

For some rather game-specific reasons, I don’t care if the player moves in Y, so my N.y is always 0. It should be straight forward to change the code to take this into account, you just need to carry the N.y term along. Anyway, in my case, I can simplify to:

G.y*t² + F.y*t + (B0.y -P0.y)  = 0

Which is the 2nd order polynomial we need to solve to determine our time of impact, t. If this doesn’t ring any bells, just google it, there’s a text-book solution for this, and chances are you’ve solved a million of these in school and just forgotten about it ;). Below I’ll just go through the code.

Code

This is what a prettified version of my implementation looks like:

Vector3 P0 = player.transform.position;
Vector3 B0 = enemy.transform.position + initialOffset;
Vector3 F  = (P0-B0);
float l = F.magnitude;
          
if(l>1)
{
  F.y = l*.5f;
            
  Vector3 G = new Vector3(0,-9.81f/2,0);
  Vector3 N = player.transform.forward * player.runningSpeed;
            
  float t = SolvePoly(G.y, F.y, B0.y-P0.y);
  if(!float.isNaN(t))
  {
    F.z = (P0.z-B0.z) / t + N.z;   
    F.x = (P0.x-B0.x) / t + N.x;   
    WaterBalloon b = (WaterBalloon)Instantiate(_waterBalloon);   
    b.transform.position = B0;   
    b.rigidbody.velocity = F;
  }          
}

In short, this starts by picking a Y velocity that is proportional to the distance to the target. This creates a higher arc for targets that are further away. With F.y set, I can solve the second order polynomial for Y and derive the time of impact “t”, which I then use to calculate the remaining two components of F.

F is now my initial velocity which I can assign directly to my (non kinematic) rigidbody in Unity. If, for some reason, you are using a kinematic projectile, you’ll need to move it yourself in the FixedUpdate method. Something along the lines of

transform.position += _velocity * Time.fixedDeltaTime;
_velocity += _gravity * Time.fixedDeltaTime;

should do the trick.

Comments

There’s a couple of details in this code that are maybe not immediately obvious:

Line 2: I add an initial offset to the enemy location to get the projectile start location. This is simply because the enemy is supposed to throw the balloon, not kick it – I.e. I want it leaving his hands, not his feet 🙂

Line 8: I set the Y velocity to half the distance between the enemy and the player. This is a more or less arbitrary choice – the smaller the number, the flatter the curve. Note that this value alone determines time-to-impact, so if you keep it constant, short throws will take just as long to hit as long throws.

Line 10: If you look closer at the definition of G you’ll notice that gravity is defined to be only half of earths normal gravity of 9.81. The reason for this is that the polynomial is describing the curve of the projectile which is the integral of its velocity, not the velocity itself. If you differentiate to get the velocity of the curve:

p = p0 + v*t + k*t² =>

p’ = v + 2*k*dt

…and insert g/2 for k, you will see that the change in velocity caused by gravity correctly becomes g*dt.

Line 11: I calculate the players velocity using his forward direction and a speed – if the target is a non-kinematic rigidbody you could probably use the velocity directly, but mine isn’t.

Line 13: As you probably know, a second order polynomial has two solutions – I, however, only calculate one of them. If you plot the curve, the reason will be obvious: The polynomial will cross zero twice between thrower and target. Once going up, and once coming down – we want the one coming down which is obviously the larger t and because of the nature of our particular set of coefficients, this is always the solution described by s1 below:

private float SolvePoly(float a,float b,float c)
{
   float sqrtd = Mathf.Sqrt(b*b - 4*a*c);
   float s1 = (-b - sqrtd)/(2*a);
   return s1;
}

Line 14: It is worth noting that this *can* fail. If the chosen Y velocity is not sufficiently large to bring the projectile to the same height as the target location, the second order polynomial will have no solutions, and sqrt will return NaN, hence the check for NaN. You *could* check for this in SolvePoly before doing the sqrt, but in my case it’s such a rare occasion that I’m not too concerned with the performance penalty. I’m happy as long as it does not create any visual artifacts.

Lines 16-17: These two equations are easily derived from the original equations for projectile position B and players position P, like this

B0 + F*t + G*t² = P0 + N*t =>

And then solve for F, noting that G is zero in the cases of X and Z which are the ones we are currently interested in.

 F*t = P0 – B0 + N*t =>

F  = (P0 – B0) / t + N

Why you should NOT advertise on LinkedIn!

I got a “promotional offer” from LinkedIn the other day: 300SEK of free advertising.

I’ve been wanting to promote TempusCura for a while, and figured LinkedIn would be a good place to do so, so as a test I created a campaign, provided my CreditCard details (because as always, free promotions are not really free, there was an administration fee of SEK 35:-.), and set it to run immediately.

I deliberately set the burn-rate high at 300SEK/day because I wanted to test if there was a noticeable difference in downloads or web-site traffic, and opted for impressions rather than clicks to get the widest exposure. As I said, it was a test.

I then went on to do other stuff…

This morning, and by complete accident, I ended up looking at the Campaign page of LinkedIn again, and to my horror saw that my little test had burned not 300, but 3.000SEK in two days. Unlike Facebook and Google, the amount you pay for the campaign is not limited (at least not by default) – if I had not seen this today, it would easily have been 30K by the time I was invoiced. Not cool!

So what did I get for my money?

213.000 impressions.

Wow!

Well, wow-not, much as expected, web adverts are not worth the price of the paper they’re not printed on… 213.000 impressions resulted in 20 clicks – probably from existing customers, friends or family.

It’s hard not to laugh. That’s SEK 150:- per click to advertise an app that I sell for SEK 7:-

I don’t know why there are so many ads on web-sites? I mean, I can certainly see it from the point of view of those who get paid, but for the advertisers? Seriously?

My advice: Draw your ad on the back of a piece of toilet paper, wipe, flush, and save a lot of money.

Floating point numbers and colors…

… and why the idea of mapping a floating point color value of 1.0 to the byte value 255 was a really bad idea.

We’re stuck with it, I know, and I found a way to hack myself around the problem, but still, it’s one of those things that may have seemed like a good idea at the time, but the sensible thing would still have been to map 1.0 to 256 – a lot of things would have worked a lot nicer that way.

This all started with me messing around with shaders in Unity. I needed to pass a bunch of data from the Unity application all the way down to the fragment shader, and decided that the best way to do this was to encode it in a texture – I mean 4 floats per pixel and relatively cheap access from the shader – what could go wrong?

The first couple of issues I ran into was a lot of sub-systems’ over-zealous desire to “improve” my texture before handing it over. No thank you, I do not want that pixel averaged and mipmapped. This was mostly a matter of setting the right options and properties, so I got that sorted.

The real problem, however, was that I somehow managed to completely ignore what a texture really is: An array of 8-bit color values. While we may see pixel values as a nice vector of 4 floats in both Unity and the Shader code, the color itself lives a small portion of its live in the cramped space of just 4 bytes.

And here comes the mapping issue: Because 1.0 is mapped to 255. The nice round floating point value of 0.5 does not become a nice round 128 byte-value. It becomes 127.5. You cannot store 127.5 in a byte, so it ends up as 127. When you convert 127 back to a float it, in turn, ends up as 127/255=0.498..something. Which is not just off from the 0.5 I was hoping for, it is also rounded down, so when I try to use it to find a tile in a texture, I end up in the wrong tile.

Bugger.

What I really needed to store was the integer values from 0 to 15, so here is my solution:

In unity: color.r = N/16 + N/4080;

In my shader: N = floor(color.r*16)

Not rocket science; I convert the number to a float in the range [0;1] , and add a magic offset. N/4080 is the smallest number that, after being converted to a byte, will not cause the conversion back to float to be rounded down. This number depends on N due to the nature of FP numbers. Why the constant is 4080 I don’t know, but I’m sure there’s some IEEE FP guru somewhere, who can enlighten me.

For now, I am just happy that my textures are tiling properly 🙂

TempusCura V1.8 Released

TempusCura V1.8.5 was released in the App Store a few hours ago, and to celebrate, all existing users has had their subscriptions extended with an additional free month 🙂

More New Features

I ended up adding quite a bit more features and changes compared to what I wrote about in my last post. During release testing I realized that some of the more “novel” UI concepts in the original app, while compact and quick to use, were not at all intuitive –  in fact, several of my test users had never understood certain aspects of the app, but had not realised it because they only had one project or a “team” of one person.

Here are some of the things that changed on that account:

Filters and Tabs

IMG_0452

In TempusCura you can filter your work on 3 properties: Time, Projects and People. These filters were set using the calendar, project and team tabs in the main UI, respectively.

However, these tabs also changed how data was visualized so that when you selected the project filter you got a list overview of work sorted by project, the team tab gave a list of work sorted by team members and the calendar presented work over time.

In addition to this, the “tab bar” had two buttons that were not tabs at all: Settings and Export, and the project list had an additional purpose in that it provided a way to add and edit projects and tasks.

In V1.8.5 this has changed completely. Now the buttons in the top are no longer tabs, but separate views.

The work button still shows work in a calendar by default, but it now has three sub-tabs to toggle between viewing work by time, project or people. Filtering is now hidden and only appears when relevant (I.e. when you have more than one project and a team of more than one person). To change project filter, you tap the project list at the bottom, and to change person filter, you tap the list of persons in the work table header.

The project button provides a list of projects and access to team and task management.

The team button is gone since its function is now handled by the work view

Work registration

The calendar grid uses tap-and-hold to register new work items rather than just a single tap. The reason for this is that tap-and-drag is used to scroll the view and without the “hold” part, it was very easy to accidentally add a new work item. While most people eventually “got” this, the way you then dragged to size the work item caused more confusion still, only to be topped off with “surprise” when the work-item view appeared immediately on release.

In V1.8.5 this has changed so that it works more like it does in the native iOS calendar. Tap and hold creates a block of one hour. Moving around drags the whole block, releasing leaves the block at its current location, but does not open the work item view. In this state you can either tap-hold-and-drag the middle of the item to move it again, or the top or bottom to resize it.

Tapping the work item then opens the work item view and allow you to provide the final details before the item is created.

Milestones

IMG_0456When I first started writing TempusCura, the center of attention was “Tasks” – planning, prioritizing and scheduling of tasks that would then be worked on. After testing this for a few months I realized that my daily business was mostly concerned with “work done”, and only rarely did I have time to sit down and create tasks, so I changed the focus of the app to be on “Work” instead.

Still, being able to plan ahead and keep track of what needs to be worked on is important, and the first version of TempusCura probably down prioritized this too far – to the point where it was only just usable. In particular, the task list, even when grouped by milestones, quickly got very long and hard to manage.

V1.8.5 takes a few steps in the right direction by automatically collapsing completed milestones and scrolling to the first relevant task. I have a few more changes coming up soon, but more of that in another post.

Calendar picker

The calendar picker has changed so that you now drag across the calendar to select the time period you wish to view instead of tapping first and last day. This is slightly embarrassing as the tap/tap method of selecting was just a temporary hack I did while implementing the date picker – it was never meant to end up in the released app, but for some reason it did. It’s not that it did not work, it was just hopelessly counter intuitive.

Also, date selection has been limited to one month since I don’t really think it makes sense to see more than a month at a time, and if you selected a long period by accident (say, 10 years) you could potentially bog down both server and client with a massive data download.

I also added buttons to quickly go to next and previous months, though this did not turn out as well as I wanted it to – it will most likely be replaced with something more sensible in a future update.

TempusCura Update

TempusCura V1.8

I’m pleased to announce that the first feature update to TempusCura is now complete and undergoing final testing prior to release on the AppStore. I’ve put together a list of the major highlights below.

If you’d like to participate in the beta test (or have found bugs in the current version you’d like to see fixed) please drop me a note.

Off-line Mode

Location tracking and off-line work registration

I originally decided that in this day and age everyone is always on-line anyway, so there was no real reason for adding the complexity of off-line synchronization with all its evil pitfalls, but for some reason I often find myself working in odd locations or have clients that seem to live in faraday cages with no access to neither 3G nor WiFi networks.

Not being able to register my work hours immediately meant I had to make notes of it elsewhere, somewhat defying the purpose of the app.

So as of V1.8 you can now use the app while off-line – registered work gets queued up until a network connection becomes available. You’ll see this in the UI as a work item with a dotted outline (See screenshot).

Note that off-line mode is currently read-only for everything but work items. Projects, tasks and user registration still requires a live connection – this is primarily because of item dependencies, but also because I have not personally had the need to modify any of these while off-line. Please let me know if your needs are different.

Location Tracking

Not being able to register work while off-line was, however, also a good thing since it made me think of ways to automatically track where I’d been and what I’d been doing.

The first step in that direction is the introduction of a location tracker. This feature uses iOS’ “significant change monitoring” to detect when the device is moved. Changes in location are marked in the calendar margin with a blue line and a little car symbol (screenshot above). These are initially gray in color to indicate an un-confirmed trip, but you can tap it to see the route and distance covered (screenshot below).

Trip details

You can associate each trip with a project (e.g. a customer) and name the start and end locations. TempusCura will remember the names of all locations you enter and automatically show the names next time you make a trip to that location. Once confirmed, the icon turns blue, and the trip will now be included in your reports. You can also delete trips that are not relevant to your bookkeeping.

Location tracking is off by default so you need to go to “Settings” and turn it on. You’ll see three options:

  1. Off
  2. Low Power
  3. Detailed

The Low power option relies exclusively on significant change monitoring and uses very little battery power. It is also not very precise. This option is mostly useful as a reminder of where you’ve been – the route is almost always too inaccurate to be of any use, and the measured distance can be quite far off (the screenshot is using the “Low Power” settings and as you can see, I’ve somehow managed to drive on water :)).

The detailed option still relies on “significant changes” in location as a trigger, but once a significant change is detected, TempusCura will turn on the GPS and use actual GPS coordinates (instead of relying on  3G triangulation and WiFi information). This is far more accurate but obviously uses more battery as well. TempusCura turns off GPS again when no major change in location has been detected for 30 seconds.

Note that because trips are device-specific rather than account-specific, I’ve so far opted to keep trip data locally on the device (in theory you could have several iOS devices making different trips at the same time on the same account which would create a mess in the UI). I will consider pushing confirmed trips to the server in a later version if there’s a desire for such a feature?

Improved Reporting

Reporting has been vastly improved over prior versions for those of you who are not really interested in importing CSV files into a spreadsheet for formatting. If you export by mail, a nice summary of all the work items is now included in the e-mail body. This includes both per-project summaries and per-person summaries. It also includes a mileage report with start and end locations.

Summaries are given in your selected currencies using the most recent exchange rates – note, however, that these are probably not the same rates you get in your local bank. Without getting into details about what I think of banks in general, I think we all know why that is.

Remembers Filter Selections

As you have undoubtedly noticed if you’ve used TempusCura with more than one project, it has an annoying ability to forget which projects you’ve checked off in the project filter. Basically, whenever the app gets shut down by iOS and has to restart, it will revert to showing only items from the first available project.

Not anymore. As of V1.8 the selected project and people filters are now stored and re-established when re-launched. (It’s one of those little annoying things that makes a big difference 🙂 )

Faster Login

Being able to work off-line prompted a change in the login procedure as well, since it now had to be optional. Revising this turned out to be a good idea as I was able to significantly reduce the number of server roundtrips and thus ended up with a much faster login procedure.

Minor UI Changes

The main view has received a minor visual update. The calendar grid now has vertical rows between days so it’s easier to tell them apart (for some reason, I missed this in the first release).

The header has changed from white to gray, so work items don’t get confused with the header when scrolling, and I’ve used the upper left corner of the table (which was previously empty) for a “now” button which scrolls the calendar view to the current time and day.

Changed work item text rendering so that it now renders special characters properly, and uses multiple lines of text if the space is available.

Bug Fixes

Fixed local timezone bug (daylight saving issue in some cases)

Fixed a bug that caused the calendar view to not center properly on the current day (it was offset by current time, and did not take the margin into account).

Fixed bug with “work by project” list sometimes not updating correctly

Remember, remember, the month of December

It’s been mildly annoying for a while – how iOS, and UIKit in particular, treats even the most irrelevant little deviation in input data as a hard error and throws an exception, instead of just logging the problem and either ignoring the request or adjusting the input parameters.

This morning it became *really* annoying, but I’ll get back to that in a minute.

You may argue that the hard error causes more of these issues to be fixed by sloppy developers who would otherwise ignore the warnings and release code that, strictly speaking, isn’t correct. This is a fair argument but I really think it should be limited to debug code.

The problems I have with throwing hard errors on a visual rect that is 2 pixels to wide or, say, a “scroll to index path” that has an invalid path (and this is what I will be getting back to, shortly), are:

1) The exception is caught at application level, and stops execution so further debugging is not possible. As a result, locating the actual code can be tedious – especially for cases that are not easily reproduced. If it had been a warning in the log, it would often be possible to step back and reproduce the action while the app is still running.

2) When this happens in released code you crash an app because of a glitch that would, in many cases, probably not even have been noticeable.

And (2), of course, is why I am even writing this – I got bit by this today, badly, as I realized that TempusCura basically does not work for the entire month of December.

The reason?

Well, as a courtesy to the end-user, the calendar view automatically scrolls to the current month when opening the main view. The current month, obviously, is a number from 1 to 12. The indices in the table that is showing the months, however, are 0-11. Now, for all other months, this creates a small offset as the view actually centers on the next month rather than the current – I never noticed this, and neither did anyone testing the beta version of the app. When you get to December, however, UIKit throws an exception because the index 12 is illegal for an index range of 0-11.

This is not really a critical feature in any way, yet it turned out to be a major showstopper, as the app is completely unusable for the entire month of December.

Yes, I know it’s a bug on my part, and yes, maybe I should have tested all uses cases for all days of the calendar year, but we all know how realistic that is.

Yet, if UIKit had not been so anal about these utterly irrelevant details, and simply showed January, or capped the index at 11, the app would have worked just fine.

And if Apple didn’t take 2 weeks to approve an app, the patch would already be on-line… Sigh!

** 2013/12/02 – UPDATE **

TempusCura is now available in V1.7.13 which fixes the December crash. Hats’ off to Apple who got the app reviewed and approved in just one day! Awesome :).

Tempus Cura Released

iTunesArtworkTempusCura was released on the App Store today.

I’m deliberately not promoting the release yet as I’d like to see the first 100 users or so trickle in slowly in case there are any issues that needs to be resolved, but I’m looking forward to any and all feedback, so please do drop me a note if you decide to try it out :).

The app itself is a free download and comes with 30 days of free subscription, so for the first month you can create or joins as many projects as you like. When the subscription expires you can either extend it at the early introductory price of $1/month or $10/year in which case the app remains unrestricted.

Alternatively you may purchase seat licenses for each of the projects you intend to use at the similarly discounted rate of $1/project. This in turn unlocks those projects once and for all, with no need to ever purchase another license or subscription to access that project again.

Tempus Cura Testing

iTunesArtwork

So, it’s crunch time again… Time to dig into those last frantic hours (days) before uploading a new app to the AppStore, making sure everything is correct, that all the texts and images that comes with the release are in their proper place, all signatures and little iTunes oddities are sorted and, of course, that the App actually works as it should.

TempusCura complicates matters further by being a hosted multi-user App, introducing a variation in UI perspective defined by different user-roles and a server backend that needs to be tested for vulnerabilities and scalability as well. I am using Google App Engine which introduces a third backend problem: Optimizing for cost: Sometimes the best performing solution is not the cheapest, and at the end of the day, I *am* trying to run a business so numbers need to add up.

But that’s a topic for a different post – right now I wanted to share my pre-release test procedure for TempusCura.

During development I use TestFlight to manage testers and while that works for distribution of test binaries, it does not really help validate anything. Unit testing sorts out bugs in core functionality and is especially useful for backend testing, but as anyone who ever worked on an App (or any other UI intensive piece of software) can tell you, there’s a scary bug potential hiding in the UI which no amount of Unit testing will find.

So at the end of the day – or the end of development, rather – boring as it is, you do need to go through that end-to-end systems test. I guess there are many ways to do this, but for an iOS app, I like to sit down with InterfaceBuilder as a guide and the app running on a device and then go through every screen and every button while writing up a test protocol.

The test protocol is basically a check list of what should happen in various parts of the app when the users performs certain actions under some given circumstances.

To give an idea of how that may look, I’ve attached the TempusCura test protocol in its first revision as an example.

TestProtocol_rev1

Some of the things that I try to keep in mind when writing the protocol are:

  1. Write each test as a simple one-line question/statement that can be easily validated as true or false.
  2. Try to group tests so that they follow the flow in the app naturally – this makes testing so much quicker.
  3. Make sure all UI paths are included.
  4. Make sure the expected result is obvious from the test description.

One of the interesting things with a test protocol is how many bugs you find just writing the document. For TempusCura I found a handful of small and large issues when writing the document, and 17 bugs (quite a few serious ones) the first time I ran the test.

Remember to always take the entire test from the start with the binary you intend to upload – we all know what usually happens when we “fix” bugs in the 11th hour 😉