In this article, I'll talk about the different business models of mobile applications and which one is the right choice for you.
Introduction
You've worked for weeks or even months on your first mobile application and it's ready for prime time. The next step, of course, is to publish it to Apple's App Store if it's an iOS application or Google Play if you're targeting the Android platform. One of the question you should ask yourself is "How much should my application cost?" $0.99? $1.99? $10.99? Free? In this article, we'll consider your options and decide which one is the road to success and which one isn't.
Revenue Sources
Say you've just developed a great application. Great. You can't wait to publish it and let the cash pour in. Should you make it free? Or should you charge $0.99 for it. Or maybe even as high as $1.99? Surely, the average consumer won't mind paying $2.99 for such a nice application. Or would they? Not quite. Consumers are getting less and less willing to pay for mobile applications. Instead they search for free alternatives. From a developer's point of view, though, free applications aren't free. They build a user base first and generate revenue through ads shown in the application or through in-app purchases or subscriptions. This is the so-called freemium model.
According to a recent report by Distimo, 92% of the total revenue earned in Apple's App Store in November 2013 came from in-app purchases in free applications, up from 77% in January 2013. Only 4% came from paid applications and 4% from paid applications with in-app purchases. This trend is also visible in Google Play. In November 2013, 98% of the total revenue came from in-app purchases in free applications, up from 89% in January 2013. Only 1% came from paid applications and 1% came from paid applications with in-app purchases. The percentages of free applications with in-app purchases continues to climb.
Free with In-App Purchases
How do you make money with a free application? In-app purchases are a good option. Users who like your application are likely to buy one or more in-app purchases (IAPs). That means that you buy virtual goods for a price from within your application. An in-app purchase can be used in many forms.
Paid Subscription: This works well for magazine and newspapers.
Virtual Currency: This is a very popular approach for free games.
Unlock Features: This is another popular option for games to sell additional levels.
Game Boosters: This is similar to selling virtual currency and frequently found in games that are tough to get through.
Lives: This strategy is a variant of game boosters. Candy Crush Saga is a good example of this strategy.
Paid Ad Removal: This approach is great if you want to allow users to upgrade from a free version to a paid version.
When used appropriately, in-app purchases can generate a lot of revenue. Candy Crush Saga, one of the recent top grossing games in Apple's App Store, generates around $950,000 in revenue ... every day.
Free with Ads
Advertising is another way to make money from free applications. Mobile advertising works well in one of the following forms, cost per click (CPC), cost per mile (CPM), and cost per action (CPA). CPC means that you earn money every time someone clicks on one of your ads. This can be very profitable, but the amount of money you earn largely depends on how many people are using your application, how interesting the ads you serve are, and how high the CPC rate is. For some categories, you can earn up to $0.05-$0.1 per click, while you can barely earn $0.001 per click for others.
CPM means that the ad network pays developers a certain rate for every 1000 views (not clicks). To generate large amounts of revenue from CPM ads, you need a large use base that view the ads every time they use your application.
CPA stands for cost-per-action, which means that developers get paid every time a user accepts and completes an offer, such as filling out a survey, downloading an application, buying a product, etc. The rates are very high, sometimes as high as $1-$2 per action. A common method would be asking the user to complete an action and giving the user something in return, such as virtual currency.
One common problem with ads is fill rate. This is the number of ads the application requests from the publisher, such as Google, divided by the number of ads received from the publisher. If you use Apple's iAd network, for example, it might not display an ad every time someone uses your application. In fact, no advertising network has a 100% fill rate. Using an ad network with a lower fill rate will significantly reduce your potential earnings even if you get a higher CPC. To make the most money with ads in an application, you need to keep track of the following three elements.
A great application: Many applications aren't the first in their segment, but they're the best and that's why they are successful.
Build a Large User Base: If you aim to earn money through advertising, you'll need a large user base. Making money with ads is a numbers game.
Relevant Ads: Not any ad will do. Unless your paid by impressions (CPM), you won't make much money if the ads you serve are dull, not relevant, or unattractive.
Here's a list of recommendations for some good ad networks you can use in your applications.
Apple's iAd
Google's AdMob
Mobpartner
Leadbolt
Inmobi
Revmob
What's the best mobile advertising network? The truth is that there is no one best network. Experiment with several networks and find out which one(s) work best for your application. I'd recommend starting with iAd for iOS applications and AdMob for the Android platform.
Paid: How does it work?
You've worked really hard on your application and you've invested time and money in your project. Why shouldn't users pay for such a great, fun, and useful application? It that's the way you feel about it, then you should probably go for a paid business model. There's one important question to answer though. How much should you charge? $0.99? $1.99? More? $0.99 is a good price for a paid application. The truth is that $0.99 is the minimum you can charge in most mobile stores. How about $1.99? That would be pushing it a bit. Users might not want to spend $1.99 on an application that they have never used before. That's why it's always a good idea to include several screenshots and maybe even a video if possible. Anything above $2.99 won't make you a lot of money. Users only tend to spend that much money on an application if it's from a reputable company or many of their friends are using and enjoying it.
Still, some applications have made a lot of money with the paid model, for example Doodle Jump and Minecraft Pocket Edition. In fact, Minecraft Pocket Edition was one of the top grossing applications in Apple's App Store. The surprise is that it's priced at a whopping $6.99. However, Minecraft Pocket Edition's success can for the most part be attributed to the success of the original Minecraft game, which is still insanely popular.
The popularity of paid applications varies from category to category. Applications in the productivity, navigation, and education categories are more likely to hit the top grossing charts than applications of other categories. Why is that? One explanation is that these types of applications are considered an investment by consumers, while games and social networking applications are aimed at users who are looking for fun and entertainment.
Conclusion
Let's go back to the original question "How should you price your application?" Let's take a look at a trend in the mobile space from a post by Mary Ellen Gordon over at Flurry. The percentage of free applications on the App Store has risen from 84% in 2010 to 90% in 2013. This means that the percentage of paid applications has fallen from 16% to 10% in that same period.
Users are getting less and less willing to pay for applications.
What does this tell us? Users are getting less and less willing to pay for applications, instead preferring free alternatives with in-app purchases or ads. What's more, Android users are even less willing to pay for applications.
Here are my suggestions.
For the freemium model, include in-app purchases to generate revenue. You could also publish a lite version and a separate paid version that includes premium features.
For free applications, monetize your applications with in-app purchases and ads. Be careful to not show too many ads as you may scare away users.
Opt for the paid model if your application stands out from the rest by offering unique features or targets a premium market. Keep in mind that it's best to price it under $2.99.
I hope you've learned something from this article. If you have any questions or comments, feel free to leave a comment below. I'll do my best to answer them.
In this two-part tutorial, I will show you how to build a poker game using the Corona SDK. You will learn about touch controls, tweening elements, timers, and how to create a custom sort function.
Introduction
In the first tutorial, we will focus on setting up the project and creating the user interface for the game. To get you excited about this project, below is a screenshot of how the game will look like when it's finished.
The playing cards are not included in the download files. If you would like to follow along, you can purchase the card set on Graphic River or use another set of cards. The dimensions of each card are 75px x 105px, which translates to 150px x 210px for the @2x size.
If you'd like to run the demo application of this tutorial, you need to add the card to the project's directory. The images for the cards should start with h, c, d, or s, with a number appended to the letter (d1.png to d12.png, h1.png to h13.png, etc.).
If you would like to work with the original vector files, the graphics for the poker machine were created by Andrei Marius and are available on VectorTuts+. Feel free to download them from Vectortuts+.
1. New Project
Open the Corona Simulator, click New Project, and configure the project as shown below enter the following details. Select a location to save your project and click OK. This will create a folder with a number of icons and three files that are important to us, main.lua, config.lua, and build.settings. We will take a look at each file in the next few steps.
2. Build.Settings
The build.settings file is responsible for the build time properties of the project. Open this file, remove its contents, and populate it with the following code snippet.
In build.settings, we are setting the default orientation and restricting the application to only support a landscape orientation. You can learn which other settings you can include in build.settings by exploring the Corona Documentation.
3. Config.lua
The config.lua files handles the application's configuration. As we did with build.settings, open this file, remove its contents, and add the following code.
application =
{
content =
{
width = 768,
height = 1024,
scale = "letterbox",
fps = 30,
imageSuffix =
{
["@2x"] = 2, -- images with "@2x" appended will be used for iPad 3
}
}
}
This sets the default width and height of the screen, uses letterbox to scale the images, sets the frames per second to 30, and uses the imageSuffix setting for dynamic image selection. The download files for this tutorial include two files for each image, imageName.png and imageName@2x.png. iPads with a retina display will use the @2x images, while the iPad 1 and 2 will use the regular images.
You can learn what other properties you can set in config.lua by checking out the Corona Documentation.
4. Main.lua
The main.lua file is the file that the application loads first and uses to bootstrap the application. We will be placing all of our code into this file. In the next few steps, we will be stubbing out a number of functions. You should add all of them to main.lua.
5. Hide Status Bar
We don't want the status bar showing in our application. Add the following to line to main.lua to accomplish this.
display.setStatusBar(display.HiddenStatusBar)
6. Variables
The next code snippet declares the variables that we will need throughout the project. Read the comments to better understand what each variable is used for. Most of them are self-explanatory.
local pokerMachine = display.newImage("cardsBG.png",true)
pokerMachine.anchorX, pokerMachine.anchorY = 0,0 -- Sets the images anchors it uses the new Graphics 2.0
local suits = {"h","d","c","s"}; -- hearts = h,diamonds =d,clubs =c,spades=s
local deck; -- The deck of Cards
local playerHand = {} --Holds the Players Cards
local cashOutButton
local betMaxButton
local betButton
local dealButton
local holdButtons = {} --Holds the "Hold" buttons
local isNewGame = true
local MAXBETAMOUNT = 15 -- Max amount player can bet
local instructionsText
local betAmount = 0 -- How much the player has bet so far
local creditText
local betText
local winText
local gamesText
local gameData = {} -- This will hold game data -- The credits and number of games played
7.setup
The setup function is used to setup the game assets and start the game.
function setup()
end
8.setupButtons
This function sets up the buttons in the game.
function setupButtons()
end
9.setupTextFields
This function sets up the text field used in the application.
function setupTextFields()
end
10.enableDealButton
As the name indicates, this function enables the deal button.
function enableDealButton()
end
11.disableDealButton
This function disables the deal button.
function disableDealButton()
end
12.enableBetButtons
This function enables the bet buttons.
function enableBetButtons()
end
13.disableBetButtons
This function disables the bet buttons.
function disableBetButtons()
end
14.enableHoldButtons
This function enables the hold buttons.
function enableHoldButtons()
end
15.disableHoldButtons
This function enables the hold buttons.
function disableHoldButtons()
end
16.createDeck
This function creates a deck of cards.
function createDeck()
end
17.holdCard
This function goes through the player's hand and determines which cards are being held.
function holdCard(event)
end
18.betMax
This function is called when the player bets the maximum amount.
function betMax()
end
19.bet
This function is called when the player places an actual bet.
function bet()
end
20.doDeal
This function handles the dealing of the cards.
function doDeal()
end
21.dealInitialHand
This function deals the initial hand.
function dealInitialHand()
end
22.dealNewCards
This function deals the second part of the hand.
function dealNewCards()
end
23.getHand
This function figures out the player's hand.
function getHand()
end
24.newGame
This function resets all the variables and starts a new game.
function newGame()
end
25.awardWinnings
This function awards the player the money won.
function awardWinnings(theHand, theAward)
end
26.resetCardsYPosition
This function resets the cards' y position after a player holds a card.
function resetCardsYPosition()
end
27.generateCard
This function generates a random card.
function generateCard()
end
28.getCard
This function calls getGenerateCard and puts it on the screen.
function getCard(index)
end
29. Implementing setupButtons
Now that we have all our functions stubbed out we are going to start implementing them. Implement setupButtons as shown below.
function setupButtons()
cashOutButton = display.newImage("buttonCashOut.png",89,572)
cashOutButton.anchorX, cashOutButton.anchorY = 0,0
local holdButtonPosition = 186
for i=1,5 do
local tempHoldButton = display.newImage("buttonHold1.png",holdButtonPosition + (93*(i-1)),572)
tempHoldButton.buttonNumber = i
tempHoldButton.anchorX, tempHoldButton.anchorY = 0,0
table.insert(holdButtons,tempHoldButton)
end
betMaxButton = display.newImage("buttonBetMax.png",679,572)
betMaxButton.anchorX, betMaxButton.anchorY = 0,0
betButton = display.newImage("buttonBet.png",775,572)
betButton.anchorX, betButton.anchorY = 0,0
dealButton = display.newImage("buttonDeal.png",869,572)
dealButton.anchorX, dealButton.anchorY = 0,0
instructionsText = display.newText( "", 400,400, native.systemFont, 30)
instructionsText:setFillColor(0,0,0)
enableBetButtons()
end
In setupButtons, we initialize our buttons as new images. We store the tempHoldButtons in a table so we can reference them without having to create five separate variables. We also setup instructionsText and enable the bet buttons. The display object has a method named newImage, which accepts the path to the image as well as an x and y position. As mentioned in the documentation, the newImage method has several optional parameters. In this tutorial, we are using the new Graphics 2.0 class so we must set the images anchorX and anchorY positions. You can learn more about migrating old projects to use the new Graphics 2.0 class in the Corona Documentation.
Add a call to setup at the very end of main.lua and then call setupButtons inside setup.
setup() -- Add to end of "main.lua"
function setup()
setupButtons()
end
If you were to test your application now, you should see the interface along with the buttons.
30. Implementing setupTextFields
In setupTextFields, we setup the text fields in the application. Take a look at its implementation below.
The display object has the method newText that takes as its parameters the text, x and y coordinates, the font, and font size. The newText method takes several optional parameters, which you can explore in the documentation. We are using the new setFillColor method from Graphics 2.0, which accepts percentages. In this example, we set the text fields to red. By setting the align property to right, the text will be aligned to the right within the text field. You must set the width property as well if you are using align. Call setupTextFields in setup.
function setup()
setupButtons()
setupTextFields()
end
If you run the application once more, you won't notice any change as the text fields are blank at the moment. They are ready for some text though.
31.randomSeed
Because we want to get a random card, we need to seed the random generator. If we wouldn't do this, then we would end up with the same randomness over and over. Enter the following code snippet inside our setup function.
function setup()
math.randomseed(os.time())
setupButtons()
setupTextFields()
end
32.createDeck
The implementation of the createDeck function isn't too difficult either. We loop through the suits table and append a number from 1 to 13 to it. We then insert it into the deck table. The images for our cards are named d1.png, h3.png, etc. with 1 being the ace and the 13 being the king.
function createDeck()
deck = {};
for i=1, 4 do
for j=1, 13 do
local tempCard = suits[i]..j
table.insert(deck,tempCard)
end
end
end
h2> Conclusion
This brings the first part of this two-part tutorial to a close. In the second part, we will finish the game. I hope to see you there.
In the first part of this tutorial, we set up the project and created the game's interface. We also created and implemented a function to create a deck of cards. In this second tutorial, we will create the game logic.
Sample Application
If you want to run the sample application of this tutorial, make sure to include images for the cars as I explained in the previous tutorial. Don't forget to include and update the dataSaver library mentioned in this tutorial.
1.enableDealButton
Update the implementation of the enableDealButton function as shown below.
function enableDealButton()
disableDealButton()
dealButton:addEventListener('tap',doDeal)
instructionsText.text = " "
end
We first call disableDealButton, which removes any previously added listeners, and add a tap listener, which invokes doDeal. The addEventListener method accepts an event and a callback. There are a number of events you can listen for, depending on the context in which you are calling it.
2.disableDealButton
As I mentioned in the previous section, in disableButton we remove any previously added listeners.
function disableDealButton()
dealButton:removeEventListener('tap',doDeal)
end
3.enableBetButtons
In enableBetButtons, we add tap listeners to betMaxButton and betButton, and give the player some instructions on how to place their bet.
function enableBetButtons()
betMaxButton:addEventListener('tap',betMax)
betButton:addEventListener('tap',bet)
instructionsText.text = "Place your Bet or Bet Max ($15)"
end
4.disableBetButtons
As in disableDealButton, we remove any previously added listeners in disableBetButtons.
function disableBetButtons()
betMaxButton:removeEventListener('tap',betMax)
betButton:removeEventListener('tap',bet)
end
5.enableHoldButtons
In enableHoldButtons, we loop through the holdButtons table and add a tap listener to each button.
function enableHoldButtons()
for i=1, #holdButtons do
holdButtons[i]:addEventListener('tap',holdCard)
end
end
6.disableHoldButtons
In the disableHoldButtons function, we also loop through the holdButtons table, but we remove previously added listeners instead of adding new listeners.
function disableHoldButtons()
for i=1, #holdButtons do
holdButtons[i]:removeEventListener('tap',holdCard)
end
end
7.generateCard
The implementation of generateCard requires a bit more explanation. We first generate a random number from 1 to the length of the deck table. We then create a temporary card using deck["randIndex]..".png" and store a reference in tempCard. What deck["randIndex]..".png" does, is grab a random element from the deck table, which would be something like c1 or h5 and adds .png to it. Because Lua is a dynamic language, we can add new properties to the objects. In this example, we add a isHolding property, which tells us whether the player is holding the card, a cardNumber property by getting a substring of the chosen deck element, and we do the same for the cardSuit property. Finally, we remove the chosen element from the deck table and return the array.
function generateCard()
local randIndex = math.random(#deck)
local tempCard = display.newImage(deck[randIndex]..".png")
tempCard.anchorX, tempCard.anchorY = 0,0
tempCard.isHolding = false
tempCard.cardNumber = tonumber(string.sub(deck[randIndex],2,3))
tempCard.cardSuit = string.sub(deck[randIndex],1,1)
table.remove(deck,randIndex);
return tempCard;
end
8.getCard
In getCard, we set the cardPosition, which is the x coordinate of the first card in the game's interface. We generate a card, add it to the playerHand table, and pass in a cardIndex variable, which will be a number between 1 and 5, representing one of the five cards. This enables us to position the cards in the proper order in the playerHand table. We set the position of each card by using an offset of (93 * (cardIndex - 1)). This means that the cards are 93 pixels apart from one another.
function getCard(index)
local cardPosition = 199
local tempCard = generateCard()
playerHand[cardIndex] = tempCard
tempCard.x = cardPosition + (93*(cardIndex-1))
tempCard.y = 257;
end
9.holdCard
In holdCard, we first obtain a reference to the button that was pressed by using its buttonNumber property. This enables us to check whether the card is in the playerHand table. If it is, we set isHolding to false and update the card's y coordinate. If the card isn't in the playerHand table, we set isHolding to true and update the card's y coordinate. If the player chooses to hold the card, it's y coordinate is decreased, which means the card is moved up slightly.
function holdCard(event)
local index = event.target.buttonNumber
if (playerHand[index].isHolding == true) then
playerHand[index].isHolding = false
playerHand[index].y = 257
else
playerHand[index].isHolding = true
playerHand[index].y = 200
end
end
10.resetCardsYPosition
In resetCardsYPosition, we loop through the playerHand table and see if any of the cards are being held. The ones that are, are moved back to their original position using the Transition library. The Transition library makes it very easy to move objects around and tween their properties.
function resetCardsYPosition()
for i=1,#playerHand do
if (playerHand[i].isHolding) then
transition.to(playerHand[i], {time=200,y=257})
end
end
end
11. Persisting Data Across Sessions
We want our game to be able to persist values or data across sessions. We can build a solution ourselves using Corona's io library, but in this tutorial we are going to use a third party solution. Arturs Sosins has made a handy little module for persisting data across game sessions.
Download the library and add the two files it contains to your project. To make use of the library, add the following line at the top of main.lua.
saver = require("dataSaver")
To make the library work in our project, we need to make a few minor changes to dataSaver.lua. Open this file and change require "json" to local json = require "json".
Change:
require "json"
To:
local json = require "json"
The second change we need to make is change all the occurrences of system.ResourceDirectory to system.DocumentsDirectory as shown below.
Change:
system.ResourceDirectory
To:
system.DocumentsDirectory
12.createDataFile
To set up a data store, insert the following code snippet below the setupTextFields function. Make sure to include the function definition as we didn't stub this function in the previous tutorial.
function createDataFile()
gameData = saver.loadValue("gameData")
if (gameData == nil) then
gameData = {}
gameData.numberOfCredits = 100
gameData.numberOfGames = 0
creditText.text = "100"
gamesText.text = "0"
saver.saveValue("gameData",gameData)
else
creditText.text = gameData.numberOfCredits
gamesText.text = gameData.numberOfGames
end
end
In createDataFile, we first attempt to load the gameData key from the saver into the gameData variable. The loadValue method returns nil if the key doesn't exist. If it doesn't exist, we initialize the gameData table, add numberOfCredits and numberOfGames properties, update the respective text fields, and save the gameData table by invoking saveValue on saver. If the key does exist, then we've already done this and we can populate the text fields with the correct values.
In the next step, we invoke the createDataFile function in the setup function as shown below.
function setup()
math.randomseed(os.time())
setupButtons()
setupTextFields()
createDataFile()
end
13.betMax
In betMax, we start by loading our data into gameData. If the number of credits is greater than or equal to 15, we go ahead and call doDeal. Otherwise, the player does not have enough credits to bet the maximum of 15 credits and we show the player an alert.
function betMax()
local gameData = saver.loadValue("gameData")
local numberOfCredits = gameData.numberOfCredits
if (numberOfCredits >= 15) then
enableDealButton()
betAmount = 15;
betText.text = betAmount
instructionsText.text = " "
doDeal()
else
local alert = native.showAlert( "Not Enough Credits", "You must have 15 or more Credits to Bet Max", { "OK"})
end
end
14.bet
In the bet function, we enable the deal button and remove the listener from betMaxButton. Since the player is doing a regular bet, she cannot play a maximum bet at the same time. We need to check if the number of credits is greater than or equal to 5 to make sure that the player isn't trying to bet more credits than they have left. If betAmount is equal to 15, we call doDeal as they've bet the maximum amount.
function bet()
enableDealButton()
betMaxButton:removeEventListener('tap',betMax)
instructionsText.text = " "
local numberOfCredits = tonumber(creditText.text - betAmount)
if (numberOfCredits >= 5) then
betAmount = betAmount + 5
betText.text = betAmount
else
doDeal()
end
if (betAmount == 15) then
doDeal()
end
end
15.doDeal
The doDeal function coordinates the dealing of the cards. If it's a new game, we deal the initial hand. Otherwise, we deal the player new cards.
function doDeal()
if(isNewGame == true) then
isNewGame = false
dealInitialHand()
else
dealNewCards()
end
end
16.dealInitialHand
We disable the bet buttons in dealInitialHand and enable the hold buttons. We invoke getCard five times, which generates the initial five cards. We then load gameData, update currentCredits, calculate newCredits, update credit text field, and save gameData.
function dealInitialHand()
disableBetButtons()
enableHoldButtons()
for i=1, 5 do
getCard(i)
end
local gameData = saver.loadValue("gameData")
local currentCredits = gameData.numberOfCredits
local newCredits = currentCredits - betAmount
creditText.text = newCredits
gameData.numberOfCredits = newCredits
saver.saveValue("gameData",gameData)
end
17.dealNewCards
In dealNewCards, we check whether the player is holding a card. If they are, then we get a reference to the current card, call removeSelf, set it to nil, and get a new card by invoking getCard(i).
function dealNewCards()
disableDealButton()
disableHoldButtons()
for i = 1, 5 do
if (playerHand[i].isHolding == false) then
local tempCard = playerHand[i]
tempCard:removeSelf()
tempCard = nil
getCard(i)
end
end
resetCardsYPosition()
getHand()
end
Whenever you remove something from the screen, you should always set it to nil to make sure it is setup for garbage collection properly.
18.getHand
The getHand function determines the player's hand. As you can see below, the function is pretty involved. Let's break if down to see what's going on. Start by implementing the getHand function as shown below.
function getHand()
table.sort(playerHand, function(a,b) return a.cardNumber < b.cardNumber end)
local frequencies = {}
for i=1, 13 do
table.insert(frequencies,0)
end
for i=1,#playerHand do
frequencies[playerHand[i].cardNumber] = frequencies[playerHand[i].cardNumber] + 1
end
local numberOfPairs = 0
local hasThreeOfAKind = false
local hasFourOfAKind = false
local winningHand = "Nothing"
local cashAward = 0
local isStraight = true
local isRoyalStraight = false
local isFlush = true
for i=0, #frequencies do
if (frequencies[i] == 2) then
numberOfPairs = numberOfPairs+1
end
if (frequencies[i] == 3) then
hasThreeOfAKind = true
end
if (frequencies[i] == 4) then
hasFour = true
end
end
if (numberOfPairs > 0) then
if(numberOfPairs == 1)then
winningHand = "1 pair"
cashAward = 1 * betAmount
else
winningHand = "2 pair"
cashAward = 2 * betAmount
end
end
if (hasThreeOfAKind) then
winningHand = "3 of A Kind"
cashAward = 3 * betAmount
end
if (hasFour) then
winningHand = "Four of A Kind"
cashAward = 7 * betAmount
end
if (numberOfPairs == 1 and hasThreeOfAKind) then
winningHand = "Full House"
cashAward = 6 * betAmount
end
if (playerHand[1].cardNumber == 1 and playerHand[2].cardNumber == 10 and playerHand[3].cardNumber == 11 and playerHand[4].cardNumber == 12 and playerHand[5].cardNumber == 13 )then
isRoyalStraight = true
end
for i=1, 4 do
if (playerHand[i].cardNumber+1 ~= playerHand[i+1].cardNumber) then
isStraight = false
break
end
end
for i=1, 5 do
if(playerHand[i].cardSuit ~= playerHand[1].cardSuit) then
isFlush = false
break
end
end
if (isFlush) then
winningHand = "Flush"
cashAward = 5 * betAmount
end
if (isStraight) then
winningHand = "Straight"
cashAward = 4 * betAmount
end
if (isRoyalStraight)then
winningHand = "Straight"
cashAward = 4 * betAmount
end
if (isFlush and isStraight) then
winningHand = "Straight Flush"
cashAward = 8 * betAmount
end
if (isFlush and isRoyalStraight) then
winningHand = "Royal Flush"
cashAward = 9 * betAmount
end
awardWinnings(winningHand, cashAward)
end
We start by calling table.sort on the playerHand table. The sort method does an in-place sort, it uses the < operator to determine whether an element of the table should come before or after another element. We can pass a comparison function as the second argument. In our example, we check whether the cardNumber property is less than the previous one. If it is, then it swaps the two elements.
We then create a frequencies table, populate it with thirteen zeros (0), loop through the playerHand table, and increment each index's number if the playerHand contains that number. For example, if you had two three's and three fives, then the frequencies table would be 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0.
In the next step we declare and set a number of local variables that we need in a few moments. We then loop through the frequencies table and check the numbers to see if the index contains a 2, which means we have a pair, a 3, which means we have three of a kind, or a 4, which means we have four of a kind. Next, we check the number of pairs, three of a kind, and four of a kind, and update the winningHand and cashAward values accordingly.
To check for a Royal Straight, we need to check if the first card is equal to an ace and the remaining cards would be ten, Jack, Queen, and King. To check for a regular Straight we loop through the playerHand and check if each subsequent cardNumber is one greater than the previous. To check for a Flush, we check if all the cards cardSuit keys were equal to the first card's cardSuit key.
At the end of getHand, we invoke awardWinnings.
19.awardWinnings
In awardWinnings, we show the player what hand they have in their hand and update the gameData settings. We save gameData and invoke newGame with a delay of three seconds.
function awardWinnings(theHand, theAward)
instructionsText.text = "You got "..theHand
winText.text = theAward
local gameData = saver.loadValue("gameData")
local currentCredits = gameData.numberOfCredits
local currentGames = gameData.numberOfGames
local newCredits = currentCredits + theAward
local newGames = currentGames + 1
gameData.numberOfCredits = newCredits
gameData.numberOfGames = newGames
saver.saveValue("gameData",gameData)
timer.performWithDelay( 3000, newGame,1 )
end
20.newGame
In newGame, we go through and reset all the variables, create a new deck of cards, and check if gameData.numberOfCredits is equal to zero. If it is, then the player has expended all their credits so we award them 100 more credits. Finally, we update the text fields.
function newGame()
for i=1,#playerHand do
playerHand[i]:removeSelf()
playerHand[i] = nil
end
playerHand = {}
deck = {}
betAmount = 0
isNewGame = true
createDeck()
enableBetButtons()
instructionsText.text = "Place your Bet or Bet Max ($15)"
winText.text = ""
betText.text = ""
local gameData = saver.loadValue("gameData")
if (gameData.numberOfCredits == 0)then
gameData.numberOfCredits = 100
saver.saveValue("gameData",gameData)
end
creditText.text = gameData.numberOfCredits
gamesText.text = gameData.numberOfGames
end
21. Testing the Game
Update the setup function by invoking the createDeck function as shown below and test the final result.
function setup()
math.randomseed(os.time())
createDeck();
setupButtons()
setupTextFields()
createDataFile()
end
Conclusion
In this tutorial, we created a fun and interesting Poker game. We did not implement the button to cash out yet, but you are free to do so in your game. I hope you learned something useful in this tutorial. Leave your feedback in the comments below.
The number of people browsing the web using mobile devices grows every day. It's therefore important to optimize websites and web applications to accommodate mobile visitors. The W3C (World Wide Web Consortium) is well aware of this trend and has introduced a number of APIs that help with this challenge. In this article, I will introduce you to one of these APIs, the Battery Status API.
Introduction
One of the most irritating limitations of the current generation of mobile devices is their limited battery life. It drives me crazy when the battery of my mobile phone dies when I'm out and about. No matter how small and convenient modern smartphones are, you need to remember to carry an adapter with you in case your phone or tablet runs out of juice.
As developers, we can help remedy this issue, but we usually prefer to ignore it. Until recently, developers had a good excuse as there wasn't an API to get information about the device's battery status. That excuse, however, is no longer valid thanks to the introduction of the Battery Status API. Let's take a few minutes to explore what the Battery Status API is all about.
1. What is it?
The Battery Status API, sometimes referred to as the Battery API, is an API that provides information about the system's battery level. It also defines events that are fired when changes of the battery level or status take place. Even though the Battery Status API is a W3C Candidate Recommendation, the specification hasn't changed since May 2012 so it is likely that the current API won't change much in the future.
There are several actions developers can take thanks to the availability of the Battery Status API. For example, a web application could temporarily pause a long-running process when it detects the battery level is low or critical. It's up to the developer to decide what is best for the user given the circumstances and battery level.
This may seem disruptive to the user, but by acting proactively it is possible to prevent, for example, the loss of data. If you're developing an application that manages content, then it may be useful to save the user's data more frequently when the battery is running low on power. Your users will thank you for it. It happens all too often that people lose data due to a dead battery.
Even though this may seem a bit far-fetched, but it's even possible to switch to a light-on-dark theme when the battery is running low on power. This causes the screen to draw less power from the battery and it may result in a few extra minutes of battery power.
I hope it's clear that we can take a number of precautions simply by being notified of the battery level of the device. It's time to take a look at the Battery Status API to see how all this works in practice.
2. Properties
The Battery Status API exposes four, read-only properties on the window.navigator.battery object.
charging: A boolean value that specifies whether the battery is charging or not. If the device doesn't have a battery or the value cannot be determined, the value of this property is set to true.
chargingTime: A number that specifies the number of seconds that remain until the battery is fully charged. If the battery is already fully charged or the device doesn't have a battery, then this property is set to 0. If the device isn't charging or it's unable to determine the remaining time, the value is Infinity.
dischargingTime: A number value that represents the number of seconds that remain until the battery is completely discharged. If the discharge time cannot be determined or the battery is currently charging, then the value is set to Infinity. If the device doesn't have a battery, then dischargingTime is also set to Infinity.
level: A number that specifies the current level of the battery. The value is returned as a float ranging from 0 (discharged) to 1 (fully charged). If the level of the battery cannot be determined, the battery is fully charged, or the device doesn't have a battery, then the value of level is equal to 1.
One element that surprised me when I was browsing the specification is that the chargingTime property holds the number of seconds until the battery is fully charged and not the number of seconds until the battery is fully discharged. Keep this in mind when working with the Battery Status API.
3. Events
The Battery Status API allows us to listen for four events, each of which can be mapped to the change of a property on window.navigator.battery.
chargingchange is fired when the device's charger is activated or deactivated.
chargingtimechange is fired when the remaining charging time changes.
dischargingtimechange is fired when the remaining time until the battery is fully discharged changes.
levelchange is fired when the battery level has changed.
It's important to note that you need to attach an event listener to window.navigator.battery if you wish to listen for the aforementioned events. Let's take a look at an example.
navigator.battery.addEventListener('levelchange', function(event) {
// Do something...
});
The available events of the Battery Status API give developers a lot of flexibility to alter the behavior of websites and web applications. What about support? That's a good question.
4. Detecting Support
Detecting support for the Battery Status API is easy. However, as I'll discuss in the next section on browser compatibility, it's important to detect support of the API due to the low number of browsers that currently support the Battery Status API. Take a look at the following code snippet to see how easy it is to test whether the Battery Status API is supported.
if (window.navigator && window.navigator.battery) {
// Grab the battery's information!
} else {
// Not supported
}
5. Browser Compatibility
Support for the Battery Status API support is pretty poor at this point. At the moment, Firefox is the only browser that provides support for the API without the use of a vendor prefix. This is interesting since the API has several use cases that could really help in a world where mobile devices are becoming more and more dominant as devices to browse the web. A polyfill for the Battery Status API doesn't exist yet for obvious reasons. A JavaScript library, no matter how powerful, cannot access information of a hardware component without the browser's permission.
6. Demo
In this section, I'd like to show you a simple demonstration of how the Battery Status API can be used and to show some of its key features. In the demo, I've included a <div> tag with an id of bt-results in which we'll show the data retrieved from the device's battery. This element is hidden by default and only shown if the browser supports the Battery Status API, which is only Firefox at the time of writing. The demo also includes a <div> tag with an id of log in which we'll log the events fired by the Battery Status API.
With regards to the JavaScript code, the first line tests if the browser supports the Battery Status API. If the test fails, the message "API not supported" is shown. Otherwise, we attach a handler to all the events of the Battery Status API using a for loop. The handler injects the values of the properties of the battery object in the appropriate placeholder elements. You can also view a live demo.
<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta name="author" content="Aurelio De Rosa"><title>Battery Status API Demo by Aurelio De Rosa</title><style>
*
{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body
{
max-width: 500px;
margin: 2em auto;
padding: 0 0.5em;
font-size: 20px;
}
h1
{
text-align: center;
}
.hidden
{
display: none;
}
.bs-info
{
font-weight: bold;
}
#log
{
height: 200px;
width: 100%;
overflow-y: scroll;
border: 1px solid #333333;
line-height: 1.3em;
}
.button-demo
{
padding: 0.5em;
margin: 1em;
}
.author
{
display: block;
margin-top: 1em;
}
</style></head><body><h1>Battery Status API</h1><span id="bs-unsupported" class="hidden">API not supported</span><div id="bt-results" class="hidden"><h3>Current Status</h3><div id="bs-status"><ul><li>Is battery in charge? <span id="in-charge" class="bs-info">unavailable</span></li><li>Battery will be charged in <span id="charging-time" class="bs-info">unavailable</span> seconds</li><li>Battery will be discharged in <span id="discharging-time" class="bs-info">unavailable</span> seconds</li><li>Current battery level: <span id="battery-level" class="bs-info">unavailable</span>/1</li></ul></div></div><h3>Log</h3><div id="log"></div><button id="clear-log" class="button-demo">Clear log</button><small class="author">
Demo created by <a href="http://www.audero.it">Aurelio De Rosa</a>
(<a href="https://twitter.com/AurelioDeRosa">@AurelioDeRosa</a>)</small><script>
window.navigator = window.navigator || {};
navigator.battery = navigator.battery ||
null;
if (navigator.battery === null) {
document.getElementById('bs-unsupported').classList.remove('hidden');
} else {
var log = document.getElementById('log');
document.getElementById('bt-results').classList.remove('hidden');
function updateInfo(event) {
if (event !== undefined) {
log.innerHTML = 'Event "' + event.type + '" fired<br />' + log.innerHTML;
}
document.getElementById('in-charge').innerHTML = (navigator.battery.charging ? "Yes" : "No");
document.getElementById('charging-time').innerHTML = navigator.battery.chargingTime;
document.getElementById('discharging-time').innerHTML = navigator.battery.dischargingTime;
document.getElementById('battery-level').innerHTML = navigator.battery.level;
}
var events = ['chargingchange', 'chargingtimechange', 'dischargingtimechange', 'levelchange'];
for (var i = 0; i < events.length; i++) {
navigator.battery.addEventListener(events[i], updateInfo);
}
updateInfo();
document.getElementById('clear-log').addEventListener('click', function() {
log.innerHTML = '';
});
}
</script></body></html>
Conclusion
In this article, we discussed the Battery Status API. As we've seen, it's pretty simple and offers developers the opportunity to improve the user experience of website and web applications. The Battery Status API exposes four properties and four events that we can use to change the behavior of websites and web applications by appropriately responding to changes of the device's battery status. Unfortunately, the only browser that supports the API is Firefox. However, we can expect a broader adoption in the near future. This means you can still prepare for broader support and adopt it in your next project.
2014-02-26T12:30:49.852Z2014-02-26T12:30:49.852ZAurelio De Rosahttp://code.tutsplus.com/tutorials/html5-battery-status-api--mobile-22795
Whether you're creating a mobile application or a web service, keeping sensitive data secure is important and security has become an essential aspect of every software product. In this tutorial, I will show you how to safely store user credentials using the application's keychain and we'll take a look at encrypting and decrypting user data using a third party library.
Introduction
In this tutorial, I will teach you how to secure sensitive data on the iOS platform. Sensitive data can be a user's account credentials or credit card details. The type of data isn't that important. In this tutorial, we will use iOS's keychain and symmetric encryption to securely store the user's data. Before we get into the nitty-gritty details, I'd like to give you an overview of what we're going to do in this tutorial.
Even though this tutorial focuses on iOS, the concepts and techniques can also be used on OS X.
iOS Keychain
On iOS and OS X, a keychain is an encrypted container for storing passwords and other data that needs to be secured. On OS X, it is possible to limit keychain access to particular users or applications. On iOS, however, each application has its own keychain to which only the application has access. This ensures that the data stored in the keychain is safe and unaccessible by third parties.
Keep in mind that the keychain should only be used for storing small pieces of data, such as passwords. With this article, I hope to convince you of the value of using the keychain on iOS and OS X instead of, for example, the application's user defaults database, which stores its data in plain text without any form of security.
On iOS, an application can use the keychain through the Keychain Services API. The API provides a number of functions for manipulating the data stored in the application's keychain. Take a look at the functions available on iOS.
SecItemAdd This function is used for adding an item to the application's keychain.
SecItemCopyMatching You use this function to find a keychain item owned by the application.
SecItemDelete As its name implies, this function can be used to remove an item from the application's keychain.
SecItemUpdate Use this function if you need to update an item in the application's keychain.
The Keychain Services API is a C API, but I hope that doesn't prevent you from using it. Each of the above functions accepts a dictionary (CFDictionaryRef), which contains an item class key-value pair and optional attribute key-value pairs. The exact meaning and purpose of each will become clear once we start using the API in an example.
Encryption and Decryption
When discussing encryption, you generally hear about two types of encryption, symmetric and asymmetric encryption. Symmetric encryption, on the one hand, uses one shared key for encrypting and decrypting data. Asymmetric encryption, on the other hand, uses one key for encrypting data and another separate, but related, key for decrypting data.
In this tutorial, we'll leverage the Security framework available on iOS to encrypt and decrypt data. This process takes place under the hood so we won't be directly interacting with this framework. We'll use symmetric encryption in our example application.
The Security framework offers a number of other services, such as Randomization services for generating cryptographically secure random numbers, Certificate, Key, and Trust Services for managing certificates, public and private keys, and trust policies. The Security framework is a low-level framework available on both iOS and OS X with C-based APIs.
Application Overview
In this tutorial, I will show you how you can use the Keychain Services API and symmetric encryption in an iOS application. We'll create a small application that securely stores photos taken by the user.
In this project, we'll use Sam SoffesSSKeychain, an Objective-C wrapper for interacting with the Keychain Services API. For encryption and decryption, we'll use RNCryptor, a third party encryption library.
Encrypting Data with RNCryptor
The RNCryptor library is a good choice for encrypting and decrypting data. The project is used by many developers and actively maintained by its creators. The library offers an easy to use Objective-C API. If you're familiar with Cocoa and Objective-C, you'll find it easy to use. The library's main features are listed below.
AES-256 Encryption
CBC Mode
Password Stretching with PBKDF2
Password Salting
Random IV
Encrypt-Then-Hash HMAC
Application Flow
Before we start building the application, let me show you what the typical flow of the application will look like.
When the user launches the application, she's presented with a view to sign in.
If she hasn't created an account yet, her credentials are added to the keychain and she's signed in.
If she has an account, but enters an incorrect password, an error message is shown.
Once she's signed in, she has access to the photos she's taken with the application. The photos are securely stored by the application.
Whenever she takes a photo with the device's camera or picks a photo from her photo library, the photo is encrypted and stored in the application's Documents directory.
Whenever she switches to another application or the device gets locked, she's automatically signed out.
Building the Application
Step 1: Project Setup
Fire up Xcode and create a new project by selecting the Single View Application template from the list of templates.
Name the project Secure Photos and set Device Family to iPhone. Tell Xcode where you want to save the project and hit Create.
Step 2: Frameworks
The next step is to link the project against the Security and Mobile Core Services frameworks. Select the project in the Project Navigator on the left, choose the first target named Secure Photos, and open the Build Phases tab at the top. Expand the Link Binary With Libraries drawer and link the project against the Security and Mobile Core Services frameworks.
Step 3: Dependencies
As I mentioned earlier, we'll be using the SSKeychain library and the RNCryptor library. Download these dependencies and add them to the project. Make sure to copy the files to your project and add them to the Secure Photos target as shown in the screenshot below.
Step 4: Creating Classes
We will display the user's photos in a collection view, which means we need to subclass UICollectionViewController as well as UICollectionViewCell. Select New > File... from the File menu, create a subclass of UICollectionViewController, and name it MTPhotosViewController. Repeat this step one more time for MTPhotoCollectionViewCell, which is a subclass of UICollectionViewCell.
Step 5: Creating the User Interface
Open the project's main storyboard and update the storyboard as shown in the screenshot below. The storyboard contains two view controllers, an instance of MTViewController, which contains two text fields and a button, and an instance of MTPhotosViewController. The MTViewController instance is embedded in a navigation controller.
We also need to create a segue from the MTViewController instance to the MTPhotosViewController instance. Set the segue's identifier to photosViewController. The MTPhotosViewController instance should also contain a bar button item as shown in the screenshot below.
To make all this work, we need to update the interface of MTViewController as shown below. We declare an outlet for each text field and an action that is triggered by the button. Make the necessary connections in the project's main storyboard.
In the MTPhotosViewController class, declare a property named username for storing the username of the currently signed in user and declare an action for the bar button item. Don't forget to connect the action with the bar button item in the main storyboard.
In MTViewController.m, add an import statement for the MTPhotosViewController class, the SSKeychain class, and the MTAppDelegate class. We also conform the MTViewController class to the UIAlertViewDelegate protocol.
The next step is implementing the login: action we declared earlier. We first check whether the user has already created an account by fetching the password for the account. If this is true, we use the application's keychain to see if the password entered by the user matches the one stored in the keychain. The methods provided by the SSKeychain library make it easy to read and manipulate data stored in the application's keychain.
We've set the view controller as the alert view's delegate, which means we need to implement the UIAlertViewDelegate protocol. Take a look at the implementation of alertView:clickedButtonAtIndex: shown below.
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
switch (buttonIndex) {
case 0:
break;
case 1:
[self createAccount];
break;
default:
break;
}
}
In createAccount, we leverage the SSKeychain class to securely store the username and password chosen by the user. We then call performSegueWithIdentifier:sender:.
- (void)createAccount {
BOOL result = [SSKeychain setPassword:self.passwordTextField.text forService:@"MyPhotos" account:self.usernameTextField.text];
if (result) {
[self performSegueWithIdentifier:@"photosViewController" sender:nil];
}
}
In prepareForSegue:sender:, we get a reference to the MTPhotosViewController instance, set its username property with the value of the usernameTextField, and reset the passwordTextField.
Open the main storyboard and add an UIImageView instance to the prototype cell of the MTPhotosViewController instance. Select the prototype cell (not the image view) and set its class to MTPhotosCollectionViewCell in the Identity Inspector on the right. With the prototype cell still selected, open the Attributes Inspector and set the identifier to PhotoCell.
Step 8: Implementing MTPhotosViewController
Start by importing the necessary header files in MTPhotosViewController.m as shown below. We also need to declare two properties, photos for storing the array of photos the collection view will display and filePath to keep a reference to the file path. You may have noticed that the MTPhotosViewController class conforms to the UIActionSheetDelegate, UINavigationControllerDelegate, and UIImagePickerControllerDelegate protocols.
I've also implemented a convenience or helper method, setupUserDirectory, for creating and setting up the necessary directories in which we'll store the user's data. In prepareData, the application decrypts the images that are stored in the user's secure directory. Take a look at their implementations below.
The bar button item in the view controller's navigation bar shows an action sheet allowing the user to choose between the device's camera and the photo library.
To handle the user's selection in the image picker controller, we need to implement imagePickerController:didFinishPickingMediaWithInfo: of the UIImagePickerControllerDelegate protocol as shown below. The image is encrypted using encryptData of the RNEncryptor library. The image is also added to the photos array and the collection view is reloaded.
If the application goes to the background, the user needs to be signed out. This is important from a security perspective. To accomplish this, the application delegate needs to have a reference to the navigation controller so it can pop to the root view controller of the navigation stack. Start by declaring a property named navigationController in MTAppDelegate.h.
In the view controller's viewDidLoad method, we set the application delegate's navigationController property as shown below. Keep in mind that this is just one way to handle this.
I have set the above property in ViewController'sviewDidLoad method as shown below.
In the application delegate, we need to update applicationWillResignActive: as shown below. It's as simple as that. The result is that the user is signed out whenever the application loses focus. It will protect the user's images stored in the application from prying eyes. The downside is that the user needs to sign in when the application becomes active again.
Build the project and run the application to put it through its paces.
Conclusion
In this tutorial, you learned how to use the Keychain Services API to store sensitive data and you also learned how to encrypt image data on iOS. Leave a comment in the comments below if you have any questions or feedback.
Xcode, the de facto integrated development environment (IDE) for iOS and OS X development, is a fantastic piece of software. It offers everything a Cocoa developer needs to go from a fragile idea to a successful application in Apple's App Store.
However, there are numerous tools and services that have become indispensable in the workflow of many Cocoa developers. In this article, I will highlight some of the tools that I use as well as some alternatives.
Source Control
In the Cocoa community, Git is arguably the most popular source code management (SCM) system. Even though Xcode's support for Git improves with every release, many Cocoa developers prefer a dedicated tool over Xcode's built-in support.
SourceTree
SourceTree is Atlassian's free Git client, available for OS X and Windows. It also supports Mercurial, but the majority of Cocoa developers choose for Git nowadays. Not only is SourceTree free, it is incredibly powerful and easy to use—even for people unfamiliar with Git.
Atlassian's Git client integrates nicely with Bitbucket as well as GitHub and FogCreek's Kiln. SourceTree even helps you with common workflows, such as Git-flow and Hg-flow. Even if you swear by the command line, SourceTree will make complex operations that much easier.
Tower
For years, Tower has been my Git client of choice. It is beautifully designed, powerful, and robust. It offer most of the features that you need on a daily basis and integrates seamlessly with Beanstalk, GitHub as well as some of your favorite editors, such as BBEdit and Coda.
The people at Fournova, the creators of Tower, have done an amazing job with Tower and they continue to amaze me with every release. Tower isn't free, but that shouldn't stop you from downloading the trial and taking it for a spin.
Kaleidoscope
Black Pixel's Kaleidoscope is your best friend when it comes to finding differences between files and folders. The concept may seem simple, but it actually isn't.
You may think that a file comparison application like Kaleidoscope isn't worth paying for, but I challenge you to give it a try and you'll be amazed. It works very, very well and I have come to rely on its intuitive user interface and powerful merging functionality.
GitHub
There are a number of platforms for hosting Git repositories, but GitHub is one of the most popular choices. It's the home of numerous open source projects, such as AFNetworking and CocoaPods.
Public repositories are free and paid plans are fairly inexpensive. With more than 5 million users and over 11 million repositories, GitHub has become a major player that many developers rely upon.
Coding
AppCode
Xcode is the de facto IDE if you're serious about Cocoa development. However, JetBrains doesn't degree with this statement and they've put their money where their mouth is by creating AppCode.
AppCode is JetBrains's answer to the complaints of many developers who've become frustrated with Xcode. It is an impressive tool to say the least and it can be used alongside Xcode. Even though it hasn't come feature par with Xcode yet, it does a better job at some of Xcode's key features, such as refactoring and code completion.
PaintCode
I know few people who enjoy writing drawing code and I'm sure the creator of PaintCode isn't fond of it either. PaintCode is a vector drawing application that generates Objective-C code on the fly.
Not only will PaintCode save you time, it will help you rely less on images to create your application's user interface. This makes it much easier to keep your application't user interface flexible, dynamic, and lightweight.
Dependency Management
CocoaPods
If you're an iOS or OS X developer and you're not using CocoaPods, then you're doing it wrong. CocoaPods started out as a small project with an ambitious goal. It has grown into the de facto dependency management solution for Cocoa development. Most popular libraries provide support for CocoaPods and even major companies, such as Facebook and Square, support CocoaPods.
CocoaPods works incredibly well, is surprisingly robust, and is integrated into JetBrains's AppCode, which I discussed earlier. CocoaPods will make your life as a Cocoa developer that much easier. Seriously, give it a try. You can thank me later.
Distribution
TestFlight
Not too long ago, distributing builds to testers was a pain in the neck—especially if those testers weren't very technical. TestFlight tackled this problem by creating a platform for over the air distribution of test builds. Distributing builds to testers has never been easier.
Although application provisioning continues to be a major hurdle for new Cocoa developers, ad hoc distribution has become a lot easier.
TestFlight also has a desktop application for OS X to make uploading a fresh build to their servers even easier. It automatically detects when you create a new archive and prompts you to upload the build to TestFlight's servers. That's what I call painless ad hoc distribution.
HockeyApp
HockeyApp is very similar to TestFlight, it helps developers distribute builds to testers. Like TestFlight, it has the ability to collect crash reports with server-side symbolication and it includes analytics to get an accurate idea of what devices and configurations your applications are tested on.
Like TestFlight, HockeyApp has a wonderful API. With the HockeyApp API, you can even fetch crash reports and update provisioning profiles.
Xcode Plugins
Alcatraz
Alcatraz is a package manager for Xcode. Say goodbye to manually copying files to some obscure directory. Installing plugins or color schemes with Alcatraz is a matter of clicking a button.
Alcatraz maintains a list of plugins, templates, and color schemes, which you can search and install with a click of a button. At the time of writing, Alcatraz isn't fully compatible with Xcode 5, but don't let that prevent you from giving it a try.
Debugging
Reveal
The goal of Reveal is simple yet impressive, it let's you inspect and manipulate your application's view hierarchy at runtime. The more I use Reveal, the more I appreciate and discover its power and capabilities.
It lets inspect your application's view hierarchy in two and three dimensions, modify the properties of individual views, and even zoom in on a subset of views to make debugging easier. Reveal isn't free, but it's worth every penny.
Charles
Charles is one of those tools that many people love once they start using it. It's a cross platform tool for monitoring network traffic. Why would this be useful for an Cocoa developer? From the moment your application needs to talk to a web service, you'll immediately see the benefit of a tool like Charles.
Instead of wondering why a request is returning a 404, you simply inspect the request in Charles to see if you're sending the correct headers with the request. Charles supports SSL, let's you filter traffic, and can even simulate a slow network connection.
SimPholders
Most iOS developers test their applications in the iOS Simulator—especially in the early stages of development. This means that you sometimes need to browse an application's sandbox. Sure, you can open the Finder and navigate to ~/Library/Application Support/iPhone Simulator/7.0.3/Applications/56A57F3E-CF48-47F6-BAE8-B8541BCEC13B/. Really?
SimPholders is a tiny application that lives in your menu bar. It gives you quick access to the sandboxes of the applications you've installed in the iOS Simulator. Do yourself a favor and grab a copy SimPholders. It's free.
Performance Monitoring
Crashlytics
TestFlight and HockeyApp have the ability to collect crash reports for you, but Crashlytics's sole focus is collecting and analyzing crashes—and it shows. Crashlytics is a free service and supports iOS and Android.
It not only collects and symbolicates crash reports, it inspects and analyzes crashes. The result is that it and shows you the severity of an issue, which helps you prioritize bug fixes. The Crashlytics desktop application automatically detects when a new archive is created and it automatically uploads the archive's dSYM file so it can symbolicate any incoming crash reports.
Crittercism
Crittercism goes one step further than Crashylitics by combining analytics, crash reporting, and application performance. The result is a service that allows developers to fine-tune their applications, spot critical bottlenecks, and prioritize bug fixes. It supports iOS, Android, Windows Phone, and web applications.
Conclusion
This list is by no means definitive. There are many more tools and services that make the life of an iOS developer easier and more enjoyable—or less frustrating. What tools and services do you use for iOS development?
In this tutorial, we are going to explore how to use the accelerometer, one of the many hardware sensors of modern smartphones, in an Android application. I'll explain what an accelerometer is and why it may be something you want to take advantage of in your Android applications.
Introduction
Before the dawn of smartphones, one of the few hardware components applications could interact with was the keyboard. But times have changed and interacting with hardware components is becoming more and more common.
Using gestures often feels more natural than interacting with a user interface through mouse and keyboard. This is especially true for touch devices, such as smartphones and tablets. I find that using gestures can bring an Android application to life, making it more interesting and exciting for the user.
In this tutorial, we'll use a gesture that you find in quite a few mobile applications, the shake gesture. We'll use the shake gesture to randomly generate six Lottery numbers and display them on the screen using a pretty animation.
1. Getting Started
Step 1: Project Setup
Start a new Android project in your favorite IDE (Integrated Development Environment) for Android development. For this tutorial, I'll be using IntelliJ IDEA.
If your IDE supports Android development, it'll have created a Main class for you. The name of this class may vary depending on which IDE you're using. The Main class plays a key role when your application is launched. Your IDE should also have created a main layout file that the Main class uses to create the application's user interface.
Since we're going to make use of a shake gesture, it's a good idea to lock the device's orientation. This will ensure that the application's user interface isn't constantly switching between portrait and landscape. Open the project's manifest file and set the screenOrientation option to portrait.
With our project set up, it's time to get our hands dirty and write some code. At the moment, the main activity class has an onCreate method in which we set the main layout by invoking setContentView as shown below.
public class Main extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
Depending on the IDE that you're using, you may need to add a few import statements to Main.java, the file in which your Main class lives. Most IDEs will insert these import statements for you, but I want to make sure we're on the same page before we continue. The first import statement, import android.app.Activity, imports the Activity class while the second import statement, import android.os.Bundle, imports the Bundle class. The third import statement, com.example.R, contains the definitions for the resources of the application. This import statement will differ from the one you see below as it depends on the name of your package.
In the next step, we'll leverage the SensorEventListener interface, which is declared in the Android SDK. To use the SensorEventListener interface, the Main activity class needs to implement it as shown in the code snippet below. If you take a look at the updated Main activity class, you'll find that I use the implements keyword to tell the compiler that the Main class implements the SensorEventListener interface.
public class Main extends Activity implements SensorEventListener {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
To use the SensorEventListener interface, you need to add another import statement as shown below. Most IDEs will intelligently add the import statement for you so you probably won't have to worry about this.
import android.hardware.SensorEventListener;
From the moment, you update the Main class implementation as shown above, you'll see a few errors pop up. This isn't surprising since we need two implement two required methods of the SensorEventListener interface.
If you're using IntelliJ IDEA, you should be prompted to add these required methods when you click the error. If you're using a different IDE, this behavior may be different. Let's add the two required methods by hand as shown in the code snippet below. Make sure to add these methods in the Main class and outside of the onCreate method.
@Override
public void onSensorChanged(SensorEvent event) {
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
Let's take a look at the onSensorChanged method. We will be using this method to detect the shake gesture. The onSensorChanged method is invoked every time the built-in sensor detects a change. This method is invoked repeatedly whenever the device is in motion. To use the Sensor and SensorEvent classes, we add two additional import statements as shown below.
Before we implement onSensorChanged, we need to declare two private variables in the Main class, senSensorManager of type SensorManager and senAccelerometer of type Sensor.
The SensorManager class is declared in android.hardware.SensorManager. If you're seeing any errors pop up, double-check that the SensorManager class is imported as well.
import android.hardware.SensorManager;
In the onCreate method, we initialize the variables we've just declared and register a listener. Take a look at the updated implementation of the onCreate method.
To initialize the SensorManager instance, we invoke getSystemService to fetch the system's SensorManager instance, which we in turn use to access the system's sensors. The getSystemService method is used to get a reference to a service of the system by passing the name of the service. With the sensor manager at our disposal, we get a reference to the system's accelerometer by invoking getDefaultSensor on the sensor manager and passing the type of sensor we're interested in. We then register the sensor using one of the SensorManager's public methods, registerListener. This method accepts three arguments, the activity's context, a sensor, and the rate at which sensor events are delivered to us.
public class Main extends Activity implements SensorEventListener {
private SensorManager senSensorManager;
private Sensor senAccelerometer;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
senSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
senAccelerometer = senSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
senSensorManager.registerListener(this, senAccelerometer , SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
There are two other methods that we need to override, onPause and onResume. These are methods of the Main class. It's good practice to unregister the sensor when the application hibernates and register the sensor again when the application resumes. Take a look at the code snippets below to get an idea of how this works in practice.
We can now start to focus on the meat of the application. It will require a bit of math to figure out when a shake gesture takes place. Most of the logic will go into the onSensorChanged method. We start by declaring a few variables in our Main class. Take a look at the code snippet below.
private long lastUpdate = 0;
private float last_x, last_y, last_z;
private static final int SHAKE_THRESHOLD = 600;
Let's now zoom in on the implementation of the onSensorChanged method. We grab a reference to the Sensor instance using the SensorEvent instance that is passed to us. As you can see in the code snippet below, we double-check that we get a reference to the correct sensor type, the system's accelerometer.
public void onSensorChange(SensorEvent sensorEvent) {
Sensor mySensor = sensorEvent.sensor;
if (mySensor.getType() == Sensor.TYPE_ACCELEROMETER) {
}
}
The next step is to extract the device's position in space, the x, y, and z axis. Take a look at the image below to better understand what I'm referring to. The x axis defines lateral movement, while the y axis defines vertical movement. The z axis is little trickier as it defines movement in and out of the plane defined by the x and y axes.
To get the values of each axis, we ask the sensor event for its values as shown below. The event's values attribute is an array of floats.
public void onSensorChange(SensorEvent sensorEvent) {
Sensor mySensor = sensorEvent.sensor;
if (mySensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = sensorEvent.values[0];
float y = sensorEvent.values[1];
float z = sensorEvent.values[2];
}
}
The system's sensors are incredibly sensitive. When holding a device in your hand, it is constantly in motion, no matter how steady your hand is. The result is that the onSensorChanged method is invoked several times per second. We don't need all this data so we need to make sure we only sample a subset of the data we get from the device's accelerometer. We store the system's current time (in milliseconds) store it in curTime and check whether more than 100 milliseconds have passed since the last time onSensorChanged was invoked.
public void onSensorChange(SensorEvent sensorEvent) {
Sensor mySensor = sensorEvent.sensor;
if (mySensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = sensorEvent.values[0];
float y = sensorEvent.values[1];
float z = sensorEvent.values[2];
long curTime = System.currentTimeMillis();
if ((curTime - lastUpdate) > 100) {
long diffTime = (curTime - lastUpdate);
lastUpdate = curTime;
}
}
}
The final piece of the puzzle is detecting whether the device has been shaken or not. We use the Math class to calculate the device's speed as shown below. The statically declared SHAKE_THRESHOLD variable is used to see whether a shake gesture has been detected or not. Modifying SHAKE_THRESHOLD increases or decreases the sensitivity so feel free to play with its value.
public void onSensorChange(SensorEvent sensorEvent) {
Sensor mySensor = sensorEvent.sensor;
if (mySensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = sensorEvent.values[0];
float y = sensorEvent.values[1];
float z = sensorEvent.values[2];
long curTime = System.currentTimeMillis();
if ((curTime - lastUpdate) > 100) {
long diffTime = (curTime - lastUpdate);
lastUpdate = curTime;
float speed = Math.abs(x + y + z - last_x - last_y - last_z)/ diffTime * 10000;
if (speed > SHAKE_THRESHOLD) {
}
last_x = x;
last_y = y;
last_z = z;
}
}
}
2. Finishing the Lottery Application
We now have an application that can detect a shake gesture using the accelerometer. Let's finish this project by using the shake gesture to pick six random lottery numbers. I'll show you how to generate a random number between 1 and 49, but you are free to modify my implementation to make it work with how the lottery is played in your country.
Let's start by setting up the application's main layout file that we'll use for the user interface. As you can see below, I use six frame layouts with a background of an image of a ball.
Each frame layout contains a text view that will display a randomly generated lottery number. Note that every frame layout and text view has an id to make sure that we can reference them later.
With the main layout ready to use, let's revisit the Main class. We start by creating getRandomNumber, a private method for generating six random numbers between 1 and 49.
private void getRandomNumber() {
ArrayList numbersGenerated = new ArrayList();
for (int i = 0; i < 6; i++) {
Random randNumber = new Random();
int iNumber = randNumber.nextInt(48) + 1;
if(!numbersGenerated.contains(iNumber)) {
numbersGenerated.add(iNumber);
} else {
i--;
}
}
}
We first create an ArrayList instance, which we use to store the six numbers in. In each loop of the for loop, we take advantage of Java's Random class to generate a random number. To make sure that we get a number between 1 and 49, we add 1 to the result. The next step is to check if the generated number is already in the array list, because we only want unique numbers in the array list.
Note that it may be necessary to add two more import statements to keep the compiler happy.
The final step is to display the randomly generated number in the user interface. We get a reference to the text views we created earlier and populate each text view with a random number. We also add a neat animation to the frame layouts, but feel free to omit or modify the animation.
private void getRandomNumber() {
ArrayList numbersGenerated = new ArrayList();
for (int i = 0; i < 6; i++) {
Random randNumber = new Random();
int iNumber = randNumber.nextInt(48) + 1;
if(!numbersGenerated.contains(iNumber)) {
numbersGenerated.add(iNumber);
} else {
i--;
}
}
TextView text = (TextView)findViewById(R.id.number_1);
text.setText(""+numbersGenerated.get(0));
text = (TextView)findViewById(R.id.number_2);
text.setText(""+numbersGenerated.get(1));
text = (TextView)findViewById(R.id.number_3);
text.setText(""+numbersGenerated.get(2));
text = (TextView)findViewById(R.id.number_4);
text.setText(""+numbersGenerated.get(3));
text = (TextView)findViewById(R.id.number_5);
text.setText(""+numbersGenerated.get(4));
text = (TextView)findViewById(R.id.number_6);
text.setText(""+numbersGenerated.get(5));
FrameLayout ball1 = (FrameLayout) findViewById(R.id.ball_1);
ball1.setVisibility(View.INVISIBLE);
FrameLayout ball2 = (FrameLayout) findViewById(R.id.ball_2);
ball2.setVisibility(View.INVISIBLE);
FrameLayout ball3 = (FrameLayout) findViewById(R.id.ball_3);
ball3.setVisibility(View.INVISIBLE);
FrameLayout ball4 = (FrameLayout) findViewById(R.id.ball_4);
ball4.setVisibility(View.INVISIBLE);
FrameLayout ball5 = (FrameLayout) findViewById(R.id.ball_5);
ball5.setVisibility(View.INVISIBLE);
FrameLayout ball6 = (FrameLayout) findViewById(R.id.ball_6);
ball6.setVisibility(View.INVISIBLE);
Animation a = AnimationUtils.loadAnimation(this, R.anim.move_down_ball_first);
ball6.setVisibility(View.VISIBLE);
ball6.clearAnimation();
ball6.startAnimation(a);
ball5.setVisibility(View.VISIBLE);
ball5.clearAnimation();
ball5.startAnimation(a);
ball4.setVisibility(View.VISIBLE);
ball4.clearAnimation();
ball4.startAnimation(a);
ball3.setVisibility(View.VISIBLE);
ball3.clearAnimation();
ball3.startAnimation(a);
ball2.setVisibility(View.VISIBLE);
ball2.clearAnimation();
ball2.startAnimation(a);
ball1.setVisibility(View.VISIBLE);
ball1.clearAnimation();
ball1.startAnimation(a);
}
We'll need to add a few more import statements to make all this work. Take a look at the code snippet below.
As for the animations, take a look at the contents of the animation file below. Note that you need to create an anim folder in your project's resources directory and name it move_down_ball_first.xml. By adjusting the values of the scale element, you can modify the animation's duration and the position of each ball.
All that's left for us to do is call getRandomNumber in onSensorChanged in the Main class. Take a look at the complete implementation of onSensorChanged shown below.
public void onSensorChange(SensorEvent sensorEvent) {
Sensor mySensor = sensorEvent.sensor;
if (mySensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = sensorEvent.values[0];
float y = sensorEvent.values[1];
float z = sensorEvent.values[2];
long curTime = System.currentTimeMillis();
if ((curTime - lastUpdate) > 100) {
long diffTime = (curTime - lastUpdate);
lastUpdate = curTime;
float speed = Math.abs(x + y + z - last_x - last_y - last_z)/ diffTime * 10000;
if (speed > SHAKE_THRESHOLD) {
getRandomNumber();
}
last_x = x;
last_y = y;
last_z = z;
}
}
}
Conclusion
In this tutorial, I've shown you how the accelerometer works and how you can use it to detect a shake gesture. Of course, there are many other use cases for the accelerometer. With a basic understanding of detecting gestures using the accelerometer, I encourage you to experiment with the accelerometer to see what else you can do with it.
In this tutorial, you'll learn how to use Apple's Sprite Kit framework to recreate a Missile Command game for the iPad. Along the way, you'll learn more about several core concepts such as, sprites, touches, physics, collisions, and explosions. The goal of the second tutorial is to add physics, collisions, and explosions. The second tutorial also expands the game experience by adding a multi-player mode.
Memories
Do you remember Missile Command? Atari released the original game on March 8, 1981. It was an action game built for Arcade systems and became very popular around the world.
Since its release in 1981, several adaptations were made for various platforms. It's now time for you to recreate the original game using modern technologies, iOS, Objective-C, and, the iPad.
You can still play the original game. Take a look over at IGN and relive some of your childhood memories.
Final Preview
The below screenshot gives you an idea of what the final result will look like. If you like what you see, then let's get started.
Requirements
To complete this tutorial, you'll need Xcode 5 and the latest iOS 7 SDK. You can download Xcode 5 and the SDK from the iOS Dev Center.
At the end of each section, you'll find one or more challenges. The objective of these challenges is to help you learn the techniques and technologies used in this tutorial. Some are easy while others will be more challenging, requiring a good grasp of the Sprite Kit framework. With the exception of the last challenge, remember that the challenges are not required to complete the tutorial. However, we hope you give them a try.
If you have questions about the Sprite Kit framework, we suggest you take a look at our other Sprite Kit tutorials on Mobiletuts+.
1. Project Setup
Launch Xcode 5 and create a new Sprite Kit project by selecting New > Project... from the File menu. Choose the SpriteKit Game template, name your project Missile Command, and select iPad from the Devices menu.
With the project set up, add the project's resources, which you can download using the link at the top of the page. We'll start by focusing on the MyScene class. Inside MyScene.m, you'll find two methods, initWithSize: and touchesBegan:withEvent:.
In initWithSize:, remove or comment out the code that is related to the SKLabelNode instance as we don't need it in this tutorial. Take a look at the updated implementation of initWithSize: below.
In touchesBegan:withEvent:, we do something similar. For now, the method should only contain the logic for touch detection and the location of the user's touch. These changes will give you a compiler warning, but we'll get rid of that later in this tutorial.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
}
}
Your next step is to create two new classes. The first class, MenuScene, will be used for the main menu and the second class, MultiScene, will be used for the multiplayer interface. Both classes are subclasses of SKScene. The MyScene class will be used to implement the single-player interface.
As we saw in the MyScene class, we need to implement an initializer for each class. For now, the initializer only sets the scene's background color as we saw in MyScene.m. The initializer of the MenuScene class also needs to set up the game title. To do that, we need to take care of a few things.
Create a SKSpriteNode object.
Set its zPosition.
Set its scale.
Set its position.
Add the object as a child to the class.
Take a look at the implementation of initWithSize: of the MenuScene class for clarification.
You should now have a main menu and a title. It's time to update the main view controller so that it displays the menu every time the application is launched. Open ViewController.m, locate the viewDidLoad method, and replace the SKScene instance with an instance of the MenuScene class as shown below.
- (void)viewDidLoad {
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
SKScene * scene = [MenuScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
Don't forget to import the header file of the MenuScene class.
#import "ViewController.h"
#import "MenuScene.h"
Great. It's time to build your project and run it to verify that everything is working correctly. You should see an interface similar to the screenshot below.
Challenge: To create a unique experience, we challenge you to do the following.
Modify the background colors of each scene.
Play with the properties of the MenuScene title object.
2. Objects, Flowers, Monsters, and Missiles
Before adding objects to the game, we need to add the ability to navigate the game. Declare two UIButton instances in the MenuScene class and a variable, sizeGlobal, of type CGSize to store the size of the screen. The latter will help us with positioning the objects on the screen. Update MenuScene.m as shown below.
Next, declare three methods in the MenuScene class.
didMoveToView: to present the buttons when the game transitions to the menu view.
moveToSinglePlayerGame which transitions the game to the MyScene scene and removes the buttons.
moveToMultiPlayerGame which transitions the game to the MultiScene scene and also removes the buttons.
In didMoveToView:, the application verifies if the scene transition took place without issues. If no issues popped up, the application loads the resources, displays them to the user, and instantiates the buttons.
In didMoveToView:, we call moveToSinglePlayerGame and moveToMultiPlayerGame. Let's implement these methods next. In each method, we need to do the following.
Create an instance of either MyScene or MultiScene.
Create a scene transition.
Present the new scene.
Remove the buttons.
Can you implement these methods on your own? If you're unsure, take a look at their implementations below.
Build your project and run the application in the iOS Simulator or on a physical device. The game should now look similar to the screenshot below.
The main menu is finished. It's time to focus on the game's single-player interface (MyScene). The MyScene class will contain most of the game's logic. The first step is to position the bullet-shooting flowers at the bottom of the screen. We'll create three flowers, each of which will have unique properties.
You will also track the number of exploded missiles and display that value in another SKLabelNode named labelMissilesExploded. As you can see below, we'll need a number of instance variables or ivars. The purpose of each ivar will become clear when we update the initWithSize: method in MyScene.
#import "MyScene.h"
@interface MyScene () {
CGSize sizeGlobal;
SKLabelNode *labelflowerBullets1;
SKLabelNode *labelflowerBullets2;
SKLabelNode *labelflowerBullets3;
SKLabelNode *labelMissilesExploded;
int position;
int monstersDead;
int missileExploded;
int flowerBullets1;
int flowerBullets2;
int flowerBullets3;
}
@end
In initWithSize:, we instantiate and configure the various SKLabelNode instances.
In initWithSize:, we also call addFlowerCommand, which we haven't covered yet. The implementation isn't difficult as you can see below. In addFlowerCommand, we instantiate three SKSpriteNode instances, one for each flower, and position them at the bottom of the screen with the help of position.
- (void)addFlowerCommand {
for (int i = 1; i <= 3; i++) {
SKSpriteNode *flower = [SKSpriteNode spriteNodeWithImageNamed:@"flower.png"];
flower.zPosition = 2;
flower.position = CGPointMake(position * i - position / 2, flower.size.height / 2);
[self addChild:flower];
}
}
You can now build the project and run it in the iOS Simulator to see the game's single-player interface.
We've got flowers, but we still need some monsters. It's time to implement the addMonstersBetweenSpace: method. In this method, we create three monsters and position them between the flowers. The position of the monsters is determined by the spaceOrder variable. We randomly position the monsters by using getRandomNumberBetween:to:, which generates a random number between two given numbers.
If you build and run your application, the result should look similar to the screenshot below.
Challenge: This is great, but we really need more monsters. We challenge you to do the following.
Add more monsters.
Add more flowers.
Modify the movement of the missiles.
3. User Interaction
We can see some movement, but we need the key component of the game, user interaction. We add user interaction by leveraging two methods, touchesBegan:withEvent: and positionOfWhichFlowerShouldBegin:. To add user interaction we must take the following into account.
Each flower can only shoot ten bullets.
You must detect the location of the user's touch and use the closest flower to shoot the bullet.
The bullet should move to the location of the user's touch.
The bullet should explode at the location of the user's touch.
The updated implementation of touchesBegan:withEvent: is shown below.
Even though that's a lot of code to go through, it isn't that complicated. You check if there are any bullets left for each flower and detect the zone in which the user tapped by invoking positionOfWhichFlowerShouldBegin:, which we'll discuss in a moment. At the same time, we verify whether the user tapped below the flower, which prevents the flower from shooting a bullet.
if (location.y < 120) return;
The positionOfWhichFlowerShouldBegin: method returns the position that corresponds to a specific zone in which the user has tapped. Remember that you divided the screen into three parts to position the three flowers so you'll need to detect the zone in which the user has tapped and link it to one of the flowers. The flower that shoots the bullet will be the one closest to the user's tap. This is what the implementation of positionOfWhichFlowerShouldBegin: looks like.
- (int)positionOfWhichFlowerShouldBegin:(int)number {
return position * number - position / 2;
}
To make the bullets move, we need to create another SKSpriteNode instance. Each bullet has a duration and an SKAction associated with it. This logic is also included in touchesBegan:withEvent:. Take a look at the updated implementation below.
If you build and run your application, the flowers should be able to shoot bullets. They don't explode yet, but we'll take care of that in the next tutorial.
Challenge: Are you up for another challenge? Take a look at these challenges.
Create and define different bullet movements.
Modify the duration of the movement of each bullet.
Conclusion
If you've followed the steps in this tutorial, you should now have the foundation of the Missile Command game using the Sprite Kit framework. If you have any questions or feedback, feel free to leave a comment below.
2014-03-05T12:30:10.000Z2014-03-05T12:30:10.000ZJorge Costa and Orlando Pereirahttp://code.tutsplus.com/tutorials/build-missile-command-with-sprite-kit-project-setup--mobile-21645
In the previous tutorial, we laid the foundation of our Missile Command game by creating the project, setting up the single-player scene, and adding user interaction. In this tutorial, you'll expand the game experience by adding a multi-player mode as well as physics, collisions, and explosions.
Final Preview
Take a look at the next screenshot to get an idea of what we're aiming for.
Pick Up Where We Left Off
If you haven't already, we strongly advise you to complete the previous tutorial to make sure we can build upon the foundation we laid in the first tutorial. In this tutorial, we zoom in on a number of topics, such as physics, collisions, explosions, and adding a multi-player mode.
1. Enabling Physics
The Sprite Kit framework includes a physics engine that simulates physical objects. The physics engine of the Sprite Kit framework operates through the SKPhysicsContactDelegate protocol. To enable the physics engine in our game, we need to modify the MyScene class. Start by updating the header file as shown below to tell the compiler the SKScene class conforms to the SKPhysicsContactDelegate protocol.
The SKPhysicsContactDelegate protocol enables us to detect if two objects have collided with one another. The MyScene instance must implement the SKPhysicsContactDelegate protocol if it wants to be notified of collisions between objects. An object implementing the protocol is notified whenever a collision begins and ends.
Since we will be dealing with explosions, missiles, and monsters, we'll define a category for each type of physical object. Add the following code snippet to the header file of the MyScene class.
Before we can start exploring the physics engine of the Sprite Kit framework, we need to set the gravity property of the physics world as well as its contactDelegate. Update the initWithSize: method as shown below.
In our game, the physics engine is used to create three types of physics bodies, bullets, missiles, and monsters. When working with the Sprite Kit framework, you use dynamic and static volumes to simulate physical objects. A volume for a group of objects is a volume that contains each object of the group. Dynamic and static volumes are an important element for improving the performance of the physics engine, especially when working with complex objects. In our game, we'll define two type of volumes, circles with a fixed radius and custom objects.
While circles are available through the SKPhysicsBody class, custom object require a bit of extra work from our part. Because the body of a monster isn't circular, we must create a custom volume for it. To make this task a little bit easier, we'll use a physics body path generator. The tool is straightforward to use. Import your project's sprites and define the enclosing path for each sprite. The Objective-C code to recreate the path is shown below the sprite. As an example, take a look at the following sprite.
The next screenshot shows the same sprite with an overlay of the path generated by the physics body path generator.
If any object touches or overlaps an object's physics boundary, we are notified of this event. In our game, the objects that can touch the monsters are the incoming missiles. Let's start by using the generated paths for the monsters.
To create a physics body, we need to use a CGMutablePathRef structure, which represents a mutable path. We use it to define the outline of the monsters in the game.
Revisit addMonstersBetweenSpace: and create a mutable path for each monster type as shown below. Remember that there are two types of monsters in our game.
With the path ready to use, we need to update the monster's physicsBody property as well as a number of other properties. Take a look at the following code snippet for clarification.
The categoryBitMask and contactTestBitMask properties of the physicsBody object are an essential part and may need some explaining. The categoryBitMask property of the physicsBody object defines to which categories the node belongs. The contactTestBitMask property defines which categories of bodies cause intersection notifications with the node. In other words, these properties define which objects can collide with which objects.
Because we are configuring the monster nodes, we set the categoryBitMask to MonsterCategory and contactTestBitMask to MissileCategory. This means that monsters can collide with missiles and this enables us to detect when a monster is hit by a missile.
We also need to update our implementation of addMissilesFromSky:. Defining the physics body for the missiles is much easier since each missile is circular. Take a look at the updated implementation below.
At this point, the monsters and missiles in our game should have a physics body that will enable us to detect when any of them collide with one another.
Challenge: The challenges for this section are as follows.
Read and understand the SKPhysicsBody class.
Create different physics bodies for the monsters.
2. Collisions and Explosions
Collisions and explosions are two elements that are closely associated. Every time a bullet shot by a flower reaches its destination, the user's touch, it explodes. That explosion can cause a collision between the explosion and any missiles in the vicinity.
To create the explosion when a bullet reaches its target, we need another SKAction instance. That SKAction instance is in charge of two aspects of the game, define the explosion's properties and the explosion's physics body.
To define an explosion, we need to focus on the explosion's SKSpriteNode, its zPosition, scale, and position. The position is the location of the user's touch.
To create the explosion's physics body, we need to set the node's physicsBody property as we did earlier. Don't forget to correctly set the categoryBitMask and contactTestBitMask properties of the physics body. We create the explosion in touchesBegan: as shown below.
In touchesBegan:, we've updated the bullet's action. The new action must call the callExplosion action before it's removed from the scene. To accomplish this, we've updated the following line of code in touchesBegan:.
Build the project and run the application to see the result of our work. As you can see, we still need to detect collisions between the explosions and the incoming missiles. This is where the SKPhysicsContactDelegate protocol comes into play.
There's one delegate method that is of special interest to us, the didBeginContact: method. This method will tell us when a collision between an explosion and a missile is taking place. The didBeginContact: method takes one argument, an instance of the SKPhysicsContact class, which tells us everything we need to know about the collision. Let me explain how this works.
An SKPhysicsContact instance has a bodyA and a bodyB property. Each body points to a physics body that's involved in the collision. When didBeginContact: is invoked, we need to detect what type of collision we're dealing with. It can be (1) a collision between an explosion and a missile or (2) a collision between a missile and a monster. We detect the collision type by inspecting the categoryBitmask property of the physics bodies of the SKPhysicsContact instance.
Finding out which type of collision we're dealing with is pretty easy thanks to the categoryBitmask property. If bodyA or bodyB has a categoryBitmask of type ExplosionCategory, then we know it's a collision between an explosion and a missile. Take a look at the code snippet below for clarification.
If we've encountered a collision between an explosion and a missile, then we grab the node that is associated with the missile's physics body. We also need to assign an action to the node, which will be executed when the bullet hits the missile. The task of the action is to remove the missile from the scene. Note that we don't immediately remove the explosion from the scene as it may be able to destroy other missiles in its vicinity.
When a missile is destroyed, we increment the missileExploded instance variable and update the label that displays the number of missiles the player has destroyed so far. If the player has destroyed twenty missiles, they win the game.
- (void)didBeginContact:(SKPhysicsContact *)contact {
if ((contact.bodyA.categoryBitMask & ExplosionCategory) != 0 || (contact.bodyB.categoryBitMask & ExplosionCategory) != 0) {
// Collision Between Explosion and Missile
SKNode *missile = (contact.bodyA.categoryBitMask & ExplosionCategory) ? contact.bodyB.node : contact.bodyA.node;
[missile runAction:[SKAction removeFromParent]];
//the explosion continues, because can kill more than one missile
NSLog(@"Missile destroyed");
// Update Missile Exploded
missileExploded++;
[labelMissilesExploded setText:[NSString stringWithFormat:@"Missiles Exploded: %d",missileExploded]];
if(missileExploded == 20){
SKLabelNode *ganhou = [SKLabelNode labelNodeWithFontNamed:@"Hiragino-Kaku-Gothic-ProN"];
ganhou.text = @"You win!";
ganhou.fontSize = 60;
ganhou.position = CGPointMake(sizeGlobal.width/2,sizeGlobal.height/2);
ganhou.zPosition = 3;
[self addChild:ganhou];
}
} else {
// Collision Between Missile and Monster
}
}
If we're dealing with a collision between a missile and a monster, we remove the missile and monster node from the scene by adding an action [SKAction removeFromParent] to the list of actions executed by the node. We also increment the monstersDead instance variable and check if it's equal to 6. If it is, the player has lost the game and we display a message telling them the game is over.
Before running the game on your iPad, we need to implement the moveToMenu method. This method is invoked when the player loses the game. In moveToMenu, the game transitions back to the menu scene so the player can start a new game. Don't forget to add an import statement for the MenuScene class.
It's time to build the project and run the game to see the final result.
Challenge: The challenges for this section are as follows.
Change the rules of the game by modifying the number of monsters and bullets.
Make the game more challenging by modifying the game's dynamics. You could, for example, increase the speed of the missiles once you've used five bullets.
3. Multi-Player
In the game's multi-player mode, two players can challenge each other through a split-screen mode. The multi-player mode doesn't change the game itself. The main differences between the single-player and multi-player modes are listed below.
We need two sets of assets.
The position and orientation of the assets need to be updated.
We need to implement game logic for the second player.
Explosions need to be tested and caught on a per-explosion basis.
Only one player can win the game.
In multi-player mode, the game should look like the screenshot below.
This is the final challenge of this tutorial. It isn't as complicated as it may seem. The goal of the challenge is to recreate Missile Command by enabling multi-player mode. The source files of this tutorial contain two Xcode projects, one of which (Missile Command Multi-Player) contains an incomplete implementation of the multi-player mode to get you started with this challenge. Note that the MultiScene class is incomplete and it is your task to finish its implementation to successfully complete the challenge. You will find hints and comments (/* Work HERE - CODE IS MISSING */) to help you with this challenge.
You don't need to add additional methods or instance variables to complete the challenge. You only need to focus on implementing the logic for the multi-player mode.
The next screenshot shows you the current state of the multi-player mode.
Conclusion
We've covered a lot of ground in this short series on Sprite Kit. You should now be able to create games that are similar to Missile Command using the Sprite Kit framework. If you have any questions or comments, feel free to drop us a line in the comments.
2014-03-07T12:30:35.113Z2014-03-07T12:30:35.113ZJorge Costa and Orlando Pereirahttp://code.tutsplus.com/tutorials/build-missile-command-with-sprite-kit-user-interaction--mobile-21659
HTML5 has been a breath of fresh air for the web, which hasn't only affected the web as we know it. HTML5 provides a number of APIs that enable developers to create interactive websites and improve the user experience on mobile devices. In this article, we'll take a closer look at the Vibration API.
Do you remember when the PlayStation was first introduced in the nineties? If you do, then you may also remember that a small revolution was created with the introduction of the DualShock, a controller that introduced feedback by means of vibration. It was a tremendous success.
All my friends with a PlayStation console switch to the DualShock almost immediately and I was no exception. Why was it so successful? The key factor of its success was its ability to provide feedback to the player in the form of vibration, it made you feel more connected with the game as if you were in the game.
The DualShock was just the start of this trend. Controllers have become much more advanced with the Kinect and revolutionary products, such as the Oculus Rift and the incredibly popular Omni.
The web has become much more powerful and the result is that game development has moved to the web. Until a few years ago, games for the web relied on Flash and Silverlight. Today, however, these technologies are no longer needed to develop games for the web and this is largely due to HTML5.
With increased power comes increased responsibility. For the web, this means the need for well-defined APIs that help create a better user experience and the ability to take advantage of new technologies available on, for example, mobile devices. One such API is the Vibration API.
1. What is the Vibration API?
The Vibration API was designed to address use cases in which tactile feedback is necessary or desired. Virtually every modern, mobile device includes has the ability to vibrate. The Vibration API offers the ability to programmatically access the device's vibration capabilities and work with them. The API isn't meant to be used as a generic notification mechanism as the Web Notifications API was created for this exact purpose. Even though the Vibration API is a W3C Candidate Recommendation at the moment, the specifications hasn't changed for several months, which is an indication that the specification to reach the final stage, the W3C Recommendation, shortly.
As with native mobile applications, the possibilities of the Vibration API are endless. You can use it while playing a video so that the device vibrates on explosions. Games are another excellent fit for the Vibration API. Games already make extensive use of the hardware capabilities of mobile devices and the Vibration API is therefore a great fit for web games. Just remember why the DualShock became so popular and you'll understand what I mean.
2. Implementation
Now that we know what the Vibration API can do for us, let's see how we can use it. Let me start with some good news, the API is very easy to use–almost trivial. There's only one method you need to know about, vibrate. That's it. The vibrate method belongs to the window's navigator. The vibrate method accepts one parameter, which can be an integer or an array of integers.
If a single integer is passed to the vibrate method, the device vibrates for the duration of the integer in milliseconds. If you pass an array of numbers, a vibration pattern is defined. The integers at odd indexes tell the device how long to vibrate while the integers add even indexes indicate how long the pauses between the vibrations should be. To stop the device from vibrating, you can pass 0 to vibrate or invoke the method without any parameters.
An image is worth a thousand words, but, for developers, a code snippet is probably worth a billion words. Let's explore a few examples.
Step 1: Detecting Support
To detect whether the device's browser supports the vibration API, you can perform a simple check as shown below.
if (window.navigator && window.navigator.vibrate) {
// Shake that device!
} else {
// Not supported
}
Another option is to inspect the navigator object.
if ('vibrate' in navigator) {
// Shake that device!
} else {
// Not supported
}
Step 2: Vibrate Once
To tell the device to vibrate, we invoke the vibrate method and pass it an integer. For example, to tell the device to vibrate for one second we would do the following:
// Vibrate once for 1 second
navigator.vibrate(1000);
Step 3: Vibrate Multiple Times
To tell the device to vibrate several times with a pause between each vibration, we pass an array of integers to the vibrate method. If we want the device to vibrate twice with a pause of half a second and end with a vibration of two seconds, we would do the following:
// Vibrate three times
// First two vibrations last one second
// Last vibration lasts two seconds
// Pauses are half a second
navigator.vibrate([1000, 500, 1000, 500, 2000]);
Step 4: End Vibration
To stop the device from vibrating, we pass the vibrate method 0 or an empty array.
// Stop vibrating
navigator.vibrate(0);
Or:
// Stop vibrating
navigator.vibrate([]);
Browser Support
Support for the Vibration API is fairly good in desktop and mobile browsers. The major browsers that currently lack support for the API are Internet Explorer and Safari. Take a look at this summary to get an idea of which browsers support the API.
Firefox 11+: Prior to version 15, you need to use the -moz prefix.
Opera 17+: To use it in versions prior to 19, you need to activate the flag "Experimental Web Platform features".
Chrome 30+: To use it in versions prior to 32, you need to activate the flag "Experimental Web Platform features".
A real polyfill for the Vibration API doesn't exist. However, there is a polyfill that targets Firefox OS. It was created by Christian Heilmann and it's called mozVibrate-polyfill. You can find it on GitHub.
Demo
I'd like to end this tutorial with a simple demonstration of the Vibration API. It's a simple HTML5 page that contains three buttons, one to vibrate once, one to vibrate repeatedly, and a button to stop vibrating. The demo detects if the browser supports the API. It it doesn't, you'll see the message "API not supported" and the buttons are disabled. I recommend testing the demo on a mobile device.
In this article, we've learned about the Vibration API, what it is, how to use it, and when to use it. As we saw earlier, the API is very simple to use and provides us with an elegant way to improve the user experience on mobile devices, especially for things like movies and games. The Vibration API is supported fairly well on desktop and mobile browsers so nothing prevents you from using it today.
2014-03-10T11:30:24.000Z2014-03-10T11:30:24.000ZAurelio De Rosahttp://code.tutsplus.com/tutorials/html5-vibration-api--mobile-22585
The Android platform provides resources for handling media playback, which your apps can use to create an interface between the user and their music files. In this tutorial series, we will create a basic music player application for Android. The app will present a list of songs on the user device, so that the user can select songs to play. The app will also present controls for interacting with playback and will continue playing when the user moves away from the app, with a notification displayed while playback elapses.
Introduction
Building the music player will involve using the ContentResolver class to retrieve tracks on the device, the MediaPlayer class to play audio and the MediaController class to control playback. We will also use a Service instance to play audio when the user is not directly interacting with the app. You should be able to complete this series if you're an intermediate Android developer, so if you've already built a few apps, then this series shouldn't be a problem for you. Here is a preview of the final app:
In this tutorial, we will create the app and query the user device for audio files using the ContentResolver and Cursor classes. In the next part, we will use an Adapter instance to present the songs in a list view, starting playback when the user taps an item from the list. In the final installment of this series, we'll use the MediaController class to give the user control over playback, implement functions to skip forward and back, and include a shuffle function. After this series, we will explore other aspects of media playback that can enhance the app, such as handling audio focus, presenting media files in different ways, and playing streaming media.
1. Create and Configure a New Project
Step 1
Create a new Android project. If you are using Eclipse, then let the IDE (Integrated Development Environment) create a main Activity class and layout file for you. For some of the code we use in the series, you will need a minimum API level of 16, so you will need to take additional steps to support older versions. Once your project is created, open the project's Manifest file. Inside the manifest element, add the following permission:
We will use this permission to let music playback continue when the user's device becomes idle. Your Manifest should already contain an element for your main Activity class. Add the following attributes to the activity element to set the screenOrientation and launchMode:
We will stick to portrait orientation for simplicity. The launchMode will aid the process of navigating back to the app after moving away from it. We will display a notification indicating the song currently being played, tapping the notification will take the user back to the app. We are also going to use a Service class for music playback. Add the following line to the project's Manifest inside the application element and after the activity element:
Makes sure to alter the tools:context attribute if your main Activity class is named differently. The layout includes a ListView in which we will present the list of songs.
We are going to include two menu items for toggling the shuffle function and for exiting the app. Open your main menu file (res/menu/main.xml) and replace its contents with the following:
If you prefer, you can store the title strings in the res/values/strings.xml file. The two items refer to drawable files. Create your own or use these two images to start with:
We will also use an icon to display in the playback notification. Create one now or use the one below:
The code will refer to the images using the names rand, end, and play so make sure that you use the same file names. Copy the images to your project's drawables folder(s). We will implement the actions later.
2. Query the Device for Songs
Step 1
Let's query the user's device for audio files. First, add a new class to your project, naming it Song. We will use this class to model the data for a single audio file. Inside the class declaration, add three instance variables for the data we want to store for each track:
private long id;
private String title;
private String artist;
Next, add a constructor method in which we instantiate the instance variables:
We will store the songs in a list and display them in the ListView instance in the main layout. In onCreate, after setting the content view, retrieve the ListView instance using the ID we gave it in the main layout:
Next, in the main Activity class declaration, after the existing methods, create a helper method to retrieve the audio file information:
public void getSongList() {
//retrieve song info
}
Inside this method, create a ContentResolver instance, retrieve the URI for external music files, and create a Cursor instance using the ContentResolver instance to query the music files:
Now we can iterate over the results, first checking that we have valid data:
if(musicCursor!=null && musicCursor.moveToFirst()){
//get columns
int titleColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media._ID);
int artistColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.ARTIST);
//add songs to list
do {
long thisId = musicCursor.getLong(idColumn);
String thisTitle = musicCursor.getString(titleColumn);
String thisArtist = musicCursor.getString(artistColumn);
songList.add(new Song(thisId, thisTitle, thisArtist));
}
while (musicCursor.moveToNext());
}
We first retrieve the column indexes for the data items that we are interested in for each song, then we use these to create a new Song object and add it to the list, before continuing to loop through the results.
Back in onCreate, after the code we added, call this new method:
getSongList();
3. Display the Songs
Step 1
Now we can display the list of songs in the user interface. In the onCreate method, after calling the helper method we created a moment ago, let's sort the data so that the songs are presented alphabetically:
Collections.sort(songList, new Comparator<Song>(){
public int compare(Song a, Song b){
return a.getTitle().compareTo(b.getTitle());
}
});
We use the title variable in the Song class, using the get methods we added, to implement a compare method, sorting the songs by title.
Step 2
Let's define a layout to represent each song in the list. Add a new file to your project's res/layout folder, naming it song.xml and entering the following:
Feel free to amend the layout to suit your preferences. Each song in the list will be represented by title and artist text strings, so we will use the TextViews to display this data. Notice that the LinearLayout opening tag lists an onClick attribute. We will use this method in the main Activity class to respond to user taps on the songs in the list, playing the song represented by the list item that was tapped.
Step 3
We will use an Adapter to map the songs to the list view. Add a new class to your app, naming it SongAdapter or another name of your choice. When creating the class, give it the superclass android.widget.BaseAdapter. Eclipse should insert the following outline:
public class SongAdapter extends BaseAdapter {
@Override
public int getCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int arg0, View arg1, ViewGroup arg2) {
// TODO Auto-generated method stub
return null;
}
}
We'll pass the song list from the main Activity class and use the LayoutInflater to map the title and artist strings to the TextViews in the song layout we created.
After the instance variables, give the adapter a constructor method to instantiate them:
public SongAdapter(Context c, ArrayList<Song> theSongs){
songs=theSongs;
songInf=LayoutInflater.from(c);
}
Alter the content of the getCount method to return the size of the list:
@Override
public int getCount() {
return songs.size();
}
You can leave the getItem and getItemId methods untouched. Update the implementation of the getView method as shown below:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//map to song layout
LinearLayout songLay = (LinearLayout)songInf.inflate
(R.layout.song, parent, false);
//get title and artist views
TextView songView = (TextView)songLay.findViewById(R.id.song_title);
TextView artistView = (TextView)songLay.findViewById(R.id.song_artist);
//get song using position
Song currSong = songs.get(position);
//get title and artist strings
songView.setText(currSong.getTitle());
artistView.setText(currSong.getArtist());
//set position as tag
songLay.setTag(position);
return songLay;
}
We set the title and artist text by retrieving the correct Song instance from the list using the position index, mapping these strings to the views we added to the song layout file. We also set the position as the view tag, which will let us play the correct song when the user clicks an item in the list. Remember that the song.xml layout file included an onClick attribute. We will use the method listed there to retrieve the tag in the Activity.
Step 3
Back in the main Activity class, in the onCreate method after sorting the list, create a new instance of the Adapter class and set it on the ListView:
SongAdapter songAdt = new SongAdapter(this, songList);
songView.setAdapter(songAdt);
When you run the app, it should present the list of songs on the device, clicking them will cause the app to throw an exception at the moment, but we will implement the click handler in the next tutorial.
Conclusion
We've now set the app up to read songs from the user device. In the next part, we will begin playback when the user selects a song using the MediaPlayer class. We will implement playback using a Service class so that it will continue as the user interacts with other apps. Finally, we will use a MediaController class to give the user control over playback.
As with every major release, iOS 7 includes many new APIs that developers can take advantage of in their applications. In this tutorial, we'll take a look at a brand new framework introduced in iOS 7, the Multipeer Connectivity framework. This framework adds support for discovering, connecting to, and communicating with nearby services, such as iOS devices. In this tutorial, I will show you how to create a simple, multi-player game using this new framework.
Introduction
A wide range new APIs have been introduced in iOS 7, giving developers new possibilities and opportunities. The framework that's drawn my attention is the Multipeer Connectivity framework. It offers a number of neat features that I'd like to demonstrate in this tutorial.
1. Framework Overview
Before we start working with the Multipeer Connectivity framework, it's important to get to know the framework first. The purpose of the framework is to allow nearby devices to communicate with one another, which includes exchanging data such as images and video.
It's important to understand the meaning of the term nearby in the context of the Multipeer Connectivity framework. The framework defines nearby devices as devices that are on the same Wi-Fi network or can communicate over Bluetooth. The Multipeer Connectivity framework cannot be used to communicate between devices over the web, for example. That's not what the Multipeer Connectivity framework was designed for.
Before two devices can start exchanging data, they first need to connect to one another. This involves two phases, 1 discovering nearby devices and 2 establishing a session between devices. In the discovery phase, one device searches or browses for devices in its vicinity. If a device wishes to be discoverable, it needs to advertise itself. This enables the browsing device to find the advertising device.
When the first device finds an advertising device, it sends a request to the device to establish a connection, which the advertiser can accept or decline. By accepting the invitation, a session is established, which means the devices can start communicating. A connection between two devices has one of three possible states, 1 not connected, 2 connecting, or 2 connected. At any time, a device can end a session, which closes the connection between the devices.
Browsing
The Multipeer Connectivity framework provides two avenues for browsing for other devices. The first option is to use a specialized view controller subclass that does everything for you. This is the approach that we'll be using in this tutorial. Sending the invitation and establishing a session is all taken care of for you. Of course, this doesn't fit every use case. Another option is to manually implement the logic to browse for nearby devices. This, however, is a topic we won't cover in this tutorial.
In the context of the Multipeer Connectivity framework, a device is referred to as a peer and each peer has a unique identifier or peerID and a display name. The latter is the name the user has given its device, for example, "Gabriel's iPhone". The display name is an important piece of information as it's the only way that users know which device belongs to whom.
Data
The data that can be transferred between peers is limited to three types, 1NSData instances, 2 resources, such as files and documents, and 3 streaming data. In this tutorial, I'll show you how to send and receive data objects, that is, NSData instances. We won't cover resources and streams in this tutorial.
Data can be sent using one of two modes, a reliable mode, which is slower, but guarantees that the transmitted data is received by the receiver, and unreliable, which is much more performant, but doesn't guarantee that the data that's sent by one peer is received by the other or is received in the order that it was sent in. Which mode you choose depends on the needs of your application.
This is everything you need to know about the Multipeer Connectivity framework to get started. Let me end this introduction by mentioning the four classes that we'll be working with in this tutorial.
MCPeerID: An instance of this class represents a peer in a multipeer session.
MCSession: This class is used to manage communication between peers in a session.
MCAdvertiserAssistant: This class is used to help with advertising a peer to nearby devices.
MCBrowserViewController: This UIViewController subclass takes care of presenting nearby devices to the user and helps establishing a multipeer session.
2. Application Overview
Let's take a brief look at the sample application we're about to create. The goal of this tutorial is to show how to establish a connection between nearby devices and exchange data, instances of NSData, between the connected devices.
I'm sure you can think of many use cases for the Multipeer Connectivity framework. In this tutorial, however, I've chosen for a very simple game, which I've named Guess the Secret Number. In the game, one person picks a number within a predefined range that other players of the game need to guess. After every guess, the host of the game, the person who picked the number, informs the player whether she's made a correct guess and whether the secret number is higher or lower. That's simple enough, right?
3. Project Setup
Let's get started by creating a new project in Xcode as shown below. Select the Single View Application template from the list of templates in the iOS Application category and click Next.
Give your project a name, enter a company identifier, and select iPhone from the Devices menu. Click the Next button to continue.
Tell Xcode where you'd like to save the project files and click the Create button to continue.
4. Handling Multipeer Connectivity
There's quite a bit of boilerplate code that we need to write to establish and manage a multipeer session. To make sure that we don't repeat ourselves, we'll centralize this logic in a custom class that we can reuse in our project. That's what we'll do in this section.
Step 1
Create a new class in Xcode by selecting New > File... from the File menu. Choose Objective-C class from the iOS Cocoa Touch category and click Next.
Name the new class MPCHandler, set its superclass to NSObject, and click the Next button. Tell Xcode where you'd like to store the class files and hit Create.
Step 2
Open the header file of the MPCHandler class and start by adding an import statement for the Multipeer Connectivity framework as shown below.
Prior to Xcode 5, we first would have needed to link the project against the Multipeer Connectivity framework. In Xcode 5, however, this is no longer necessary thanks to the compiler's new Auto Linking feature. The compiler is smart enough to link the project against the appropriate libraries and frameworks, which means that we don't have to worry about this.
Since the MPCHandler class will be in charge of managing a multipeer session, it needs to conform to the MCSessionDelegate protocol as shown below. We also declare four properties that will help us with this task.
Notice that these are the classes that I mentioned earlier in this tutorial. The peerID object will represent the device and it will also be included in every communication between peers along with any data that is exchanged between peers. The session object contains and manages the multipeer session. We'll talk more about this class later in this tutorial. The browser object holds a reference to a MCBrowserViewController instance, which we'll use to discover nearby devices and to create a multipeer session between them. The advertiser object takes care of advertising a device as well as handling incoming invitations.
Before we take a look at the implementation of the MPCHandler class, we need to declare four methods as shown below. The first method, setupPeerWithDisplayName:, accepts one argument, the display or public name that will be used for the device. The advertiseSelf: method also takes one argument to indicate whether the device should be visible to other devices. The setupSession method will be in charge of setting up a multipeer session, while the setupBrowser method will be responsible for setting up the MCBrowserViewController instance.
Let's start with the implementation of the setupPeerWithDisplayName: method. In this method, we create an instance of the MCPeerID class and pass it displayName. That's it.
In setupSession, we create an MCSession instance by passing the peerID instance variable and set the session's delegate to our MPCHandler instance. It's important that we invoke setupPeerWithDisplayName: before we setup the session, because the peerID instance variable cannot be nil.
The implementation of setupBrowser couldn't be easier. All we do in this method is initialize an instance of MCBrowserViewController by passing the service type and the session we created in setupSession. The first argument is the type of service to browse for. It uniquely identifies the service and must be between 1 and 15 characters long. Note that the service type can only contain ASCII lowercase letters, numbers, and hyphens. We store a reference to the browser view controller in the _browser instance variable.
In advertiseSelf:, we initialize an instance of the MCAdvertiserAssistant class by passing the same service type we used to instantiate the MCBrowserViewController instance. We also pass the session object and we set the discovery info to nil since we don't provide any discovery info about the device to nearby devices during the discovery phase.
If the advertise parameter of advertiseSelf: is set to YES, we call start on the MCAdvertiserAssistant object to start the assistant and begin advertising the service. If advertise is set to NO, we stop the advertiser assistant and set the advertiser property to nil.
You may have noticed that Xcode displays a few warnings since we haven't implemented the required MCSessionDelegate methods yet. Let's get rid of these warnings by implementing the MCSessionDelegate protocol. The methods that we'll implement are shown below.
session:peer:didChangeState:: This delegate method is called every time the connection state of a peer changes. Remember that there are three possible states, not connected, connecting, and connected. We'll use this delegate method to keep track of the peers that connect and disconnect from the game.
session:didReceiveData:fromPeer:: This method is called every time the device receives data from another peer.
session:didStartReceivingResourceWithName:fromPeer:withProgress:: When the application has started receiving a resource, such as a file, from another peer, this method gets called. We won't cover this method in this tutorial.
session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:: As its name implies, this method is called when a resource was received by our application.
session:didReceiveStream:withName:fromPeer:: This method is called when a stream is received by the application. Since we won't be working with streams, we won't cover this method either.
Even though we won't be using the last three delegate methods in this tutorial, we need to implement them in our view controller to get rid of the compiler warnings.
In session:peer:didChangeState: and session:didReceiveData:fromPeer:, we post a notification using the notification center to make sure that any part of the application that is interested in the event can be notified about the event. This allows the view controllers of our application to add themselves as an observer of those notifications and respond to them if appropriately. This also means the implementation of these delegate methods is fairly simple as you can see below.
We start by creating the notification's userInfo dictionary and post the notification by passing in the dictionary as the third argument of postNotificationName:object:userInfo:. Notice that we post the notification inside a dispatch_async block to ensure that the notification is posted on the main thread. This is important as we cannot guarantee that the delegate method is invoked on the main thread. The notification, however, needs to be posted on the main thread to make sure that every observer receives the notification.
The implementation of session:didReceiveData:fromPeer: looks very similar as you can see below.
As I've mentioned earlier, the application will have two view controllers. Xcode's Single View Application template has only one by default. Let's add a second view controller right now. Select New > File... from the File menu, choose Objective-C class from the iOS Cocoa Touch category on the left, and click Next.
Make sure that the new class inherits from UIViewController and set the Class field to OptionsViewController. The checkboxes should remain unchecked. Click Next to continue.
Tell Xcode where you'd like to save the class files and hit Create.
6. Create User Interface
It's time to create the application's user interface. Open the project's main storyboard (Main.storyboard) to get started.
Step 1
Select the view controller in the storyboard and select Embed In > Navigation Controller from the Editor menu. The result is that the view controller is embedded in a navigation controller and a navigation bar is added to the top of the view controller.
Select the view controller's navigation bar, open the Attributes Inspector on the right, and set the Title field to MPCDemo.
Drag two UIBarButtonItem instances from the Object Library to the navigation bar and update their titles as shown below.
This is the view that players will see and use to enter a guess, send it to the other players, and keep track of previous guesses made in a game. To make all this possible, we need to add a text field, two buttons, and a text view. Take a look at the next screenshot for clarification. I've also included the attributes of each subview to help you with this task.
UITextField
Frame: X=20, Y=77, Width=280, Height=30
Placeholder Text: Guess a number (1 - 100)...
UIButton
Frame: X=254, Y=115, Width=46, Height=30
Title: Send
UIButton
Frame: X=20, Y=115, Width=48, Height=30
Title: Cancel
UITextView
Frame: X=20, Y=153, Width=280, Height=395
Text: Nothing (Delete the existing text)
Background Color: Light Gray Color
Text Color: White Color
Editable: No
This is how the view controller should look after you've added the four subviews to its view.
Step 2
Before we move on to the OptionsViewController, let's declare and connect the necessary outlets and actions in the ViewController class. Open ViewController.h and update its contents as shown below.
Revisit the project's main storyboard, select the ViewController instance, and connect the outlets and actions with the view controller's interface. If you're unfamiliar with this step, then let me show you how this works. Start by expanding the view controller's scene so you can see its associated objects.
The next step is to ctrl click or right click the View Controller - MPCDemo object in the list, which should bring up a black popup window. In this window, you should see the outlets and actions that we declared earlier as well as a number of other items. To connect the outlet of the text field, click the circle on the right of the txtGuess outlet and drag to the text field in the user interface. A blue line appears to visualize the connection. Take a look at the next screenshot to better understand the process.
Before you continue, make sure that you've connected every outlet and action to its corresponding view in the user interface.
Step 3
For the OptionsViewController class we need to add a new scene to the interface. Drag a UIViewController instance from the Object Library onto the canvas. To connect it with our existing scene, ctrl click or right click the bar button item labeled Options and drag to the new view controller. From the black popup window that appears, select Push from the list of options.
The class name of a new scene defaults to UIViewController, but that's something we want to change. At the bottom of the second scene, select the first item, open the Identity Inspector on the right, and set the Class field to OptionsViewController in the Custom Class section.
Select the navigation bar of the options view controller, and set the title to Options in the Attributes Inspector. Add five subviews to the view controller's view as shown below.
UITextField
Frame: X=20, Y=77, Width=280, Height=30
Placeholder Text: Your player name...
UISwitch
Frame: X=136, Y=141, Width=51, Height=31
UIButton
Frame: X=76, Y=231, Width=168, Height=30
Title: Browse for other players
UITextView
Frame: X=20, Y=269, Width=280, Height=241
Text: Nothing (Delete existing contents)
Editable: NO
UIButton
Frame: X=121, Y=518, Width=78, Height=30
Title: Disconnect
The next screenshot should give you an idea of what the final result should look like.
Step 4
To finish the user interface of the options view controller, let's declare some outlets and actions in the view controller's header file. Open OptionsViewController.h and update its contents as shown below.
Revisit the project's main storyboard and connect the outlets and actions with the user interface. We've completed the user interface of the game and we're ready to start writing some code to see the Multipeer Connectivity framework in action.
7. Discovering Other Players
The next step is to implement the OptionsViewController class, because we need a connection between two devices before we can play the game and focus on the game's logic. I'd like to start by showing you how to discover nearby players (devices) by using the built-in solution of the Multipeer Connectivity framework.
Step 1
The first step is to fetch the mpcHandler object we created earlier in the AppDelegate class. This implies that we need to access the mpcHandler object through the application delegate. Open OptionsViewController.m, navigate to the top of the file, and add an import statement for the header file of the AppDelegate class.
There's no need to instantiate an AppDelegate instance as this is automatically done during the application launch. In the view controller's viewDidLoad method, we grab a reference to the application delegate and set up the mpcHandler object. We initialize the peerID and session properties of the MPCHandler class by invoking the methods we declared and implemented earlier in this tutorial.
In viewDidLoad, we set up the peer by passing the device's name as the display name. We also initialize the session property of the MPCHandler instance that we will use later to establish a connection between peers. It's important that we do this after initializing the MCPeerID instance, because the session requires a valid peerID object during its initialization. Finally, we invoke advertiseSelf: and pass in the state of the switch in the options view controller's user interface. The default state of the switch is YES, which means that our device is discoverable by other devices.
Step 2
Presenting the browser view controller is as easy as it gets. However, the browser property of the MPCHandler instance needs to be initialized first. We want to show the browser view controller when the user taps the Browse for other players button. Let's implement the searchForPlayers: action next.
Presenting the browser view controller requires two steps. First, we invoke setupBrowser on the application delegate's mpcHandler object to initialize the MCBrowserViewController instance and we then present that instance to the user. Note that we first verify whether the session object of the MPCHandler instance is not equal to nil, because the session object is used to initialize the MCBrowserViewController instance.
If you run the application and search for other players, you should see a view similar to the one in the next screenshot.
To test the application, you need at least two devices, for example, the iOS Simulator and a physical device. To create the above screenshot, I ran the application in the iOS Simulator and on a device. If you tap on a device's name, the selected device is moved to a section labeled Invitees as shown below.
At the same time, an alert view is displayed on the second device, notifying the other player that you want to establish a connection. The invitee can either accept or decline the invitation. If the player accepts the invitation, the browser establishes a connection between the two devices.
When the second player accepts the invitation, the Done button of the browser view controller is enabled. However, if you try to tap the Cancel or Done buttons, you'll notice that nothing happens. Why is this? The explanation is simple, we first need to implement the MCBrowserViewControllerDelegate protocol in the OptionsViewController class. Let's do that now. First, however, update the searchForPlayers: action by setting the options view controller as the delegate of the browser view controller.
However, this results in another compiler warning as the OptionsViewController doesn't conform to the MCBrowserViewControllerDelegate protocol yet. To fix this, open OptionsViewController.h, import the header files of the Multipeer Connectivity framework, and make the OptionsViewController conform to the MCBrowserViewControllerDelegate protocol.
We need to implement two methods of the MCBrowserViewControllerDelegate protocol to enable the Cancel and Done buttons. The implementations are identical, that is, dismissing the browser view controller. Open the implementation file of the OptionsViewController class and add the two delegate methods as shown below.
If you run the application again, you'll see that the Cancel and Done buttons work as expected. By tapping the Done button, a connection is established behind the scenes and the browser view controller is dismissed.
8. Detecting Connection State Changes
The previous section was a key aspect of the Multipeer Connectivity framework. However, after tapping the Done button, we didn't notice any changes. Why is that? During the connection process, the devices that are connecting transition from the Not Connected state to the Connecting state, and, if all goes well, end in the Connected state. While these transitions take place, the session:peer:didChangeState: delegate method of the Multipeer Connectivity framework is called. As a result, the custom notification we set up in the MPCHandler class is posted on every state change.
It's our task to observe these notifications and respond appropriately when a peer is connected or disconnected from the user's device. Whenever the connection state changes, apart from the Connecting state, we'll update the text view of the OptionsViewController class with the players that are currently connected to the user's device.
First, we must add the view controller instance as an observer for MPCDemo_DidChangeStateNotification notifications. Update the view controller's viewDidLoad method as shown below.
Remember that a notification with name MPCDemo_DidChangeStateNotification is posted each time the connection state of a peer changes. When this happens, the view controller's peerChangedStateWithNotification: is invoked. The implementation of peerChangedStateWithNotification: isn't difficult as you can see below. We extract the state of the peer from the notification's userInfo dictionary and, if it's not equal to MCSessionStateConnecting, we update the view controller's text view to display the states of every peer.
- (void)peerChangedStateWithNotification:(NSNotification *)notification {
// Get the state of the peer.
int state = [[[notification userInfo] objectForKey:@"state"] intValue];
// We care only for the Connected and the Not Connected states.
// The Connecting state will be simply ignored.
if (state != MCSessionStateConnecting) {
// We'll just display all the connected peers (players) to the text view.
NSString *allPlayers = @"Other players connected with:\n\n";
for (int i = 0; i < self.appDelegate.mpcHandler.session.connectedPeers.count; i++) {
NSString *displayName = [[self.appDelegate.mpcHandler.session.connectedPeers objectAtIndex:i] displayName];
allPlayers = [allPlayers stringByAppendingString:@"\n"];
allPlayers = [allPlayers stringByAppendingString:displayName];
}
[self.tvPlayerList setText:allPlayers];
}
}
As its name implies, the connectedPeers property of the session object is an array that contains the connected peers. We use a for loop to iterate over this array and populate the text view with the names of the connected peers.
Run two instances of the application and establish a connection to see the result of our work. When you now tap the Done button, the text view will display a list of the peers that are connected to the user's device.
Conclusion
This wraps it up for the first part of this tutorial about the Multipeer Connectivity framework. We've covered several new APIs and laid the foundation of our game. In the next tutorial, we'll continue building the game by implementing the game's logic as well as integrating the Multipeer Connectivity framework to send data between the players of a game.
Marketing is just as important as the development of your product. Marketing your application helps build an audience, which in turn gives you the opportunity to generate revenue.
When speaking about marketing a mobile application, a product goes through three distinct phases, pre-release, release, and post-release. Let's take a closer look at each phase.
1. Pre-Release
Build A Compelling Landing Page
An online presence is crucial. Building a landing page before your app is released is key to showcase the app's design and feature set. Your goal is to get visitors excited so they'll take action, like subscribing to a newsletter or tweeting about your app to get an invitation for the app's private beta.
A great landing page takes effort. Investing in a designer to help polish the app and its landing page is a wise decision if you're not confident about your own design skills. You want your app, and its icon and landing page to be well designed since it will impress potential customers and increase the desirability of your product.
Don't forget to include a download link for the press kit of your app on the landing page. In a press kit, you should include a handful of screenshots of your app and a document which briefly explains the app's features. Of course, don't forget to include your contact details so people can get in touch with you if they have any questions.
Build an Audience Through Blogging
A blog is the perfect complement to a landing page. However, the opinions about blogging are mixed. It can be a great strategy to improve your app's visibility, but maintaining a blog isn't always the best use of your time.
What are some of the deciding factors for maintaining a blog?
Are you a good storyteller, for example? Do you have experience blogging? What will you write about? If you're creating a health app, for example, a blog about health and lifestyle could be useful to get potential customers interested in your product.
The ultimate goal of your blog is increasing the size of your audience.
Whether blogging is the right strategy for you is an answer that only you can answer. If you believe it's worth your time, then, by all means, go for it. If you don't feel confident writing for an audience, then you're better off focusing your efforts on other aspects of marketing.
Work On Your Public Relations
Reach out to several technology reporters with your product and its story. Twitter is a great medium to help you find people and start a discussion. Even before the release of your product, it's perfectly possible to get attention from the press.
Shadow is a great example. The company behind Shadow used Kickstarter to test the assumptions they had about their product. It turned out to be an overwhelming success and it got the attention of the press as a result.
If you're having difficulties dealing with public relations, then consider hiring a company specialized in handling this aspect of marketing. This can be worthwhile investment if you're not afraid of spending money before your product is released.
It's a good idea to build a list of key contacts that you believe can be useful for marketing your product. You could offer a beta invitation to a befriended tech blogger or chat about an idea you have with someone you met on Twitter.
Managing Social Media
As much as social media is part of our daily lives, its effectiveness is questionable when it comes to marketing a product by leveraging social media. Companies flood social networks and try to get our attention all the time. Even though an online presence is mandatory these days, don't expect social media to be a powerful tool for getting traction for your product.
Consider social media as a tool to communicate instead of a channel to improve your product's conversion.
You can use social media in clever ways, such as in-app sharing to improve the visibility of your application once it's released. You should only implement this when it makes sense and provides value to the user.
Don't forget to claim your social media profiles on time. When you're choosing a name for your application, check if you're able to register a name for the social networks you plan to use. Social networks like Twitter are useful to network with other designers, developers, and pioneers in the mobile industry.
Don't Forget App Store Optimization
The most crucial elements or App Store Optimization (ASO) are your product's name and keywords. The goal is to rank high for those keywords and convert potential users to users.
The App Store is the largest discovery channel available.
Try to include relevant keywords in the title of your application. For example, compare Weather App with Weather App - Weather Radar and Storm Alerts. The second choice is much better optimized for the App Store.
Research Users & Competitors
Every product caters to a specific group of people that you can define by answer the question "Who am I building this application for?" For example, a wine application is best marketed to people who enjoy wine. Focus your efforts on the group of users that are a good fit for your product.
Don't be blinded by your own product. Pay attention to what competing apps are doing. What are their marketing tactics? How do their actions compare to yours? How do users discover their app?
Learning about your competitors has two major advantages. First, you can draw inspiration from their efforts and do what they do—but better. Second, they're a rich source of information if you have little or no experience in the market you're entering. Watch and learn.
Even better advice is to build on what they do by diversifying. What are possibly better marketing strategies? How can you reach users differently and more efficiently?
Explore Growth Hacking
Growth hacking has become a popular phenomenon in the startup space. The concept of growth hacking is to focus on low-cost marketing mechanics to gain exposure. You accomplish this by viral marketing, the use of social media, and modern marketing strategies, such as SEO, A/B testing, and content marketing.
If you're interested in growth hacking, there are many resources available. With regards to marketing, growth hacking is certainly worth exploring.
2. Release
The majority of your marketing efforts take place before the release of your product. When done right, your pre-release marketing efforts should have resulted in traction so you can welcome interested users when your product is released. This will improve downloads, which in turn are good for App Store Optimization.
Public Relations & Social Media, Once Again
Now would be a good time to take advantage of the network you've built during the pre-release phase. Revisit your list of key contacts and reach out to them.
It's best to start a conversation a few days before the actual release. Give them a chance to play with your product and see if they can help you with the marketing of your product. If they like your work, they could tweet how much they love it, for example.
The next step would be writing a press release. Don't forget to include your press kit and send it to technology journalists. Remember that it's always better to reach out to a specific person instead of using a catch-all address.
The key component of a good press release is the inclusion of a story with a title that draws attention. Journalists receive product pitches all the time. Stand out from the crowd. Discover the aspects that make your product unique and stand out.
Optimize
Launch day means optimization. If you're marketing a paid application, then a launch promotion with a lower price is a smart choice. The goal of the first few days is to get as many downloads as possible and rise to the top of the charts.
Update your landing page to include a big download button and write a blog post about your release. Spread the message on social media that your product has finally launched.
3. Post-Release
Focus on User Retention
Your marketing efforts after the release of your product need to focus on retaining your users. You accomplish this by fixing bugs quickly, releasing updates, and keeping your users engaged.
Word of mouth is another powerful marketing tool if you have a great product.
If you've built a blog or you're active on social media, then don't stop now. You should stay in touch with the users of your application, get feedback from them, and learn how you can improve your product.
Analyze your product's usage and draw conclusions from this data. Every release cycle is an opportunity to learn, it teaches you what went wrong and what can be better. Store data for future reference. As you build more apps, you get better at marketing them and your network will only grow if done right.
Conclusion
The hardest part of marketing is realizing that there's no secret sauce to success. As with most things, successfully marketing an application is a combination of skill and luck.
The most important lesson is that the majority of your marketing efforts take place before your product is released, not during or after the release.
Do you have experience with running a marketing campaign? What are some of the tips and tricks you've learned along the way?
In this tutorial, we will give you nine practical techniques for writing elegant and readable code. We won't be talking about specific architectures, languages, or platforms. The focus lies on writing better code. Let's get started.
"Measuring programming progress by lines of code is like measuring aircraft building progress by weight." - Bill Gates
Introduction
If you're a developer, then there probably have been times when you've written code and, after a few days, weeks, or months, you looked back at it and said to yourself "What does this piece of code do?" The answer to that question might have been "I really don't know!" In that case, the only thing you can do is going through the code from start to finish, trying to understand what you were thinking when you wrote it.
This mostly happens when we're lazy and we just want to implement that new feature the client asked for. We just want to get the job done with as little effort as possible. And when it works, we don't care about the code itself, because the client won't ever see the ugly truth, let alone understand it. Right? Wrong. These days, collaborating on software has become the default and people will see, read, and inspect the code that you write. Even if your code isn't scrutinized by your colleagues, you should make it a habit to write clear and readable code. Always.
Most of the time, you don't work alone on a project. We frequently see ugly code with variables having names like i, a, p, pro, and rqs. And if it really gets bad, this pattern is visible across the entire project. If this sounds familiar, then I'm pretty sure you've asked yourself the question, "How can this person write code like this?" Of course, this makes you all the more grateful when you come across clear, readable, and even beautiful code. Clear and clean code can be read in seconds and it can save you and your colleagues a lot of time. That should be your motivation for writing quality code.
1. Easy to Understand
We all agree that code should be easy to understand. Right? The first example focuses on spacing. Let's look at two examples.
Even though the result of theses examples is identical, they look quite different. Why should you use more lines of code if you can write less? Let's explore two other examples, something I bet you see frequently.
Again, the result of these examples is identical. Which one's better? And why? Do fewer lines of code mean better code? We'll revisit this question later in this tutorial.
2. Is Smaller Always Better?
In computer science, you often hear the phrase "less is more". Generally speaking, if you can solve a problem in fewer lines of code, the better. It will probably take you less time to understand a 200-line class than a 500-line class. However, is this always true? Take a look at the following examples.
Don't you agree that the second example is easier to read and understand? You need to be able to optimize for readability. Of course, you could add a few comments to the first example to make it easier to understand, but isn't it better to omit the comments and write code that's easier to read and understand?
// Determine where to spawn the monster along the Y axis
CGSize winSize = [CCDirector sharedDirector].winSize;
int minY = monster.contentSize.width / 2;
int maxY = winSize.width - monster.contentSize.width/2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
3. Naming
Choosing descriptive names for things like variables and functions is a key aspect of writing readable code. It helps both your colleagues and yourself to quickly understand the code. Naming a variable tmp doesn't tell you anything other than that the variable is temporary for some reason, which is nothing more than an educated guess. It doesn't tell if the variable stores a name, a date, etc.
Another fine example is naming a method stop. It's not a bad name per se, but that really depends on the method's implementation. If it performs a dangerous operation that cannot be undone, you may want to rename it to kill or pause if the operation can be resumed. Do you get the idea?
If you're working with a variable for the weight of potatoes, why would you name it tmp? When you revisit that piece of code a few days later, you won't recall what tmp is used for.
We're not saying that tmp is a bad name for a variable, because there are times when tmp is perfectly reasonable as a variable name. Take a look at the following example in which tmp isn't a bad choice at all.
In the above example, tmp describes what it does, it temporarily stores a value. It isn't passed to a function or method, and it isn't incremented or modified. It has a well-defined lifetime and no experienced developer will be thrown off by the variable's name. Sometimes, however, it's just plain laziness. Take a look at the next example.
If tmp stores the user's information, why isn't it named userInfo? Proper naming of variables, functions, methods, classes, etc. is important when writing readable code. Not only does it make your code more readable, it will save you time in the future.
Objective-C is quite verbose, but it's very easy to read. Apple uses a well-defined naming convention that you can adopt in most programming languages. You can read more about this naming convention in Programming with Objective-C.
4. Add Meaning to Names
As we saw in the previous tip, it's important to choose names wisely. However, it's equally important to add meaning to the names you use for variables, functions, methods, etc. Not only does this help avoid confusion, it makes the code you write easier to understand. Choosing a name that makes sense is almost like adding metadata to a variable or method. Choose descriptive names and avoid generic ones. The word add, for example, isn't always ideal as you can see in the next example.
bool addUser(User u){
...
}
It's not clear what addUser is supposed to do. Does it add a user to a list of users, to a database, or to a list of people invited to a party? Compare this to registerUser or signupUser. This makes more sense. Right? Take a look at the following listing to get a better idea of what we're driving at.
Word
Synonyms
do
make, perform, execute, compose, add
start
launch, create, begin, open
explode
detonate, blow up, set off, burst
5. Name Size
A lot of programmers don't like long names, because they're hard to remember and cumbersome to type. Of course, a name shouldn't be ridiculously long like newClassForNavigationControllerNamedFirstViewController. This is hard to remember and it simply makes your code ugly and unreadable.
As we saw earlier, the opposite, short names, aren't any good either. What is the right size for a variable or method name? How do you decide between naming a variable len, length, or user_name_length? The answer depends on the context and the entity to which the name is tied to.
Long names are no longer a problem when using a modern IDE (Integrated Development Environment). Code completion helps you avoid typos and it also makes suggestions to make remembering names less of a pain.
You can use short(er) names if the variable is local. What's more, it's recommended to use shorter names for local variables to keep your code readable. Take a look at the following example.
Booleans can be tricky to name, because they can have a different meaning depending on the way you read or interpret the name. In the next code snippet, read_password can mean that the password has been read by the program, but it can also mean that the program should read the password.
BOOL readPassword = YES;
To avoid this problem, you could rename the above boolean to didReadPassword to indicate that the password has been read or shouldReadPassword to show that the program needs to read the password. This is something you see a lot in Objective-C, for example.
7. To Comment or Not To Comment
Adding comments to code is important, but it's equally important to use them sparingly. They should be used to help someone understand your code. Reading comments, however, takes time too and if a comment doesn't add much value, then that time is wasted. The next code snippet shows how not to use comments.
// This happens when memory warning is received
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// This validate the fields
-(BOOL)validateFields {
}
Are these code snippets helpful to you? The answer is probably "No." The comments in the above examples do not add additional information, especially since the method names are already very descriptive, which is common in Objective-C. Don't add comments that explain the obvious. Take a look at the next example. Isn't this a much better use of comments?
// Determine speed of the monster
int minDuration = 2.0;
int maxDuration = 8.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
Comments like this make it very easy to navigate a code base quickly and efficiently. It saves you from having to read the code and helps you understand the logic or algorithm.
8. Style and Consistency
Every language or platform has a (or more) style guide and even most companies have one. Do you put the curly braces of an Objective-C method on a separate line or not?
- (void)calculateOffset {
}
- (void)calculateOffset
{
}
The answer is that it doesn't matter. There's no right answer. Of course, there are style guides you can adopt. What's important is that your code is consistent in terms of style. Even though this may not affect the quality of your code, it certainly affects the readability and it will most likely annoy the hell out of your colleagues or whoever reads your code. For most developers, ugly code is the worst kind of code.
9. Focused Methods and Functions
A common mistake among developers is trying to cram as much functionality into functions and methods. This works, but it's inelegant and makes debugging a pain in the neck. Your life—and that of your colleagues—will become much easier if you break larger problems into tiny bits and tackle those bits into separate functions or methods. Take a look at the next example in which we write an image to disk. This seems like a trivial task, but there's a lot more to it if you want to do it right.
- (BOOL)saveToImage:(UIImage *)image withFileName:(NSString *)fileName {
BOOL result = NO;
NSString *documents = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if (paths.count) {
documents = [paths objectAtIndex:0];
NSString *basePath = [documents stringByAppendingPathComponent:@"Archive"];
if (![[NSFileManager defaultManager] fileExistsAtPath:basePath]) {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:&error];
if (!error) {
NSString *filePath = [basePath stringByAppendingPathComponent:fileName];
result = [UIImageJPEGRepresentation(image, 8.0) writeToFile:filePath atomically:YES];
} else {
NSLog(@"Unable to create directory due to error %@ with user info %@.", error, error.userInfo);
}
}
}
return result;
}
If a unit of code tries to do too much, you often end up with deeply nested conditional statements, a lot of error checking, and overly complex conditional statements. This method does three things, fetch the path of the application's documents directory, fetch and create the path for the archives directory, and write the image to disk. Each task can be put in its own method as shown below.
- (NSString *)applicationArchivesDirectory {
NSString *documentsDirectory = [self applicationDocumentsDirectory];
NSString *archivesDirectory = [documentsDirectory stringByAppendingPathComponent:@"Archives"];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:archivesDirectory]) {
NSError *error = nil;
[fm createDirectoryAtPath:archivesDirectory withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
NSLog(@"Unable to create directory due to error %@ with user info %@.", error, error.userInfo);
return nil;
}
}
return archivesDirectory;
}
This is much easier to debug and maintain. You can even reuse the applicationDocumentsDirectory method in other places of the project, which is another benefit of breaking larger problems into manageable pieces. Testing code becomes much easier too.
Conclusion
In this article, we've taken a closer look at writing readable code by wisely choosing names for variables, functions, and methods, being consistent when writing code, and breaking complex problems into manageable chunks. If you have any questions or feedback, feel free to leave a comment below.
2014-03-19T11:30:59.454Z2014-03-19T11:30:59.454ZJorge Costa and Orlando Pereirahttp://code.tutsplus.com/articles/writing-elegant-and-readable-code--mobile-21514
In this tutorial, I will show you how to create a simple, multi-player game using the Multipeer Connectivity framework that was introduced in iOS 7. In the first installment of this series, we laid the foundation of the game. In this article, we'll implement the game logic.
1. Implementing the Game Logic
At this point, the application is able to discover other nearby players, establish a connection, and display the connected peers in the text view of the options view controller. However, the implementation of the OptionsViewController class isn't finished yet.
Step 1
The options view controller contains a text field at the top of its view. We'll use this text field to let the user change the display name of the device. At the moment, the device name is used as the peer's display name. Let's explore how we can customize that display name.
Let's start by adopting the UITextFieldDelegate protocol in the OptionsViewController class. Open OptionsViewController.h and modify the class's interface as shown below.
We first call resignFirstResponder on the text field to dismiss the keyboard. Next, if the peerID and session objects are not nil, we reset both by setting them to nil. We also call disconnect on the session object to disconnect the device from any peers it may be connect to. We then initialize the peerID object using the name entered by the user in the text field and set up the session object. Try this out by running the application and changing the display name. If all goes well, the custom name will be used instead of the name of the device.
There's one caveat, when using a custom display name, the device disconnects from any active session it's currently part of. This also means that any game that is in progress is ended. Even though we haven't added any measures to prevent this, make sure that you don't allow users to change the display name when the device is part of an active session.
Step 2
The next step is to implement the toggleVisibility: action we declared earlier. Its implementation couldn't be easier. Whenever the switch is toggled, we invoke the advertiseSelf: method of the mpcHandler object and pass it the state of the switch. Run the application to give it a try.
We also need to implement the disconnect: action. Disconnecting a device from a session is just as easy. Take a look at the implementation of disconnect: below.
That finishes it up for the OptionsViewController class. The application in its current state is able to establish a connection, update the device's display name, and disconnect from an active session. It's time to focus on the game itself by exploring how we can exchange data between peers.
2. Creating a New Game
Step 1
Let's now focus on the implementation of the ViewController class. Start by declaring a property for the application delegate and setting it in the view controller's viewDidLoad method. This involves three steps.
Open ViewController.m and add an import statement for the AppDelegate class.
The secretNumber property will store the secret number chosen by the player hosting the game.
The hasCreatedGame flag indicates whether the current player is the one that started the current game.
The isGameRunning flag indicates whether a game is currently in progress.
Step 3
In order for a new game to be started, a secret number must be set by the player who starts the game. The primary focus of this tutorial is the Multipeer Connectivity framework, so we won't bother with a complex mechanism to set a secret number. And what's easier than an alert view asking the player for a secret number?
A new game starts when a player taps the Start button in the navigation bar, which means we need to implement the startGame: action. As you can see in its implementation below, we first check if a game is already in progress before presenting the alert view. We don't want to start a new game while another game is ongoing.
- (IBAction)startGame:(id)sender {
if (!self.isGameRunning) {
UIAlertView *newGameAlert = [[UIAlertView alloc] initWithTitle:@"MPCDemo"
message:@"Please enter a number between 1 and 100:"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Start Game", nil];
newGameAlert.alertViewStyle = UIAlertViewStylePlainTextInput;
[[newGameAlert textFieldAtIndex:0] setKeyboardType:UIKeyboardTypeNumberPad];
[newGameAlert show];
}
}
By setting the alertViewStyle to UIAlertViewStylePlainTextInput, a text field is added to the alert view.
Step 4
Once the player has entered a secret number, there's three things we need to take care of.
We need to handle the player's tap of the Start Game button.
We also need to verify whether the chosen number falls between 1 and 100.
Other players of the game need to be notified that the game has started and a secret number has been set.
Let's start with the first task by implementing the alertView:clickedButtonAtIndex: delegate method of the UIAlertViewDelegate protocol. Open the view controller's header file and adopt the UIAlertViewDelegate protocol as shown below.
Next, implement the alertView:clickedButtonAtIndex: method of the UIAlertViewDelegate protocol. To make sure that the alert view contains a text field, we first inspect its alertViewStyle property to see whether it's equal to UIAlertViewStylePlainTextInput. We also make sure that the Start Game button was tapped by verifying that the buttonIndex property of the tapped button is equal to 1.
The next step is checking whether the chosen number is between 1 and 100, which is very easy to do. If the number doesn't fall within the required range, we display an alert view to the player. If the secret number passes our test, however, we create a message for the other players and send it to the connected peers using the Multipeer Connectivity framework. How does this work?
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView.alertViewStyle == UIAlertViewStylePlainTextInput && buttonIndex == 1) {
UITextField *textField = [alertView textFieldAtIndex:0];
self.secretNumber = [textField.text intValue];
// Make sure that the given number is between 1 and 100.
if (self.secretNumber >= 1 && self.secretNumber <= 100) {
// Create a message to tell other players that a new game has been created,
// convert it to a NSData object and send it.
NSString *messageToSend = @"New Game";
NSData *messageAsData = [messageToSend dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
[self.appDelegate.mpcHandler.session sendData:messageAsData
toPeers:self.appDelegate.mpcHandler.session.connectedPeers
withMode:MCSessionSendDataReliable
error:&error];
// If any error occurs, just log it.
// Otherwise set the following couple of flags to YES, indicating that the current player is the creator
// of the game and a game is in progress.
if (error != nil) {
NSLog(@"%@", [error localizedDescription]);
} else{
self.hasCreatedGame = YES;
self.isGameRunning = YES;
[self.tvHistory setText:@""];
}
} else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"MPCDemo"
message:@"Please enter a valid number."
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"Okay", nil];
[alert show];
}
}
}
We create an NSString instance, set it's value to New Game, and encode the string to an NSData object. Remember that a NSString instance cannot be sent using the Multipeer Connectivity framework. It first needs to be converted to an NSData object.
The most interesting line of code is the one shown below. In this single line, we send the NSData object to all the connected peers. We pass the address of an NSError pointer to catch any errors that might be thrown during the process.
As you may have guessed, the sendData:toPeers:withMode:error: method is declared in the Multipeer Connectivity framework. Because we want every peer to receive the message, we pass self.appDelegate.mpcHandler.session.connectedPeers as the second argument. The method's third argument, MCSessionSendDataReliable, specifies the transmission mode. If you recall from the introduction, the Multipeer Connectivity framework can send data one of two ways, reliably or unreliably. In this example, it is key to send the data reliably to make sure that every player receives the message without hiccoughs.
If no error was thrown, we set hasCreatedGame and isGameRunning to YES. We also clear the text view to prepare the user interface for the new game.
3. Updating the User Interface
If you were to test the application in its current state, you'd notice that nothing has changed from the player's perspective. The other players aren't notified when a new game is started. We still need to take care of a few things to accomplish this.
First, however, we need to update the application's user interface to reflect the state of the current game. Currently, the buttons and text fields are enabled even when a new game has started. For example, when one player starts a new game, a player who has joined the game shouldn't be able to start a new game. We will implement a simple helper method to take care of this problem.
In toggleSubviewsState:, we enable or disable the buttons and text field depending on the state of the game. Let's invoke toggleSubviewsState: in the view controller's viewDidLoad method.
Every time a peer receives an NSData object, the session:didReceiveData:fromPeer: delegate method of the Multipeer Connectivity framework is invoked. This also result in the posting of a notification as you may remember from earlier in this tutorial. To receive and handle these notifications, we need to add the view controller as an observer as shown below. Whenever a notification with a name of MPCDemo_DidReceiveDataNotification is posted, the handleReceivedDataWithNotification: method is invoked.
The implementation of handleReceivedDataWithNotification: may seem daunting so let's break it down in digestible chunks.
- (void)handleReceivedDataWithNotification:(NSNotification *)notification {
// Get the user info dictionary that was received along with the notification.
NSDictionary *userInfoDict = [notification userInfo];
// Convert the received data into a NSString object.
NSData *receivedData = [userInfoDict objectForKey:@"data"];
NSString *message = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
// Keep the sender's peerID and get its display name.
MCPeerID *senderPeerID = [userInfoDict objectForKey:@"peerID"];
NSString *senderDisplayName = senderPeerID.displayName;
}
We get the notification's userInfo dictionary, extract the NSData object, recreate the NSString object, and store it in a variable named message. The action we take next depends on the value of the message object. We also extract the name of the peer that sent the message, so we can use it to update the game's user interface.
If the value of message is equal to the New Game, a new game has been started. We then notify the user of this event, update the value of isGameRunning, and update the user interface to let the player make guesses. Take a look at the updated implementation of handleReceivedDataWithNotification:.
- (void)handleReceivedDataWithNotification:(NSNotification *)notification {
// Get the user info dictionary that was received along with the notification.
NSDictionary *userInfoDict = [notification userInfo];
// Convert the received data into a NSString object.
NSData *receivedData = [userInfoDict objectForKey:@"data"];
NSString *message = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
// Keep the sender's peerID and get its display name.
MCPeerID *senderPeerID = [userInfoDict objectForKey:@"peerID"];
NSString *senderDisplayName = senderPeerID.displayName;
if ([message isEqualToString:@"New Game"]) {
// In case the message is about a new game, then show an alert view telling that the sender of the message
// has just started a new game.
NSString *alertMessage = [NSString stringWithFormat:@"%@ has started a new game.", senderDisplayName];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"MPCDemo"
message:alertMessage
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"Done", nil];
[alert show];
// Also, indicate that a game is in progress.
self.isGameRunning = YES;
// Enable all subviews.
[self toggleSubviewsState:YES];
// Clear all previous history from the text view.
[self.tvHistory setText:@""];
}
}
Two details are interesting to point out. First, the name of the player that started the game is mentioned in the alert view's message to make sure that the other players know who's hosting the game. Second, we invoke toggleSubviewsState: to update the user interface. If you test the application once more, you'll notice that an alert view is displayed to every connected player when a new game is started.
5. Play
Step 1
The next step is to add the ability for other players to make a guess at the secret number the host of the game has chosen. The flow we use to notify the connected peers is very similar to what we've seen so far. Let's start by implementing the sendGuess: action.
Before we send a guess to the other players in the game, we check if the player's guess is a valid number and falls within the required range. If it is, we convert the contents of the txtGuess text field to an NSData object and send it to the connected peers. On the device of the player who made the guess, we append the guess to the text view's contents.
- (IBAction)sendGuess:(id)sender {
// Check if a number has been entered or not, and if it's valid.
if (self.txtGuess.text.length == 0 || [self.txtGuess.text intValue] < 1 || [self.txtGuess.text intValue] > 100) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"MPCDemo"
message:@"Please enter a valid number."
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"Okay", nil];
[alert show];
} else{
// Convert the guess string to a NSData object and send it to all peers (players).
NSData *guessAsData = [self.txtGuess.text dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
[self.appDelegate.mpcHandler.session sendData:guessAsData
toPeers:self.appDelegate.mpcHandler.session.connectedPeers
withMode:MCSessionSendDataReliable
error:&error];
// If any error occurs just log its description.
if (error != nil) {
NSLog(@"%@", [error localizedDescription]);
}
// Add to the history text view the number value given by the current player.
NSString *history = [NSString stringWithFormat:@"I guessed the number: %@\n\n", self.txtGuess.text];
[self.tvHistory setText:[history stringByAppendingString:self.tvHistory.text]];
}
self.txtGuess.text = @"";
[self.txtGuess resignFirstResponder];
}
Step 2
When a valid guess is sent to the other players in the game, handleReceivedDataWithNotification: is invoked. We've already partially implemented this method earlier in this tutorial to notify other players when a new game starts. The current implementation includes only one conditional statement to check for a new game.
It's time to add an else clause to handle other incoming messages. At the moment, we only want to handle a guess made by another player, which means that we need to check if the message contains a valid number. The following code snippet detects if the NSString object includes a number.
If the message does indeed contain a valid guess, we display it in the text view. Additionally, we check if the player is the host of the game and, if she is, we display an alert view with three possible actions to enable the application to send feedback to the other players. Take a look at the updated implementation of handleReceivedDataWithNotification: for clarification.
- (void)handleReceivedDataWithNotification:(NSNotification *)notification {
// ... //
if ([message isEqualToString:@"New Game"]) {
// ... //
} else {
// Check if the message contains only digits. If that's the case, then
// that means that it contains a guess from the player who sent it.
NSCharacterSet *numbersSet = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *messageSet = [NSCharacterSet characterSetWithCharactersInString:message];
if ([numbersSet isSupersetOfSet:messageSet]) {
// The message contains the guess from another player.
// Convert it to a number.
int guess = [message intValue];
// Add this guess to the history text view.
NSString *history = [NSString stringWithFormat:@"Player %@ guessed the number: %d\n\n", senderDisplayName, guess];
[self.tvHistory setText:[history stringByAppendingString:self.tvHistory.text]];
// If self is the game creator, then show all available options regarding this guess.
if (self.hasCreatedGame) {
NSString *optionsMessage = [NSString stringWithFormat:@"%@\n\nThe secret number is %d.\n\nWhat's your answer?", history, self.secretNumber];
UIAlertView *optionsAlert = [[UIAlertView alloc] initWithTitle:@"MPCDemo"
message:optionsMessage
delegate:self
cancelButtonTitle:nil
otherButtonTitles:@"Correct Guess!", @"Give a greater number", @"Give a lower number", nil];
[optionsAlert show];
}
}
}
}
The next screenshot shows the alert view the host will see when a player makes a valid guess.
Step 3
To handle the response of the host, we need to update the alertView:clickedButtonAtIndex: method of the UIAlertViewDelegate protocol as shown below. All we do is sending the button's title as a message to the other players in the game.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView.alertViewStyle == UIAlertViewStylePlainTextInput && buttonIndex == 1) {
// ... //
} else {
// Get the tapped button's title as the answer, convert it to a NSData object and send it to other players.
NSString *selectedAnswer = [alertView buttonTitleAtIndex:buttonIndex];
NSData *answerAsData = [selectedAnswer dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
[self.appDelegate.mpcHandler.session sendData:answerAsData
toPeers:self.appDelegate.mpcHandler.session.connectedPeers
withMode:MCSessionSendDataReliable
error:&error];
if (error != nil) {
NSLog(@"%@", [error localizedDescription]);
}
// In case of correct guess then turn the flags to off.
if (buttonIndex == 0) {
self.hasCreatedGame = NO;
self.isGameRunning = NO;
}
}
}
When an opponent makes a correct guess, we end the game by setting hasCreatedGame and isGameRunning to NO. In the next step, we'll handle the message sent to the other players in the game.
Step 4
I'm sure that you are starting to understand how the different pieces of the game fit together. It's a bit like a game of tennis in which the players hit a ball to one another until one drops the ball. To complete the game, we need to revisit handleReceivedDataWithNotification: one more time to handle the response sent by the host of the game after a player has made a valid guess.
We start by adding an else clause to the second if statement as shown below. All we do is appending the message, the title of the button the host has tapped, to the text view's contents. If the message is equal to Correct Guess, we end the game and disable the game's controls by invoking toggleSubviewsState: and passing NO to it.
- (void)handleReceivedDataWithNotification:(NSNotification *)notification {
// ... //
} else {
// Check if the message contains only digits. If that's the case, then
// that means that it contains a guess from the player who sent it.
NSCharacterSet *numbersSet = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *messageSet = [NSCharacterSet characterSetWithCharactersInString:message];
if ([numbersSet isSupersetOfSet:messageSet]) {
// ... //
} else {
// If the message doesn't contain digits, then it contains the answer from the player who started the game.
// For starters, just show answer to the history text view.
NSString *history = [NSString stringWithFormat:@"%@ says:\n%@\n\n", senderDisplayName, message];
[self.tvHistory setText:[history stringByAppendingString:self.tvHistory.text]];
// Check if the game creator answered that the last guess was the correct one. In that case, the game
// should stop.
if ([message isEqualToString:@"Correct Guess!"]) {
self.isGameRunning = NO;
[self toggleSubviewsState:NO];
}
}
}
}
Step 5
The application is almost finished. All that's left for us to do is, implement the cancelGuessing: action. In this method, we hide the keyboard by calling resignFirstResponder on the txtGuess text field.
Our simple game is now ready to play. Keep in mind that the game's implementation is simple as the main focus of this tutorial has been exploring the Multipeer Connectivity framework that was introduced in iOS 7. The next screenshot should give you an idea of the various states the game can be in.
Conclusion
I've hopefully convinced you that the Multipeer Connectivity framework is a great new addition to the iOS SDK. This tutorial has showed you that sending data from peer to peer is very easy with the Multipeer Connectivity framework. The framework has a lot more to offer so I encourage you to explore Apple's documentation for a deeper understanding of its possibilities.
In this series, we are creating a music player on Android using the MediaPlayer and MediaController classes. In the first part, we created the app and prepared the user interface for playback. We presented the list of songs on the user device and specified a method to execute when the user makes a selection. In this part of the series, we will implement a Service class to execute music playback continuously, even when the user is not directly interacting with the application.
Introduction
We will need the app to bind to the music playing Service in order to interact with playback, so you will learn some of the basic aspects of the Service life cycle in this tutorial if you haven't explored them before. Here is a preview of the final result that we're working towards:
In the final installment of this series, we'll add user control over the playback and we'll also make sure the app will continue to function in various application states. Later, we will follow the series up with additional enhancements that you may wish to add, such as audio focus control, video and streaming media playback, and alternate ways of presenting the media files.
1. Create a Service
Step 1
Add a new class to your app, naming it MusicService or another name of your choice. Make sure it matches the name you listed in the Manifest. When creating the class in Eclipse, choose android.app.Service as its superclass. Eclipse should enter an outline:
public class MusicService extends Service {
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}
Extend the opening line of the class declaration to implement some interfaces we will use for music playback:
public class MusicService extends Service implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {
Eclipse will display an error over the class name. Hover over the error and choose Add unimplemented methods. We will add code to the methods in a few moments. The interfaces we are implementing will aid the process of interacting with the MediaPlayer class.
Your class will also need the following additional imports:
Add the following instance variables to the new Service class:
//media player
private MediaPlayer player;
//song list
private ArrayList<Song> songs;
//current position
private int songPosn;
We will pass the list of songs into the Service class, playing from it using the MediaPlayer class and keeping track of the position of the current song using the songPosn instance variable. Now, implement the onCreate method for the Service:
public void onCreate(){
//create the service
}
Inside onCreate, call the superclass method, instantiating the position and MediaPlayer instance variables:
//create the service
super.onCreate();
//initialize position
songPosn=0;
//create player
player = new MediaPlayer();
Next, let's add a method to initialize the MediaPlayer class, after the onCreate method:
public void initMusicPlayer(){
//set player properties
}
Inside this method, we configure the music player by setting some of its properties as shown below:
The wake lock will let playback continue when the device becomes idle and we set the stream type to music. Set the class as listener for (1) when the MediaPlayer instance is prepared, (2) when a song has completed playback, and when (3) an error is thrown:
Notice that these correspond to the interfaces we implemented. We will be adding code to the onPrepared, onCompletion, and onError methods to respond to these events. Back in onCreate, invoke initMusicPlayer:
initMusicPlayer();
Step 3
It's time to add a method to the Service class to pass the list of songs from the Activity:
public void setList(ArrayList<Song> theSongs){
songs=theSongs;
}
We will call this method later in the tutorial. This will form part of the interaction between the Activity and Service classes, for which we also need a Binder instance. Add the following snippet to the Service class after the setList method:
public class MusicBinder extends Binder {
MusicService getService() {
return MusicService.this;
}
}
We will also access this from the Activity class.
2. Start the Service
Step 1
Back in your app's main Activity class, you will need to add the following additional imports:
We are going to play the music in the Service class, but control it from the Activity class, where the application's user interface operates. To accomplish this, we will have to bind to the Service class. The above instance variables represent the Service class and Intent, as well as a flag to keep track of whether the Activity class is bound to the Service class or not. Add the following to your Activity class, after the onCreate method:
//connect to the service
private ServiceConnection musicConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicBinder binder = (MusicBinder)service;
//get service
musicSrv = binder.getService();
//pass list
musicSrv.setList(songList);
musicBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
musicBound = false;
}
};
The callback methods will inform the class when the Activity instance has successfully connected to the Service instance. When that happens, we get a reference to the Service instance so that the Activity can interact with it. It starts by calling the method to pass the song list. We set the boolean flag to keep track of the binding status. You will need to import the Binder class we added to the Service class at the top of your Activity class:
When the Activity instance starts, we create the Intent object if it doesn't exist yet, bind to it, and start it. Alter the code if you chose a different name for the Service class. Notice that we use the connection object we created so that when the connection to the bound Service instance is made, we pass the song list. We will also be able to interact with the Service instance in order to control playback later.
Return to your Service class to complete this binding process. Add an instance variable representing the inner Binder class we added:
private final IBinder musicBind = new MusicBinder();
Now amend the onBind method to return this object:
@Override
public IBinder onBind(Intent intent) {
return musicBind;
}
Add the onUnbind method to release resources when the Service instance is unbound:
@Override
public boolean onUnbind(Intent intent){
player.stop();
player.release();
return false;
}
This will execute when the user exits the app, at which point we will stop the service.
3. Begin Playback
Step 1
Let's now set the app up to play a track. In your Service class, add the following method:
public void playSong(){
//play a song
}
Inside the method, start by resetting the MediaPlayer since we will also use this code when the user is playing subsequent songs:
player.reset();
Next, get the song from the list, extract the ID for it using its Song object, and model this as a URI:
//get song
Song playSong = songs.get(songPosn);
//get id
long currSong = playSong.getID();
//set uri
Uri trackUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
currSong);
We can now try setting this URI as the data source for the MediaPlayer instance, but an exception may be thrown if an error pops up so we use a try/catch block:
After the catch block, complete the playSong method by calling the asynchronous method of the MediaPlayer to prepare it:
player.prepareAsync();
Step 2
When the MediaPlayer is prepared, the onPrepared method will be executed. Eclipse should have inserted it in your Service class. Inside this method, start the playback:
@Override
public void onPrepared(MediaPlayer mp) {
//start playback
mp.start();
}
In order for the user to select songs, we also need a method in the Service class to set the current song. Add it now:
public void setSong(int songIndex){
songPosn=songIndex;
}
We will call this when the user picks a song from the list.
Step 3
Remember that we added an onClick attribute to the layout for each item in the song list. Add that method to the main Activity class:
public void songPicked(View view){
musicSrv.setSong(Integer.parseInt(view.getTag().toString()));
musicSrv.playSong();
}
We set the song position as the tag for each item in the list view when we defined the Adapter class. We retrieve it here and pass it to the Service instance before calling the method to start the playback.
Before you run your app, implement the end button we added to the main menu. In your main Activity class, add the method to respond to menu item selection:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//menu item selected
}
Inside the method, add a switch statement for the actions:
switch (item.getItemId()) {
case R.id.action_shuffle:
//shuffle
break;
case R.id.action_end:
stopService(playIntent);
musicSrv=null;
System.exit(0);
break;
}
return super.onOptionsItemSelected(item);
We will implement the shuffle function in the next tutorial. For the end button, we stop the Service instance and exit the app. Pressing the back button will not exit the app, since we will assume that the user wants playback to continue unless they select the end button. Use the same process if the the app is destroyed, overriding the activity's onDestroy method:
When you run the app at this point, you will be able to play songs by selecting them from the list and you can also exit the app using the end button.
Conclusion
We have now implemented basic playback of music tracks selected from the user's list of music files. In the final part of this series, we will add a media controller through which the user will be able to control playback. We will add a notification to let the user return to the app after navigating away from it and we will carry out some housekeeping to make the app cope with a variety of user actions.
Alcatraz is a package manager for Xcode 5 created and maintained by Marin Usalj and Delisa Mason. You may be asking yourself "Why would I need a package manager for Xcode?" You may not know this, but Xcode supports plugins, custom project templates, and color schemes. Installing these packages isn't difficult, but it can become cumbersome after a while. Alcatraz solves this problem in a spectacularly elegant fashion.
Development of Alcatraz began in 2013 so it's been around for quite some time. However, support for Xcode 5 wasn't great, but Alcatraz 1.0 has changed this.
Installation
Requirements
Alcatraz requires OS X Mavericks (10.9) and Xcode 5. Xcode 5 is a great improvement over Xcode 4, so if you're still working with Xcode 4, then now is a good time to switch to Xcode 5.
Another requirement of Alcatraz is that the Xcode Command Line Tools be installed. Installing the Xcode Command Line Tools is as simple as executing xcode-select --install from the command line.
Installing Alcatraz
You have two options to install Alcatraz. The first option is to execute the following command from the command line.
curl -fsSL https://raw.github.com/supermarin/Alcatraz/master/Scripts/install.sh | sh
This command downloads a shell script from GitHub and executes it, making the installation quick and painless.
Alternatively, you can clone Alcatraz's repository from GitHub and build it in Xcode. Don't forget to restart Xcode after installing Alcatraz.
Uninstalling Alcatraz
Uninstalling Alcatraz is as simple as removing the Alcatraz package in Xcode's Plug-ins directory. To do that, execute the following command from the command line.
Alcatraz not only makes it easy to install plugins, project templates, and color schemes, it's also easy to browse packages created by other developers. Just like CocoaPods maintains a specs repository, Alcatraz manages a packages repository, which you can browse using Alcatraz. Let's see how this works.
Fire up Xcode and select Package Manager from the Window menu. This should bring up Alcatraz's package browser. If you don't see the Package Manager menu item, then restart Xcode and, of course, make sure you're using Xcode 5+.
The real power of Alcatraz lies in what it does under the hood, so its user interface is simple and straightforward. Alcatraz shows a list of packages available to install. The icon in front of each package indicates what type of package it is and whether it's installed (blue) or not (grey). At the time of writing, Alcatraz supports plugins, project templates, and color schemes.
Installing a package is as simple as clicking the package icon on the left. To uninstall the package, you click the icon once more. It's as simple as that.
You can even see screenshots of a package by clicking the little eye icon when hovering over a package. This is especially useful if you're browsing color schemes in Alcatraz.
Clicking the little arrow icon on the right takes you to the package on GitHub or wherever the package is hosted.
Some Favorites
Plugins and project templates are a great but undervalued feature of Xcode. Alcatraz does its best to change this. Some of my favorite packages include:
I'm a big fan of Dash and the plugin for Xcode integrates Dash with Xcode. The plugin is maintained by Ole Zorn.
CocoaPods has become indispensable for many Cocoa developers. There's a plugin that integrates CocoaPods with Xcode, which makes managing dependencies even easier. The project is maintained by Delisa Mason.
Conclusion
Alcatraz is the package manager that Apple forgot to include in Xcode. Plugins and custom project templates are surprisingly powerful and some of them have become indispensable in my workflow. If you're a Cocoa developer, then I strongly recommend that you check out Alcatraz. What are some of your favorite packages?
We are building a simple music player app for Android in this series. So far, we have presented a list of the songs on the device and allowed the user to make selections from it, starting playback using the MediaPlayer class in a Service class. In this final part of the series, we will let the user control playback, including skipping to the next and previous tracks, fast-forwarding, rewinding, playing, pausing, and seeking to particular points in the track. We will also display a notification during playback so that the user can jump back to the music player after using other apps.
Introduction
The music player control functionality will be implemented using the MediaController class, in which a SeekBar instance displays the progress of playback as well as letting the user skip to particular locations in a track. We will use the Notification and PendingIntent classes to display the title of the currently playing track and let the user navigate back to the app.
This is how the app should look when you complete this tutorial:
After this series we will also explore related functionality you may wish to use to enhance the music player app. This will include video playback, streaming media, managing audio focus, and presenting media data in different ways.
1. Create a Controller
Step 1
Open your main Activity class and add the following import statement:
Extend the opening line of the class declaration as follows, so that we can use the Activity class to provide playback control:
public class MainActivity extends Activity implements MediaPlayerControl {
Hover over the class name and select Add unimplemented methods. Eclipse will add various methods for playback control, which we will tailor as we go along.
Step 2
The MediaController class presents a standard widget with play/pause, rewind, fast-forward, and skip (previous/next) buttons in it. The widget also contains a seek bar, which updates as the song plays and contains text indicating the duration of the song and the player's current position. So that we can configure the details of the control, we will implement a class to extend it. Add a new class to your project, naming it MusicController. In Eclipse, choose android.widget.MediaController as the superclass when creating it.
Give the class the following content:
public class MusicController extends MediaController {
public MusicController(Context c){
super(c);
}
public void hide(){}
}
You can tailor the MediaController class in various ways. All we want to do is stop it from automatically hiding after three seconds by overriding the hide method.
Tip: You may need to tweak the theme your app uses in order to ensure that the media controller text is clearly visible.
Step 3
Back in your main Activity class, add a new instance variable:
private MusicController controller;
We will be setting the controller up more than once in the life cycle of the app, so let's do it in a helper method. Add the following code snippet to your Activity class:
private void setController(){
//set the controller up
}
Inside the method, instantiate the controller:
controller = new MusicController(this);
You can configure various aspects of the MediaController instance. For example, we will need to determine what will happen when the user presses the previous/next buttons. After instantiating the controller set these click listeners:
controller.setPrevNextListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
playNext();
}
}, new View.OnClickListener() {
@Override
public void onClick(View v) {
playPrev();
}
});
We will implement playNext and playPrev a bit later, so just ignore the errors for now. Still inside the setController method, set the controller to work on media playback in the app, with its anchor view referring to the list we included in the layout:
We will also call it elsewhere in the class later.
2. Implement Playback Control
Step 1
Remember that the media playback is happening in the Service class, but that the user interface comes from the Activity class. In the previous tutorial, we bound the Activity instance to the Service instance, so that we could control playback from the user interface. The methods in our Activity class that we added to implement the MediaPlayerControl interface will be called when the user attempts to control playback. We will need the Service class to act on this control, so open your Service class now to add a few more methods to it:
public int getPosn(){
return player.getCurrentPosition();
}
public int getDur(){
return player.getDuration();
}
public boolean isPng(){
return player.isPlaying();
}
public void pausePlayer(){
player.pause();
}
public void seek(int posn){
player.seekTo(posn);
}
public void go(){
player.start();
}
These methods all apply to standard playback control functions that the user will expect.
Step 2
Now let's add methods to the Service class for skipping to the next and previous tracks. Start with the previous function:
public void playPrev(){
songPosn--;
if(songPosn<0) songPosn=songs.size()-1;
playSong();
}
We decrement the song index variable, check that we haven't gone outside the range of the list, and call the playSong method we added. Now add the method to skip to the next track:
//skip to next
public void playNext(){
songPosn++;
if(songPosn>=songs.size()) songPosn=0;
playSong();
}
This is analogous to the method for playing the previous track at the moment, but we will amend this method later to implement the shuffle functionality.
Step 3
Now switch back to your Activity class so that we can make use of these methods. First add the methods we called when we set the controller up:
We call the methods we added to the Service class. We will be adding more code to these later to take care of particular situations. Now let's turn to the MediaPlayerControl interface methods, which will be called by the system during playback and when the user interacts with the controls. These methods should already be in your Activity class, so we will just be altering their implementation.
Start with the canPause method, setting it to true:
@Override
public boolean canPause() {
return true;
}
Now do the same for the canSeekBackward and canSeekForward methods:
@Override
public boolean canSeekBackward() {
return true;
}
@Override
public boolean canSeekForward() {
return true;
}
You can leave the getAudioSessionId and getBufferPercentage methods as they are. Amend the getCurrentPosition method as follows:
@Override
public int getCurrentPosition() {
if(musicSrv!=null && musicBound && musicSrv.isPng())
return musicSrv.getPosn();
else return 0;
}
The conditional tests are to avoid various exceptions that may occur when using the MediaPlayer and MediaController classes. If you attempt to enhance the app in any way, you will likely find that you need to take such steps since the media playback classes throw lots of exceptions. Notice that we call the getPosn method of the Service class.
Amend the getDuration method similarly:
@Override
public int getDuration() {
if(musicSrv!=null && musicBound && musicSrv.isPng())
return musicSrv.getDur();
else return 0;
}
Alter the isPlaying method by invoking the isPng method of our Service class:
Do the same for the pause, seekTo and start methods:
@Override
public void pause() {
musicSrv.pausePlayer();
}
@Override
public void seekTo(int pos) {
musicSrv.seek(pos);
}
@Override
public void start() {
musicSrv.go();
}
3. Handle Navigation Back Into the App
Step 1
Remember that we are going to continue playback even when the user navigates away from the app. In order to facilitate this, we will display a notification showing the title of the track being played. Clicking the notification will take the user back into the app. Switch back to your Service class and add the following additional imports:
We will add the missing variables next. The PendingIntent class will take the user back to the main Activity class when they select the notification. Add variables for the song title and notification ID at the top of the class:
private String songTitle="";
private static final int NOTIFY_ID=1;
Now we need to set the song title, in the playSong method, after the line in which we retrieve the song from the list (Song playSong = songs.get(songPosn);):
songTitle=playSong.getTitle();
Since we have called setForeground on the notification, we need to make sure we stop it when the Service instance is destroyed. Override the following method:
@Override
public void onDestroy() {
stopForeground(true);
}
4. Shuffle Playback
Step 1
Remember that we added a shuffle button, so let's implement that now. First add new instance variables to the Service class:
private boolean shuffle=false;
private Random rand;
Instantiate the random number generator in onCreate:
rand=new Random();
Now add a method to set the shuffle flag:
public void setShuffle(){
if(shuffle) shuffle=false;
else shuffle=true;
}
We will simply toggle the shuffle setting on and off. We will check this flag when the user either skips to the next track or when a track ends and the next one begins. Amend the playNext method as follows:
public void playNext(){
if(shuffle){
int newSong = songPosn;
while(newSong==songPosn){
newSong=rand.nextInt(songs.size());
}
songPosn=newSong;
}
else{
songPosn++;
if(songPosn>=songs.size()) songPosn=0;
}
playSong();
}
If the shuffle flag is on, we choose a new song from the list at random, making sure we don't repeat the last song played. You could enhance this functionality by using a queue of songs and preventing any song from being repeated until all songs have been played.
Step 2
Now we can let the user select the shuffle function. Back in your main Activity class in the onOptionsItemSelected method, amend the section for the shuffle action to call the new method we added to the Service class:
case R.id.action_shuffle:
musicSrv.setShuffle();
break;
Now the user will be able to use the menu item to toggle the shuffling functionality.
5. Tidy Up
Step 1
We are almost done, but still need to add a few bits of processing to take care of certain changes, such as the user leaving the app or pausing playback. In your Activity class, add a couple more instance variables:
We will use these to cope with the user returning to the app after leaving it and interacting with the controls when playback itself is paused. Override onPause to set one of these flags:
If the user interacts with the controls while playback is paused, the MediaPlayer object may behave unpredictably. To cope with this, we will set and use the playbackPaused flag. First amend the playNext and playPrev methods:
Now set playbackPaused to true in the pause method:
@Override
public void pause() {
playbackPaused=true;
musicSrv.pausePlayer();
}
As you work with the MediaPlayer and MediaController classes, you will find that this type of processing is a necessary requirement to avoid errors. For example, you will sometimes find that the controller's seek bar does not update until the user interacts with it. These resources behave differently on different API levels, so thorough testing and tweaking is essential if you plan on releasing your app to the public. The app we are creating in this series is really only a foundation.
Step 3
Let's take some final steps to make the app behave consistently. Back in the Service class, amend the onError method:
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.reset();
return false;
}
We simply reset the player, but you may of course wish to enhance this approach.
The onCompletion method will fire when a track ends, including cases where the user has chosen a new track or skipped to the next/previous tracks as well as when the track reaches the end of its playback. In the latter case, we want to continue playback by playing the next track. To do this we need to check the state of playback. Amend your onCompletion method:
We call the playNext method if the current track has reached its end.
Tip: To ensure that your app does not interfere with other audio services on the user's device, you should enhance it to handle audio focus gracefully. Make the Service class implement the AudioManager.OnAudioFocusChangeListener interface. In the onCreate method, create an instance of the AudioManager class and call requestAudioFocus on it. Finally, implement the onAudioFocusChange method in your class to control what should happen when the application gains or loses audio focus. See the Audio Focus section in the Developer Guide for more details.
That is the basic app complete! However, you may well need to carry out additional enhancements to make it function reliably across user devices and API levels. The controls should appear whenever you interact with the app.
The notification should allow you to return to the app while playback continues.
Conclusion
We have now completed the basic music player for Android. There are many ways in which you could enhance the app, such as adding support for streaming media, video, audio focus, and providing different methods to interact with the music tracks on the device. We will look at some of these enhancements in future tutorials, outlining how you can add them to the app or to other media playback projects. In the meantime, see if you can extend the app to build additional functionality or to improve reliability on different devices. See the Media Playback section of the Android Developer Guide for more information.
The new look and feel of the iOS 7 user interface relies heavily on subtle animations to help give users a stronger sense of direct manipulation. In this tutorial, I'll give you an overview of the UIKit Dynamics classes and how they work together.
1. Introduction
If you tap the camera icon on the lock screen, you'll see the screen slide up slightly to reveal the camera interface before falling back into place with a gentle bump. This interaction is a subtle hint that you can slide the lock screen up to access the camera. At the same time, it reassures you that if it accidentally slides a short distance, for example, while your phone is in your pocket or bag, you won't end up with a Camera Roll full of useless photos.
With the help of UIKit Dynamics, you can add animations to your own apps without needing to code everything from scratch. There are hundreds of versions of the Hamburger Menu on GitHub. Each one allows you to slide the screen to the side to reveal a hidden menu and many have a bounce animation on opening and closing.
However, the bounce effect has been implemented differently by each developer. Apple have made it easier for developers to add these finishing touches by providing a framework for the physical interactions of elements on the screen. With UIKit Dynamics, you only need to specify how the elements interact, not code the physics of each interaction.
There are several new classes you will need to become familiar with. Let me walk you through them in this article. In the next tutorial, I will show you how to use them in your own projects.
2. Dynamic Items
Any class you want to animate needs to conform to the UIDynamicItem protocol, which basically means that it needs to expose its bounds, center, and transform properties. UIView and UICollectionViewLayoutAttributes, and their subclasses, already conform to the protocol by default. If you just want to add animations to your existing interface, you probably won't need to interfere with the components' classes at all. If you're using something more exotic and have lots of custom classes, you need to do some extra work before you can start using UIKit Dynamics.
3. Dynamic Behavior
The way to let your application know how you want your dynamic items to interact, is to create a dynamic behavior and let it know about the dynamic items. The UIDynamicBehavior class is readily available, but it provides little functionality on it's own. You can subclass UIDynamicBehavior to tell the application how the dynamic items should interact, but you probably want to use one of the predefined subclasses which cause the dynamic items to interact in different ways.
UIAttachmentBehavior
An attachment behavior relates dynamic items to each other or to an anchor point as though they were connected by springs. When a dynamic item or the anchor point moves, the attached dynamic item also moves. The attachment behavior has several properties, which can be configured to control the behavior of the the virtual spring that connect the dynamic items.
Damping: Defines how quickly the spring stops bouncing after one of the items moves.
Frequency: Defines how quickly the spring bounces when one of the items moves.
Length: Defines how long the spring is once it stops bouncing.
UICollisionBehavior
A collision behavior allows its dynamic items to collide with each other or with a bounding box that you specify. A dynamic item won't collide with a dynamic item that isn't part of the same collision behavior. The collision behavior allows you to set whether the dynamics items collide with just other items, just the bounding box, or other items and the bounding box.
UIGravityBehavior
A gravity behavior causes its dynamic items to fall in the direction in which gravity is acting. By default, gravity acts downwards, but you are able to set any angle you choose by changing the angle property. You can change the gravity behavior's magnitude property to cause items to fall faster or slower.
UIPushBehavior
A push behavior applies a force to its dynamic items causing them to move in the direction of the force. You have more control over a push behavior than you do over a gravity behavior as the force applied can be continuous or instantaneous.
UISnapBehavior
A snap behavior causes its dynamic item to move to a specified point as though it were connected to it by a spring. Like the attachment behavior, a snap behavior will cause the dynamic item to overshoot the final point before springing back to it. You can set the damping property of the behavior to define how quickly the oscillations die down.
UIDynamicItemBehavior
You should use UIDynamicItemBehavior if you want to control how each dynamic item moves. You can independently add rotation or movement, and set properties to control the motion. It requires more development than the predefined behaviors, but will allow you to model much more physical scenarios if you have a specific behavior in mind.
4. Dynamic Animator
Once you've created some dynamic behaviors, you will need a dynamic animator to provide the context and calculations for the dynamic items you want to animate. The dynamic animator will update your dynamic items' position and rotation according to the dynamic behaviors. Create UIDynamicAnimator with initWithReferenceView: if you're going to animate individual views, or with initWithCollectionViewLayout: if you plan on animating a collection view.
Conclusion
There are new mechanisms in iOS 7 to help you implement subtle animations without requiring lots of extra work. The views you want to animate must be dynamic items, that is, they need to conform to the UIDyamicItem protocol.
You can then create one or more dynamic behaviors, instances of UIDynamicBehavior or any of its subclasses, to represent the interactions you wish to simulate. Finally, you pass the behaviors to a dynamic animator, which is responsible for updating the position and rotation of your dynamic items according to the behaviors you specified. In the next tutorial, I'll show you a concrete example of these UIKit Dynamics in action.
The explosive growth of the mobile space has accelerated the search for a robust and viable cross-platform solution. In 2008, shortly after the introduction of the iPhone SDK and after fiddling with Cocoa and Objective-C, Brian LeRoux and his colleagues at Nitobi decided that their time was better spent building a cross-platform solution than building native mobile applications.
Today, PhoneGap powers tens of thousands of mobile applications. For Brian and his team, a lot has changed since the inception of PhoneGap. In 2011, Adobe acquired Nitobi and the source of PhoneGap was donated to the Apache Software Foundation as Cordova.
In today's spotlight, I talk with Brian about the early days of PhoneGap, the future of mobile, and why PhoneGap's destruction is a good thing.
PhoneGap has been around since the start of the mobile revolution and is well known among developers. For those who don't Brian LeRoux or PhoneGap, can you tell us about yourself and how you're involved in the project?
PhoneGap was created by a small group of people most of which worked at Nitobi, in Canada at the time.
The first commits where landed by Brock Whitten and Rob Ellis for iOS. These days, iOS is completely the domain of the prolific Shazron Abdulla. Joe Bowser hacked out the very early Android and continues to maintain it to this day.
Dave Johnson added various BlackBerry bits, now largely maintained by BlackBerry themselves with the help of Lorin Beer. Jesse Macfadyen cut the Windows Phone incarnations working closely with Microsoft.
Michael Brooks rocked out the documentation and much of the CLI (Command Line Interface) and test tooling with Fil Maj. Anis Kadri has lead much of the plugins tooling and discovery. Steve Gill has been responsible for releases and contributed much of the related tooling.
Herm Wong started up Firefox OS and now our new GUI project. The branding identity started with Yohei Shimomae and has since been taken up by Joni Rustulka. Google and IBM have a whole bunch of contributors too.
A tidy creation story with a single hacker in the basement is a kind of fantasy that our industry loves but is rarely the case. Software is always a collective effort and everyone that contributes deserves recognition. I'm missing a Canadian metric ton of contributors here, but you get the idea.
For myself, I worked at Nitobi, contributed plenty of code to various areas of PhoneGap from incarnation, but my main focus was building the early project culture, philosophy, and goals—the things crucially important in communicating direction and galvanizing community.
Testing, tooling, and onboarding were other early key concerns for me. Eventually my focus shifted more to growing the committership beyond Nitobi, which culminated in the source donation to Apache as Cordova.
With PhoneGap being close to six years old, most people have at least heard of it. For those not familiar with PhoneGap, can you tell what problem PhoneGap tries to solve?
PhoneGap is for building mobile apps using HTML, CSS, and JavaScript. We support all major mobile operating systems for building and distributing to native app stores. But we are web developers foremost and PhoneGap's intent is to demonstrate the web as a first class development platform. We want to build web apps, not proprietary traps.
Ultimately, PhoneGap gives you a fancy full screen web browser and an extensibility model for accessing native platform functionality through a simple plugin interface. The plugin model makes it trivially simple to expose anything on the operating system to the web view. In this way, downstreams can rapidly prototype new web features and application developers are not constrained by the traditional web view sandbox.
In recent years the bulk of our effort has been dedicated to creating tools that abstract common native mobile development workflows. Compiling, emulating, logging, installing plugins, and that sort of thing.
What did the early days of PhoneGap look like? When did you realize PhoneGap was a solution to a problem many companies and developers were facing?
The early days were ridiculous and fun. PhoneGap was a side project mostly and the early core developers often hacked and philosophized at the Alibi Room, a famous beer bar in Vancouver, after hours.
Slowly, as mobile began its meteoric rise, many other developers came to the fray, attracted by the philosophical understandings we were sharing.
It was, and still is, a group fed up with proprietary platforms, changing operating systems, and locked in developer ecosystems. Fatigued by software platforms claiming "one true way", only to update "that way" every six months and later deprecate it—if not disappear altogether.
All through years of this abuse, the web platform was slowly improving and apps that targeted it still ran. We were no longer falling for the shiny marketing material calling itself "human interface design guidelines".
The web has never suffered any threat, but we found a hack that could bring the fight to the very platforms threatening it. The project was always open source, respected the web first, and designed to demonstrate features we felt the platform needed to remain competitive to proprietary alternatives. It has always been a scrappy group of hackers, but we are careful to not take ourselves too seriously.
A few years ago, you said that PhoneGap "isn't a golden hammer" and that PhoneGap isn't the solution for every mobile application. Is that still true or are we coming closer to a mobile web that's as powerful as the native experience?
The spectrum of potential apps is always growing wider as browsers, and the devices they run on, improve. I would never advocate PhoneGap as the ultimate solution. There are technical considerations such as native platform affordances and there are softer concerns such as business drivers, employee skills, existing content investment, licensing, reliance in third party platform vendors, and even partner relations.
Technology choices always bring tradeoffs and investing in web technologies like PhoneGap is no different.
The real challenge faced by developers, and especially in the enterprise, is recognizing that mobile app development is just like regular software development. This isn't just some point in time marketing spike. There is an entire lifecycle to consider; design, development, testing, analytics, and monitoring.
Mobile development requires ongoing maintenance and resourcing. One-offs by a consulting firm will need updating when a new version of iOS or Android ships. The marketing department needs to understand what content is performing and have the ability to quickly publish changes to content that isn't performing. The IT department needs runtime crash reporting and access to push notification infrastructure.
The longer game that requires deliberate strategic resourcing is only just being recognized as many organizations are only just now discovering that they, at least in part, are becoming software companies themselves. A strategic investment in technology that relies on a third party proprietary ecosystem is a business risk. PhoneGap can help mitigate that risk. Ultimately you'll never lose a bet on the web.
The acquisition of Nitobi by Adobe was an important milestone for PhoneGap, but Apache Cordova was probably even more important. How has Apache Cordova changed the platform?
The acquisition of Nitobi by Adobe was absolutely important. We were freed of crazy consulting work to focus solely on PhoneGap and there is no question that the platform benefitted tremendously. The donation of PhoneGap source to Apache as Cordova is equally significant in a longer view.
Working with Apache brought a whole new level of discipline to the project. Our release process is far more formalized and while it has been challenging to keep up our cadence, our community wins the legal safe ground that Apache is famous for.
This neutral territory is a great environment for individuals employed by different organizations to collaborate without concern. Since joining Apache we have welcomed committers from IBM, BlackBerry, Microsoft, Google, Intel, HP, LG, Samsung, and more.
As a result of this, we've seen many new downstream distributions of Cordova. My bias is for Adobe PhoneGap, but developers can choose to target BlackBerry Webworks, IBM Worklight, SAP SDK, Telerik, Intel XDK, or Google Mobile Chrome Apps.
Some folks just use vanilla Apache Cordova and create their own downstream. I love this diversity. All of this points to a vibrant and healthy ecosystem that our developer community can count on. We iterate fast, address bugs quickly, add new features on a regular cadence, and have a very well understood contribution process anyone can participate in.
Apache has a well earned reputation for politics and bureaucracy, but improving it is a part of our job too and working with the ASF (Apache Software Foundation) has been ultimately the right path for our community long term. I am very proud of what we have accomplished with the ASF.
PhoneGap is a great platform for developing mobile applications. The deployment remains cumbersome on many platforms, but you've tried to solve this with PhoneGap/Build. PhoneGap/Build does sound like a golden hammer for developers who are looking for a cross-platform solution. Can you tell us what the service does and what problem it solves?
PhoneGap Build is a compiler hosted in Adobe Creative Cloud. Using PhoneGap/Build, you can target any mobile operating system we support from any web browser. You can build an iOS app from a netbook or even your own phone (meta).
Initially we thought this might be helpful for continuous integration and testing purposes. It has become an all-around utility for the very discreet process of compiling an app and giving the resulting artifact a URL. It does just that one thing and it does it rather well. We've seen many people use PhoneGap/Build as an API or as their compiler.
You once said that you believe PhoneGap's future lies in its own destruction. Can you explain what you mean by that?
Yes, the ultimate goal of PhoneGap is to cease to exist. We do not want to write native apps. We have been down the road of proprietary client development and know it leads to risky ecosystem lock-in.
We want to build web apps and PhoneGap always has been a stopgap solution until browsers, or perhaps installable web apps, are capable alternatives. I think we are very close to that reality today. For many applications, web first is absolutely appropriate.
In order for mobile web apps to succeed with the ubiquity we see on the desktop, it is helpful to demonstrate the areas the web needs to improve. The PhoneGap plugin architecture is a really slick surface to create discreet prototypes for exposing new features to the traditional web surface. This subtle philosophy helped light the right directions for us, by implementing standards, pollyfills, collaborating with the W3C on API design, and bringing concerns to the browser vendors that lead to new platform features.
Open acknowledgment of ultimate demise could either be a tragic event in time or strategic understanding to plan against. To witness to our own undoing, PhoneGap needs to do everything it can to see the web platform win.
What are some of the painpoints that we still need to solve on the web? In other words, how close are we from a mobile web that offers an experience rivaling that of native applications?
25 years on, it's hard to criticize the web platform. However, bashing the web, on the web, is a time honored tradition of the webmaster craft.
The #1 revenue generating category in the App Store is games. So let's think about what it takes to be a great game. Audio overall is messy, but the Web Audio API is crazy awesome. WebRTC, or whatever we end up calling it, holds great promise for making real time apps more real.
Then there is a bunch of plumbing that hasn't quite landed ubiquitously such as Full Screen and Game Controller. When all of this stuff is generally available, it will shake up gaming. Data intense APIs like Web Audio, WebRTC, and WebGL are going to help us find the edges of JavaScript execution performance and all early indications are extremely positive.
Layout is getting quite good. Flexbox is great and I have high hopes for CSS Grids. The last version of Firefox (28) fixes the last bugs with Flexbox. I have no idea when CSS Grids land, but I am patient. Media Queries, sometimes known as Responsive Web Design, are helpful. I want a more robust capability model to allow us to render adaptive interfaces optimally.
The biggest opportunity is really cracking the offline story—probably better termed "occasionally connected". Installed web apps—like PhoneGap apps—are intrinsically offline, but a full permission model remains to be defined. Mozilla, Google, and the W3C are working on it.
Many of our readers have the ambition to develop for mobile. If you were starting out today, where would you start? What advise would you give yourself?
Mobile isn't much different than regular client programming. The classic advice is to test on real devices and I do encourage people to learn the native platforms, but to not grow too attached to them. A good example would be the iOS 6 to iOS 7 update. A well architected and designed PhoneGap or regular web app was not fragile to that update.
Otherwise, all the regular programmer wisdom applies. Be ambitious in your scope but discreet in your implementations. Create lots of branches and be prepared to throw most of your work away. You are not your code, so refactor ruthlessly and seek critical feedback.
Small modules and prototypes are easier to reason about, course correct, test, and validate. Don't get caught up in framework and library fashion. Focus on the problem space you have and iterate furiously and dispassionately. Write tests and make it dead simple for someone new to the codebase to run the tests themselves.
Finally, be super excellent to your colleagues. The web has a long memory, this industry is smaller than it first may appear and nobody wants to work with an asshole. No one has ever thought a rude person was being smart. Flaming is just insecure behavior and professionally immature. Programming is hard enough, we can all learn something from each other and opt for a pleasant experience in doing so.
Future Insights Live 2014
I'd like to thank Brian for his time and for sharing his ideas and insights with Tuts+. You can hear Brian speak at Future Insights Live 2014 in Las Vegas in June. The conference has an impressive list of speakers covering the best in web design, development, and mobile. Use coupon code TUTS for 10% off.