On Demand Resources

09/18/2015 § 4 Comments


On Demand Resources (ODR) is an iOS 9 feature that enables applications to host resources on a remote server while using those as if they were bundled within the app. It allows applications to considerably reduce their binary size and still offer high speed, high quality content.

Your application resources are hosted by the App Store if your app is published, but those can also be hosted on custom web servers if your application is signed with an Enterprise Profile. As for the development stage, Xcode 7 hosts the resources on your local machine. If you are doing Continuous Integration, Xcode Server can also host your resources.

An application can leverage ODR for basically any kind of resource. That includes images, videos, data files and other specific file types such as Apple TV Image Stack files.

It is really important to note ODR is not supported on iOS 8. By unsupported I don’t mean that ODR assets are seen as regular bundled assets on iOS 8, but rather they are not available at all (not even included on the binary). Unfortunately, if you are looking to leverage ODR on your application, you must not support older versions of iOS.

If that is your case, leveraging ODR is really simple. All you need to do is categorize your resources and then tag your categories. Once you’ve done that, there are new API calls which handle the download in your behalf and tell you when the resources are locally available, so you can access them through the very same APIs you already do today.

Of course, we will go over the APIs and such, but those are so simple, I rather first go over topics like what you should expect, think about or be aware of, when integrating ODR in your application.

The first topic in that regard is what files you should use ODR for.

ODR is all about reducing your application’s size, both the binary and footprint. Any resources you don’t need right away or that you don’t need to have always available are good candidates. Specially if those are big files.

Apple likes to use Game apps to exemplify. That is because it is very easy to imagine that a game app doesn’t need all the levels as soon as it is installed. It will also no longer needs the initial levels if the player is far through the game. Any assets needed for those levels can be marked as ODRs. The app will be much smaller because a lot of assets (images, sprites and videos for example) needed for those levels will not be downloaded together with the game, but only when needed.

Another very important detail on the ODRs life cycle that you might have picked up on the last paragraph, is that resources once downloaded, can become unavailable again. The reason is that often you need a given resource until you don’t. In the Game example, those early levels are not required and could be removed once you are past them, to make space for new stuff. That is precisely what iOS does. It tries to keep your footprint as small as possible. But it will only delete files your application downloaded if it needs more space. You should always keep that in mind.

As iOS might delete resources that are not in use, it also allows your application to set a content preservation priority. iOS will use that information to decide which files to delete first. Basically the bigger files that are less important for you will be deleted first.

It is also possible for you to use ODR for resources you do need available as soon as your application installs. iOS will take care of assuming the app as installed only once those resources – you marked as needed upon first launch – are downloaded and available. As you might expect, the file size for these resources counts towards your total app size in the App Store, while other ODR resources don’t. The upside in using ODR for these resources you need right away, is that those might not be needed afterwards. iOS can free that space for you and provide those back if you ever need them again. A good example are welcome videos. Users see these welcome videos when they install your application but after that, they rarely go after those again.

I guess that is enough detail concerning what you should have in mind while working with ODR. Let’s dive into the implementation details!

First thing to do is enable ODR for your project.

Once you’ve done that, you need to create and assign tags for the resources you would like to access via ODR. You do that on your Assets Catalog. In an asset catalog, the field is shown in the Attributes inspector:

Creating and Setting Tags. Screen shot from Apple’s documentation.

You can also specify tags by typing it’s name in the On-Demand Resources Tags field on your Target settings. If you are looking for having your resources Prefetched, you can set that up on your Target’s Resource Pane. Just assign your tags to either Initial Install Tags or Prefetched Tag Order categories. The first ensures your resources will be downloaded and made available as soon as your app finishes installing, while the second will trigger the download of your resources in the order those were specified, as soon as your app finishes installing.

After that all you need do is request access to the resources you need when you need those. You do that using one of the following two APIs:

  • beginAccessingResourcesWithCompletionHandler: This API will download the resource if needed and call the completion block when the resource is available. It will also tell iOS you are currently using the resource and that it should not be deleted by system if it needs extra disk space.
  • conditionallyBeginAccessingResourcesWithCompletionHandler: Does not download the resource. The completion block will return immediately with a resourcesAvailable boolean. If the resources are available, those are also marked as currently in use.

One very important aspect of the completion handler (I better bold this) is: The callback block is not called on the main thread. If you are going to do anything UI related, make sure to schedule such operation on the main thread. Within this block (or after it was called) you can access the resources you asked (as long as you don’t have any errors), the same way you did before (i.e: [UIImage imageNamed:]).

Both of these methods are called on a NSBundleResourceRequest instance. To create one you need only to call initWithTags: and provide the tags you want to access.

You should keep reference to the instance of NSBundleResourceRequest you created, so that you can call endAccessingResources once you are done. That will tell the system that you have finished accessing the resources marked with the tags managed by the request, effectively allowing iOS to delete those resources if required from this point forward.

If you don’t call that method, the system will when the resource request object is deallocated.

That reference can also be used to listen for progress updates or cancel the request/download operation or to set the loadingPriority, which tells the system how to prioritise the order of your download requests.

Another topic I should mention before we call this post done, regards error handling. There are really three types of errors you can encounter when dealing with ODR:

  1. Network related issues: These can be either common networking issues, non-existing resources or asset pack size errors. The last two you should find and resolve during development.
  2. Local storage issues: These can either be maximum in-use memory exceeded (NSBundleOnDemandResourceExceededMaximumSizeError), which you fix by calling endAccessingResources on resources you no longer need; or a system memory exceeded error  (NSBundleOnDemandResourceOutOfSpaceError), in which case you should listen for Low Space Warnings.
  3. Unexpected state: This kind of error tells you there is something wrong with your state handling. The best way to debug these, is by using the Disk Gauge (there is an icon for that on Debug Navigator) and check out the size and status of each resource.

This should be a pretty good overview/sum-up to get you started right away. If you feel like you need more information, don’t hesitate to check the official documentation.

Thank you!

Advertisements

Adopting 3D Touch: Peek and Pop

09/17/2015 § 7 Comments


Today’s subject is 3D Touch, the new feature introduced on iOS 9 and so far available only for iPhone 6s and 6s Plus.

You can picture it as a tap and hold gesture that takes pressure into account. But more than that, it provides a very specific user experience around it, which turns it into a new way to interact with your iPhone. In addition to that, 3D Touch also enables your app to anticipate and accelerate the user interaction with your app by providing shortcuts or deep links to your app’s features from the Home Screen.

3D Touch is currently available in 3 different ways:

  1. Home Screen Quick Actions: You can define static and dynamic actions that appear right on your app’s icon in the Home Screen.
  2. Peek and Pop: This is an in-app application of 3D Touch with the intention of displaying a preview of some content you have available within your app without forcing the user to change scope, while also provide the ability of taking actions on that content.
  3. Pressure Sensitivity: This is another in-app feature that allows you to leverage pressure-sensing for completely custom purposes, such as drawing applications.

This post will go over Peek and Pop. But before we dive into it, I would like to comment on the difference between 3D touch and Force touch.

The force touch was originally introduced on Watch OS 1. It enables you to display a context menu on a controller upon a force touch, that is, a simple tap with additional pressure. The 3D Touch is the manifestation of the force touch on iOS.  It not only provides you APIs for displaying a context menu (or peek in iOS terminology), but it also provides you means to consider the pressure applied on a touch to build completely custom and new features, effectively adding a third dimension to your gestures (traditional x, y coordinates as well as the pressure variant).

Additionally, the 3D Touch is highly related to the long press gesture. The Peek and Pop usage for example (yes, the topic is finally being introduced), happens on three phases.

To better understand these phases we need first to have the use case for Peek and Pop very clear. You wanna leverage Peek & Pop anytime you could display a quick view of some content you have available, without taking the user to some other place in your app. The most common example are UITableViewControllers. Typically your table view lists the contents you have available and tapping on a cell shows the user details about that content, usually navigating to another UIViewController.

If you think taking a peek before effectively transitioning to the detail view has value for your users, that is when you leverage the Peek and Pop APIs.

A light press on a cell will trigger the first phase of the 3D Touch. It will blur the surrounding content but not the cell pressed by the user, as an indication the peek functionality is available for that row (or content):

Peek and Pop Official Documentation

Indication of peek availability from 3D Touch Official Documentation

If the user continues to press, the view transitions to show a peek. This is a UIViewController you provide as a preview of the content you would display if the user navigated forward.

At this point, the user can swipe upwards to see a list of actions available for that content from the preview view, or keep pressing to effectively navigate forward into the details for that content or simply stop pressing the screen which will dismiss the peek effectively returning the app to it’s normal state. This is the third phase.

Peek quick actions from the 3D Touch Official Documentation

Adopting this functionality is very easy. Apple introduced on iOS 9 some new APIs for this purpose. You start with registering a view which you want to display a peek for and a delegate which will handle the transitions for the 3D Touch. You do this by invoking registerForPreviewingWithDelegate:sourceView: on your current UIViewController (or UITableViewController in this example). You can designate more than one source view for a single registered view controller, but you cannot designate a single view as a source view more than once.  

Note that for our UITableViewController example, you don’t need to call registerForPreviewingWithDelegate:sourceView: for each cell. That would be a bit complex as you need to consider that cells are re-used. It is much simpler to set your sourceView as your UITableViewController’s view and set the sourceRect on the UIViewControllerPreviewing object that is returned by that call, to convey what row will not be blurred upon a light press. We will get to that later.

 - (void)viewDidLoad {
     [super viewDidLoad];
     self.previewingContext = 
      [self registerForPreviewingWithDelegate:self 
                                   sourceView:self.view];
 }
 

That is really all you need to do to handle registration. You don’t need to call unregisterForPreviewingWithContext: unless it is no longer possible to provide a peek for some reason. The system calls this method automatically when a 3D Touch-registered view controller is deallocated.

Note that we saved a reference for the previewing context object (UIViewControllerPreviewing). The reason is that 3D Touch  is a feature that not only is available just for two devices, but also because a user can at any time disable or enable it from the Settings app. To do so a user needs only to go to Settings > General > Accessibility > 3D Touch and toggle a switch.

So before registering we need to know whether 3D Touch is available/enabled or not. We do that by checking the forceTouchCapability flag available in the UITraitCollection. Additionally, if your app supports versions older than iOS 9, you need to check for that APIs availability as well.

 - (BOOL)isForceTouchAvailable {
   BOOL isForceTouchAvailable = NO;
   if ([self.traitCollection respondsToSelector:
     @selector(forceTouchCapability)]) {
      isForceTouchAvailable = self.traitCollection
        .forceTouchCapability == UIForceTouchCapabilityAvailable;
   }
   return isForceTouchAvailable;
 }
 
 - (void)viewDidLoad {
   [super viewDidLoad];
   if ([self isForceTouchAvailable]) {
     self.previewingContext = 
       [self registerForPreviewingWithDelegate:self 
                                    sourceView:self.view];
   }
 }

I mentioned earlier that a user can at any time toggle the 3D Touch availability switch. When that happens traitCollectionDidChange is called.

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { 
    [super traitCollectionDidChange:previousTraitCollection];
    if ([self isForceTouchAvailable]) {
       if (!self.previewingContext) {
          self.previewingContext = 
             [self registerForPreviewingWithDelegate:self 
                                          sourceView:self.view];
       }
    } else {
       if (self.previewingContext) {
          [self unregisterForPreviewingWithContext:self.previewingContext];
          self.previewingContext = nil;
       }
  }
}

Once you have that set, your view controller is ready to display peeks. We just need to implement the UIViewControllerPreviewingDelegate, where you provide iOS a view controller for the peek as well as define the behaviour for the scenario where the user decided to commit with content and effectively dive into it.

The UIViewControllerPreviewingDelegate protocol counts with two very important methods. The first is previewingContext:viewControllerForLocation:. This method is where you find out whether or not you have a peek to display to the user and if you do, you return a UIViewController instance which will be presented to the user (as the second screen shot on this post). If you do provide one, you can specify a sourceRect which will not be blurred by iOS upon a light press. If you don’t specify, the blur animation doesn’t happen.

For our UITableViewController example, that method could be implemented as follows:

 - (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
    NSIndexPath *indexPath = 
       [self.tableView indexPathForRowAtPoint:location];
    if (indexPath) {
       UITableViewCell *cell = 
         [self.tableView cellForRowAtIndexPath:indexPath];
       id *content = self.data[indexPath.row];

       YourViewControllerHere *previewController = 
         [[YourViewControllerHere alloc] init];
       previewController.content = content;

       previewingContext.sourceRect = cell.frame;

       return previewController;
    }
    return nil;
 }

The second method is called when the user does a deeper press, effectively indicating she/he wants to change context to that content:

 - (void)previewingContext:
    (id <UIViewControllerPreviewing>)previewingContext 
      commitViewController:
    (JMDocumentPreviewViewController *)viewControllerToCommit {
    id *content = viewControllerToCommit.content;
    [self presentViewController:viewControllerToCommit 
                       animated:YES completion:nil];
 }

Here we are just re-using the same controller we displayed in the preview and opening it on full screen. But you could provide a different UIViewController or even present the view controller in a navigation controller by calling the navigation controller’s showViewController:sender: method.

As for your preview controller, there is nothing special about it. It is a regular UIViewController, it doesn’t need to conform to any special protocols.

Unless you want to leverage the ability to provide some user actions in your peek. That could really be anything a user can do with your content. Share via E-mail for example. Such a feature would allow your user to take a peek on the content, check it out without leaving the current context and share it if it pleases him/her.

To do so, all you need is to implement the previewActionItems getter of your preview view controller. From that method you can return UIPreviewActionItem objects (which are either groups of actions represented by UIPreviewActionGroup instances or simple actions represented by UIPreviewAction instances).

For the sake of completeness we will take a peek into providing UIPreviewActions. The Share action we discussed previously would be added to your preview controller as follows:

 - (NSArray<id<UIPreviewActionItem>> *)previewActionItems {
    __weak YourViewControllerHere *weakSelf = self;
    UIPreviewAction *shareAction = 
      [UIPreviewAction actionWithTitle:@"Share" 
                       style:UIPreviewActionStyleDefault 
       handler:^(UIPreviewAction *action, 
                 UIViewController *previewViewController){
      // call a delegate or present the mail composer
    }];
    return @[shareAction];
 }

And that is all you need to leverage this feature and keep your app up-to-date with what is latest on iOS!

There is one additional note I have to share before we call this post done. Apple repeatedly recommends in the 3D Touch Documentation, that you should look into providing alternative ways for your user to access your 3D Touch features when 3D Touch is not available. Not only because the user can disable that functionality, but mainly because 3D Touch is not supported on all devices yet. For example, all the iPads are currently off the scene. The recommendation is to use UILongPressGestureRecognizer as a means to access these functionalities alternatively.

I hope you enjoyed this post and that it helped you get up to speed. Please leave any comments you might have to help people reading this post or to make my writing better as new posts are published.

Thank you!

Where Am I?

You are currently browsing entries tagged with iOS 9 at iOS Guy.