iOS7: Fun times with the new Full Screen Layout!

In a mad rush, I had to prepare an app for iOS7 after its release. Yes, I know, shame on me for not listening to everyone telling me to start getting my apps ready early. How bad could it be anyway?

For the most part, not too bad. The worst part for me came in the shape of dealing with this new idea that every UIViewController now expands the bounds of the entire screen, including where the status bar and navigation bars could be. In any case, I had to do a bit of learning really quickly. If you don’t already know what I’m talking about, here’s a picture of one unpleasant conversion experience:

http://i.imgur.com/wHK3KSO.jpg

At this point, many thoughts went through my head. I could just change the table view’s Frame right? Well then I would have to check if it’s iOS7 or not, and adjust according to iOS version, and and and… that just doesn’t seem right. Why would you do this to me, Apple?! At this point I took a deep breath, relaxed, and did what any competent developer would do: I Googled it.

This of course led me to StackOverflow, and several people asking the same thing. Why is my navigation bar and status bar appearing over my view?. There’s some pretty good answers there no doubt.

Everything new is old again!

Of course, the easiest way is just: make it work like it used to! This is entirely possible! All it requires really, is setting your view controller’s EdgesForExtendedLayout property to UIRectEdge.None. This was a quick fix, and ended up getting me up and running again until I could figure things out (you can of course choose some, all, or none of the edges to be used for extended layout with this property – for example, you might want the top to blur under the navigation bar, but the bottom to not blur underneath the tabs).

This was a workable solution, but I still wasn’t really happy with it. After all, the cool kids were all using the fancy blurred view scrolling behind the navigation and status bars like in the image below, and, who doesn’t want shiny and new?

blurred

Newfangledness $#&@*

So, what Apple really intends is to get a nice blurring effect of the contents of a scrollable view that happen to be under navigation bars, status bars, tab bars, etc. It’s really quite nice, if you haven’t seen it in action for yourself yet.

To make this happen, we essentially need to project our view to the bounds of the entire screen. The problem with this of course, is that we don’t want the top parts of our scrollable view to be initially hidden behind the navigation and status bars.

Content Insets to the rescue! (grab a coffee first)

If our view fills up the entire bounds of the screen, yet there’s still a status bar and navigation bar to show over top of it, we need a way to make sure the contents of our view aren’t initially hidden by the navigation and status bars.

For this purpose, we can take advantage of the ContentInset property that exists on any kind of scrollable view (eg: UITableView, UIWebView, UIScrollView, etc). This little gem of a property allows us to specify where our content should initially start, offset from the edges of the view that is holding it.

It’s important to note that Apple tries to help us out here. By default the UIViewController property AutomaticallyAdjustsScrollViewInsets is set to true, which means that in many cases, this will just work, and you won’t have to think about it. However, in practice you might run into some edge cases (haha, get it?) where the inset cannot automatically be inferred correctly, like I did.

In one case, my UITableView was not automatically adjusting the inset correctly. Long story short, I had to account for a status bar (20pt), and a navigation bar (44pt) for a total of (64pt) inset from the top edge of the screen. That meant I needed to set my UITableView.ContentInset = new UIEdgeInsets (64, 0, 0, 0);. The result was my UITableView still taking up the entire screen, but not making the top row initially hidden behind the navigation bar, while still taking advantage of the nice blurred scrolling underneath of it.

More dynamic?

You may have scoffed at my example of hard-coding the inset. All I can say is that sometimes you just need it to work immediately. If you’d like to do this more properly, you should consider the other new properties Apple has included on the UIViewController which are: TopLayoutGuide and BottomLayoutGuide. These serve as a reference to us to let us know what the top (and bottom) of our content should be within the bounds of the screen (by using the .Length property of Top/Bottom layout guides).

Now, there is one catch. As of this article being published, the .Length property was missing from the Xamarin API. There’s already a bug in for a fix, and in the meantime there’s a work around:

var length = MonoTouch.ObjCRuntime.Messaging.float_objc_msgSend (TopLayoutGuide.Handle, (new Selector("length")).Handle);

Oops, there’s another catch. This property doesn’t seem to return > 0 until the views have all been laid out. So, you may consider doing something like this:

public override void ViewDidLayoutSubviews ()
{
  base.ViewDidLayoutSubviews ();

   var length = MonoTouch.ObjCRuntime.Messaging.float_objc_msgSend (
                 TopLayoutGuide.Handle,
                 (new Selector("length")).Handle);

  TableView.ContentInset = new UIEdgeInsets (length, 0, 0, 0);
}

Also, don’t get caught with your pants down!

If you haven’t already guessed it, some of these properties are iOS7 only. So if you’re targeting iOS6 still, you’ll have to do some sort of version checking to make sure you only use these API’s when the app is running under iOS7!

Anyway, I hope this finds its way to someone else who is going through the same learning pain as I did, to get their app ready for iOS7!

iOS7 Recipe: Background Fetching

This is my entry into the Xamarin Recipe Cook-Off. Recipes, in Xamarin terms, are very simple demonstrations of how a single feature or piece of functionality is implemented. I thought background fetching would be useful to many developers, and it’s pretty easy to implement, especially after reading this recipe.

I’ve also included the recipe in this blog post:

Background Fetching Data

This recipe shows how to register your application to perform background fetching on intervals.

1. Recipe

In your application’s Info.plist file, add the value fetch to the UIBackgroundModes (Required background modes) property.

Next, in your AppDelegate class, in the FinishedLaunching override method, add the following code to register your application for background fetching:
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval (UIApplication.BackgroundFetchIntervalMinimum);
Finally, in your AppDelegate class, override the PerformFetch method.

This method will be executed by the operating system when it sees best fit (eg: when the device is awake and connected already). You do not have complete control over how often or when fetching happens.

You should execute your own code to fetch new data in this method. It’s important to call the Action<UIBackgroundFetchResult> completionHandler parameter which is passed into this method with the appropriate result when you are done.

2. Sample PerformFetch

In this sample, a weather service is called by background fetching so that when the user opens the app, recent weather is available. The weather is cached locally after it’s fetched in the background, and the UI is also updated if there is new weather info.

public override async void PerformFetch (UIApplication application, Action<UIBackgroundFetchResult> completionHandler)
{
  Console.WriteLine ("PerformFetch called...");

  //Return no new data by default
  var result = UIBackgroundFetchResult.NoData;

  try
  {
    //Get latest weather
    var w = await GetWeatherAsync("Windsor, Canada");

    if (w != null)
    {
      //Cache the weather locally
      CacheWeatherAsync(w);

      //Update the UI
      weatherViewController.UpdateWeather(w);

      //Indicate we have new data
      result = UIBackgroundFetchResult.NewData;
    }
  }
  catch
  {
    //Indicate a failed fetch if there was an exception
    result = UIBackgroundFetchResult.Failed;
  }
  finally
  {
    //We really should call the completion handler with our result
    completionHandler (result);
  }
}

3. Additional Information

  • Your PerformFetch has about 30 seconds to run before it’s killed
  • The operating system is more likely to grant more time (and more often) to your application for background fetching if you are efficient, which means executing quickly, and always calling completionHandler with an accurate result
  • You can tell the operating system the minimum time to sleep between waking up your application and calling its PerformFetch method if you know your app only updates at a certain interval, to avoid extra calls to PerformFetch and wasting battery life. You would specify the minimum time in seconds in the UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval (double minimumBackgroundFetchInterval) method
  • You can actually make calls to update your UI from the PerformFetch method so that the next time the user launches the app, everything is up to date