On-Demand Resources in iOS Tutorial

On-demand resources are a technique to reduce the initial download size of applications which rely on large assets for use. This is especially useful to games developers with large assets only required during certain stages of the game. By James Goodwill.

Leave a rating/review
Save for later
Share

Note: This tutorial uses Xcode 9 and Swift 4.

iOS 9 and tvOS introduced the concept of on-demand resources (ODR), a new API used to deliver content to your applications after you’ve installed an app.

ODR allows you to tag specific assets of your application and have them hosted on Apple’s servers. These assets won’t be downloaded until the application needs them, and your app will purge resources from your user’s devices when they’re no longer needed. This results in smaller apps and faster downloads — which always makes users happy.

In this tutorial, you’ll learn the basics of on-demand resources including:

  • Tagging and downloading individual resources
  • Looking at Xcode’s Disk Report to see which resources are on the device
  • How to organize resources into different tag types
  • Some best practices to make sure your users get the best experience

Getting Started

Download the starter project for this tutorial. You can find it Bamboo-Breakout-Starter.

The starter project is a game called Bamboo Breakout. Michael Briscoe wrote this app as a SpriteKit tutorial, and it serves as a great example of how simple it is to write a SpriteKit app in Swift. You can find the original tutorial here.

The original game had only one game level, so I’ve added a few changes to the original app: five new game levels and some code to load each level.

Examining the App

Once you have the starter application, open it in Xcode and open the Bamboo Breakout folder.

BambooBreakou on-demand resources

In this folder, you will see six SpriteKit scenes. Each one of these scenes represents a level in the Bamboo Breakout game. At the moment, you’re packaging all these scenes with the application. By the end of this tutorial, you’ll have only the first level installed.

Build and run the app, and you’ll see the first level of the game in the simulator.

Level 1

Game States

Time to take a look at the starter project. You don’t need to examine the entire project, but there’s a few things you do need to be familiar with.

Look at the top of the GameScene.swift class. In Xcode, open GameScene.swift and look for the following snippet:

lazy var gameState: GKStateMachine = GKStateMachine(states: [
  WaitingForTap(scene: self),
  Playing(scene: self),
  LevelOver(scene: self),
  GameOver(scene: self)
])

Here you see the creation and initialization of a GKStateMachine object. The GKStateMachine class is part of Apple’s GameplayKit; it’s a finite-state machine that helps you define the logical states and rules for a game. Here, the gameState variable has four states:

  • WaitingForTap: The initial state of the game
  • Playing: Someone is playing the game
  • LevelOver: The most recent level is complete (this is where you’ll be doing most of your work)
  • GameOver: The game has ended either with a win or a loss

To see where the initial game state is set, scroll down to the bottom of didMove(to:).

gameState.enter(WaitingForTap.self)

This is where the initial state of the game is set, and it’s where you’ll begin your journey.

Note: didMove(to:) is a SpriteKit method and part of the SKScene class. The app calls this method immediately after it presents the scene.

Starting the Game

The next thing you need to look at is touchesBegan(_:with:) in GameScene.swift.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  switch gameState.currentState {

  // 1
  case is WaitingForTap:
    gameState.enter(Playing.self)
    isFingerOnPaddle = true
  
  // 2
  case is Playing:
    let touch = touches.first
    let touchLocation = touch!.location(in: self)
    
    if let body = physicsWorld.body(at: touchLocation) {
      if body.node!.name == PaddleCategoryName {
        isFingerOnPaddle = true
      }
    }
  
  // 3  
  case is LevelOver:
    
    if let newScene = GameScene(fileNamed:"GameScene\(self.nextLevel)") {
      
      newScene.scaleMode = .aspectFit
      newScene.nextLevel = self.nextLevel + 1
      let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
      self.view?.presentScene(newScene, transition: reveal)
    }

  // 4
  case is GameOver:
    
    if let newScene = GameScene(fileNamed:"GameScene1") {
      
      newScene.scaleMode = .aspectFit
      let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
      self.view?.presentScene(newScene, transition: reveal)
    }
    
  default:
    break
  }

There’s a lot going on here. Let’s go through this line by line.

  1. When the system invokes touchesBegan(_:with:), gameState.currentState is set to WaitingForTap. When the switch hits this case, the app changes gameState.currentState to the Playing state and sets isFingerOnPaddle to true. The app uses the isFingerOnPaddle variable to move the paddle.
  2. The next case in the switch executes when the game is in the Playing state. This state is used to track when the user is playing the game and touching the game paddle.
  3. The app executes the next case statement when the game is in the LevelOver state. In this case the game loads the next scene based on the nextLevel variable. The nextLevel variable is set to 2 on creation of the very first scene.
  4. When the game is in the GameOver state, it loads the scene GameScene1.sks and restarts the game.

This process assumes you packaged all these scenes with the installed app.

Bundles

Before you start using on-demand resources, you need to know how resource bundles work.

iOS uses Bundles to organize resources into well-defined subdirectories inside an application. You need to use the Bundle object to retrieve the resource you are looking for; the Bundle object provides a single interface for locating items. You can imagine the main bundle looking similar to the following:

Main Bundle

This example shows what the main bundle looks like when it contains three game levels.

On-demand resources are different. They’re not packaged with the distributed application. Instead, Apple stores them on their servers. Your app retrieves them only when it needs to, using NSBundleResourceRequest. You pass the NSBundleResourceRequest object a collection of tags, which represent the resources you want to retrieve. When the app downloads the resources to the device, they’re stored in an alternative bundle.

Alternate Bundle

In this example, the application is making a request for three on-demand resources. The system will retrieve these resources and store them in an alternative bundle.

Now, what exactly are tags?

Tag Types

  • Initial Install Tags: These resources are downloaded when the app is downloaded from the App Store. These resources affect the total size of the in the App Store.
  • Prefetched Tag Order Tags: These resources are downloaded once the app has been installed on the user’s device. The App downloads them in the same order they appear in the Prefetched Tag Order group.
  • Downloaded Only On Demand Tags: These resources are downloaded when the app requests them.

Note: You can only use Downloaded Only On Demand while in development. You’ll have to deploy the app to the App Store or TestFlight to use the other tag types.

James Goodwill

Contributors

James Goodwill

Author

Darren Ferguson

Tech Editor

Chris Belanger

Editor

Andy Obusek

Final Pass Editor and Team Lead

Over 300 content creators. Join our team.