Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all 4374 articles
Browse latest View live

Winners – Reader’s App Awards 2013!

$
0
0
Get ready for our Reader's Apps Awards for 2013!

Get ready for our Reader’s Apps Awards for 2013!

It’s that magical time of year again, we’ve been revisiting the entire archive of 2013 Readers’ Apps and picking the best of the year!

This is a huge deal, so I had some help: a team of 8 judges including Ray. Together we voted on the best apps of the year for 7 categories:

  • Most Addicting: The app that made time fly.
  • Most Hilarious Idea: The app that had us on the floor.
  • Most Surprisingly Good: The app that surprised us all.
  • Most Technically Impressive: The app that made our inner programmer jealous.
  • Most Visually Impressive: The app that dropped our jaws.

As well as two grand prize winners, who will walk away with an epic prize pack of over $1000 in value:

  • Best Reader’s Game: The overall best game of the year from a reader like you.
  • Best Reader’s App: The overall best app (non-game) of the year from a reader like you.

Drum roll please… keep reading to see the best reader’s apps of 2013!

Most Addicting

Sky Hawks

Sky Hawks has been a go-to game on my iPhone for months now. In Sky Hawks, you take off for fun and frantic dogfights in the sky.

I love its retro look and feel, and there are plenty of planes to unlock for the competitive advantage and endless swarms of enemies to battle in the sky.

“A fun, easy to play twin stick shooter, that keeps you coming back for just one more round. Get your Top Gun on without having to play volleyball with Tom Cruise afterwards.” – Brian Moakley

“Sky Hawks took me back to my early days of gaming. A well implemented, entertaining trip into the past.” – Dustin Bruzenak

“Seriously, who doesn’t want to fly a fighter jet? I loved this game so much that before I realized it, I was making ‘Pew! Pew! Pew!’ sounds while I was playing.” – Tammy Coron



Most Hilarious Idea

Mutton for Punishment

What do you do if you want the addiction of missile command but with a hilarious twist? Add some Mutton of course!

Mutton for Punishment pits the wolves against the sheep in this missile command style arcade game. The wolves have learned to use bow and arrows but the sheep have an ace up their sleeve, You! Fling rocks to deflect the wolves attacks and defend the flock!

“Brings the love of Missile Command to… mutton. Protecting sheep is more fun!” – Bear Cahill

“Fairytale graphics, nice music and challenging levels, you need to have an eye like Robin Hood and be able to anticipate to keep playing. – Przemek Rembelski



Most Surprisingly Good

Teggleteggle

Teggle looked like a game too simple to be fun, but it turned out to be quite addictive. Born of multitouch, Teggle has you frantically tapping and swiping to win before long.

Teggle is simple, but its also hard to master and surprisingly addictive as you try to extend your combos and boost your score.

“I don’t know what “teggle” means, but if I had to guess, I’d say, “Slick, flat design, intuitive controls, and fast-paced gameplay that’s so shockingly simple and addictive that you can’t stop kicking yourself for not thinking of it yourself.” – Chris LaPollo

“If the design and elegance of iOS 7 was distilled into a game … this would be it. It’s both simple to play yet very addicting.” – Brian Moakley



Most Technically Impressive

ECHO

ECHO takes iPhone gaming to a new level. ECHO uses directional, stereo audio to give you cues where obstacles are at as you fly through space. But don’t think you can play this game on mute, for once you’d better trust your ears not your eyes. ECHO dims the screen as you advance until you’re flying in almost pitch black sky. Your only hope is to listen to your ears.

ECHO was designed before the iPhone and iPad had stereo speakers so you’ll have to grab a pair of headphones to really get into it unless you’ve got the latest tech, but its well worth it to give this impressive game a try.

“Whoa! Don’t play this game in the dark. It’s creepy… and mesmerizing. I could play this game forever (except in the dark, which I think we established already)” – Tammy Coron

“Truly rewarded for putting the headphones on – I might just listen to this as my coding soundtrack. :)” – Bear Cahill



Most Visually Impressive

Wide Sky

Wide Sky is a game with stunning graphics and animations that doubles as a chance to hurl hedgehogs through the sky.

Wide Sky takes 2D to the extreme with crisp retina graphics and fast, fluid animations. And who doesn’t want to sling hedgehogs into aerobatic feats of agility?

As you collect stars and orbs you can unlock new power-ups and more springy ropes to push the limit on how high you can sling your hedgehogs.

“I am totally in love with graphics in this game. At the beginning not the easiest game to understand but absorbing enough to get the picture quickly.” – Przemek Rembelski

“Wide Sky is absolutely beautiful. The art style rivals the best that the app store has to offer. I’m abysmally bad at the game itself, but the wonderful graphics kept me playing long past the point of frustration.” – Dustin Bruzenak

“This game has an amazing sense of humor and a great sense of design that must be seen to be believed. I see great things in Marcus’s future.” – Ray Wenderlich



Grand Prize Winners

Finally we make it to our two Grand Prize winners! Lets take a look at what they’ve won!

Readers' Apps Awards 2013

The “Best Reader’s App” and “Best Reader’s Game” grand prize winners will win a massive prize pack with over $1,000 in value!

Tools

  1. AppViz 6-month subscription ($60 value): The best tool for monitoring your iOS and Mac App Store sales.
  2. ParticleDesigner ($50 value): Create amazing particle systems for your games with a visual editor!
  3. GlyphDesigner ($40 value): Design beautiful fonts for your iOS games!
  4. Tower Git ($60 value): The most powerful Git client for your Mac.
  5. Pixelmator ($30 value): An inspiring, easy-to-use, beautifully designed image editor built to help you create stunning new images and edit your existing photos.
  6. Paint Code ($75 value): PaintCode is a vector drawing app that generates Objective-C code in real time. Mentioned in our PaintCode tutorial series!
  7. New! PixelKit Designer 1-Year Subscription ($39 value): PixelKit is a high quality set of reusable icons and other UI elements to help your app look great!

Books

  1. iOS Apprentice 1-4 Bundle ($54 value): Learn how to make iPhone and iPad apps via epic length tutorials – for complete beginners!
  2. iOS 5 by Tutorials Second Edition ($24 value): Learn about the new APIs that were introduced in iOS 5 like ARC, Storyboards, and Core Image. Now fully updated for iOS 6!
  3. iOS 6 by Tutorials ($44 value): Learn about the new APIs that were introduced in iOS 6 like Auto Layout, Collection Views, and Passbook – over 1,500 pages of high quality content!
  4. iOS 7 by Tutorials ($54 value): Learn about the new APIs that were introduced in iOS 7 like “flat” design, UIKit dynamics, and NSURLSession – over 800 pages of high quality content!
  5. iOS Games by Tutorials ($54 value): Learn how to make your own iOS games using Apple’s brand new game framework, Sprite Kit.

Starter Kits

  1. Space Game Starter Kit ($129 value): Learn how to make a full side-scrolling space game with Cocos2D!
  2. Platformer Game Starter Kit ($129 value): Learn how to make a side-scrolling platformer game (like Mario)!
  3. Beat ‘Em Up Game Starter Kit ($129 value): Learn how to make a beat ‘em up game (like Double Dragon)!

Swag

  1. raywenderlich.com T-shirt and magnet set ($25 value): Sport a stylish black t-shirt with a colorful mosaic iPhone design!
  2. 2013 Reader’s Apps Awards Plaque (Priceless!): Show off to your friends and family that your app has been recognized as the best Reader’s App of the year!


And without further delay, here are your 2013 Reader’s App Awards Grand Prize Winners!


Best Reader’s Game

Whirl the SquirrelWhirl

Whirl the Squirrel is a fantastic side scrolling, level based, adventure runner. You’ve got to help Whirl get back his treasures from the evil ‘Nenemies. Its got some hilarious animations as you jump and roll, go through hoops and under electric fences, and blast through with powerups.

Its got all the necessary components of a great iOS game including retina graphics, universal support, and GameCenter leaderboards & achievements.

Whirl the Squirrel is a polished, beautiful game thats fun for hours. It should be the benchmark for everyone hoping to make a great game! :]

“There is no question that you should try this game. Impressive visuals and great gameplay.” – Przemek Rembelski

“This fast-paced platformer confirms what I’ve always suspected: it would be fun to strap a rocket to a squirrel! Great levels that are difficult to beat on your first try, but doable with some practice. The art looked great and the gameplay was smooth. I don’t usually like virtual buttons on touch devices, but these didn’t bother me at all.” – Christopher LaPollo

“This game brings back fond memories of sitting in front of my TV with starry eyes playing Sonic the hedgehog. The artwork is amazing and the gameplay is crisp – well done!” – Ray Wenderlich



Best Reader’s App

Amaziograph

You’ll never be bored with Amaziograph! Amaziograph is an iPad drawing application like none other.

Amaziograph turns your simple drawings into complex works of art with one of six symmetry templates straight from your favorite kaleidoscopes. But it just keeps getting better from there, because you can change templates mid drawing and layer your strokes to create truly one of a kind artwork.

All the normal drawing features you need are present from brush sizes, to full RGB color support, to transparency, and more. You can even drag you line of symmetry to bend the templates to your will.

Amaziograph is certainly the most unique drawing app I’ve seen, and its also a well made, polished iPad application that we all should strive to match.

“Quite easy to create dynamic beautiful images. It’s one of the few drawing programs that I found the actual creation of an image to be just as hypnotizing as the end product.” – Brian Moakley

“This app makes me feel like an artist – and believe me, with my drawing skills that is quite a feat! This can easily keep you mesmerized for hours – it’s the kind of app I wish I had when I was a kid.” – Ray Wenderlich


That’s It!

Congratulations to our winners! If you are a grand prize winner, Ray will be in touch to get your prizes to you. :]

Thanks to all our judges for helping me pick! And a very special thanks to Dustin Bruzenak for being an awesome guest judge again.

And a huge thanks to our our amazing sponsors who donated their great apps and resources for our prizes. Be sure to check out the sites for all of our sponsors, we wouldn’t be able to do this without them!

Don’t forget, if you’ve made an app or game using our tutorials, we’d love to review your app too! Please submit it for next month.

As always, it was a load of fun playing with your apps and getting to see our tutorials in action! Its been another terrific year for apps and we can’t wait to see what you submit next year. Have a Merry Christmas!

Winners – Reader’s App Awards 2013! is a post from: Ray Wenderlich

The post Winners – Reader’s App Awards 2013! appeared first on Ray Wenderlich.


The iOS Apprentice Print Version Now Available!

$
0
0

The iOS Apprentice series has been out for almost three years now. It has been updated each year to the latest version of iOS, from iOS 4 all the way to iOS 7.

But during all of this time, we’ve never had a print version of the book available. That changes today! :]

IA1234Print1

In case you haven’t heard of the iOS Apprentice before, it is our introduction to iOS development for complete beginners.

The book is 816 pages, and split into four epic-length tutorials, each of which shows you how to build a complete app from scratch. And best of all – the book is fully up-to-date with Xcode 5 and iOS 7!

IA1234Print2

The iOS Apprentice is written by Matthijs Hollemans, one of the best developers and writers on the Tutorial Team. The book has sold thousands of copies, and has received rave reviews from readers such as this:

“Your writing style, your humor and your explanations are one of the best (if not the best, seriously) I’ve ever seen. Very happy to have spent my money for your work.” –Adriano G

Note that on our store page, we have two options for each product:

  • Just the PDF. Nothing’s changed here – if you prefer electronic books you can buy just the PDF same way you usually would.
  • The PDF + Print Version Bundle. This gives you the best of both worlds. It also saves you a lot of money – you’re effectively getting the printed books at a big discount compared to buying them separately.

Note we don’t offer an option for just the printed books. This is because we like to keep our books as up-to-date as we can, and we want to be able to give all our customers access to these updates (in PDF form). So far, iOS Apprentice PDF customers have received 4 major updates since it was first released (from iOS 4->5->6->7)!

Discount for iOS Apprentice PDF Customers

If you bought the PDF version of the iOS Apprentice, you are eligible for a special discount on the print version, to effectively “upgrade” your order to the PDF+Print bundle.

I will send all PDF version customers an email with instructions for how to upgrade your purchase in a few minutes.

If for some reason you do not get the email, just contact me and we’ll get it sorted.

Note this discount will expire in at the end of 2013, so be sure to grab it before New Year’s! :]

IA1234Print3

The Giveaway

One last thing. To celebrate the launch, we’re giving away a copy of the book!

All you have to do to enter is leave a comment on this post – next Monday we’ll choose a random winner.

That’s it – we hope you all enjoy the print version of the iOS Apprentice! If you’d like to pick up a copy just check out our store page.

The iOS Apprentice Print Version Now Available! is a post from: Ray Wenderlich

The post The iOS Apprentice Print Version Now Available! appeared first on Ray Wenderlich.

Merry Christmas 2013!

$
0
0

As you may or may not know, we have a tradition where each year, we sing a silly Christmas song about a geeky topic.

This year, we have made a song titled “This Year in iOS 7″, sung to the tune of “Rudolf the Red Nosed Reindeer.” We hope you enjoy – and we apologize in advance for our singing! :]

Lyrics and Credits

This year in iOS 7 (Ray Wenderlich)
Jony kept us on our toes (B.C. Phillips)
He redesigned the OS (Pietro Rea)
Skeuomorphism: it goes. (Chris Belanger)

All of the app designers (John Nyquist and family)
used to laugh and call iOS names (Ellen Shapiro)
They made fun of all the leather (Bear Cahill)
And green felt inside iOS games. (Pawel ‘Kender’ Maczweski and family)

Then this year at WWDC (Scott Sherwood and family)
Tim Cook came to say (Soheil Azarpour)
App devs with your eyes so bright (Cesare Rocchi)
You’ll be redesigning your apps tonight. (Toby Stephens and family)

Then all the coders loved him (Tammy Coron)
As they shouted out with glee (yippee!) (Brian Moakley and family)
With such a major update (Ryan Poolos)
Hello job security! (Ray Wenderlich and Vicki Wenderlich)

Special thanks to Cosmin Pupaza for the guitar background music! :]

But Wait, There’s More!

If you enjoyed this video, we have a few more videos you might want to check out:

That’s It!

Have a Merry Christmas and very happy New Year everyone, and thanks so much for reading this blog! :]

Merry Christmas 2013! is a post from: Ray Wenderlich

The post Merry Christmas 2013! appeared first on Ray Wenderlich.

Google Analytics for iOS

$
0
0
Learn how to use Google Analytics

Learn how to use Google Analytics for iOS7!

To build successful products, you and your team have to become a lean, mean, measuring machine! Otherwise, you’ll have little way of knowing which parts of your app are consistently used and in working order.

There are many ways to measure usage and many off-the-shelf frameworks that can help. In this tutorial, you’re going to look at using Google Analytics for iOS.

This tutorial will take you through the major components of the Google Analytics SDK. Along the way, you will add Google Analytics to a timer app suitable for runners, and put the SDK to work tracking how frequently people use the various features and controls.

When you are finished you will have enabled app screen and event tracking, you will be able to track app versions usage, and will allow the app users to opt-out of the anonymous usage tracking if they wish so:

Opt Out

Take your position at the starting line. Get ready… get set… BOOM! GO, GO, GO!

Getting Started: What is Google Analytics?

Google Analytics is a service offered by Google that generates detailed statistics about your website or mobile app’s usage. Originally, the product was aimed at marketers rather than developers. However, modern development methodologies require developers to define, measure and test the features they create in an iterative, low-cost way. Google Analytics is a great tool for measuring your app’s usage to test if new features are working both functionally and experientially.

Google Analytics provides you with high-level, dashboard-type data for an overview of your app’s performance, along with more in-depth reporting tools. Its analysis can identify such things as poorly performing screens, important user events and the geographical distribution of your user-base… all of which are critical to improving your app!

With a quick glimpse at the Google Analytics Dashboard you can see how many unique users your app had last month, what percentage of them used it more than once, how long did they use your app in a single session, which country your users came from and more:

GA_mt_screen1

By tracking events in your app like users pressing various buttons or performing different actions you can have insights on whether one or other feature is more used and plan how to develop your app further. For example on the report below you can clearly see that users prefer saving photos to their device (2,223 times last month) vs. sharing straight to Facebook (113 times) and posting to Twitter (33 times):

GA_mt_screen2

Lap 1: Setting Up Google Analytics for Your App

Okay, that might have felt like a false start, but now you are off and running. Go to www.google.com/analytics. The homepage should appear like so:

1 Sign In

Note: If you do not have a Google account, you will need to create one now by selecting Create an Account on the top-right side of the page. For the remainder of this tutorial we are going to assume you have create your own Google Analytics account.

Once you have logged in, you simply need to click the Access Google Analytics button on the top right.

Google Analytics Existing Users

The first screen you see lists all of your accounts. If you have clients, typically you will have one account set up for each. If you are new to Google Analytics you will only have the first account you set up.

Google Analytics - Existing Accounts

Select the Admin view on the top right. To see the main dashboard area for managing your Accounts, Properties, and Views.

Google Analytic Dashboard

On the main dashboard you are presented with a workflow that starts with Account, progresses to Property and ends in View (Profile). Lets first break down what each of these sections refer to.

  • Account – It can seem confusing however, within your own Google Analytics Account you can be a member of multiple analytics accounts owned by clients or other collaborators. In the account drop down list on the right-hand column you will see a list of all of the accounts you are a member of.
  • Property – A property refers to a particular website or application that belongs to a given account. In this tutorial you will be creating a new Clock property to house all of the data associated with your app.
  • View – The final column is the view column. This enables you to create different views on your data. By creating different views on a single property (in this tutorial’s case an app) you are able to monitor data from different versions of your app which you will look at later on in the tutorial.

Ok lets go! Ensure that you have the correct Account selected for this project. You don’t want to add a new property to one of your clients’ accounts.

Select the Property drop-down and click on the Create new property item, like so:

Google Anaylitcs New Property

Google then asks you to submit the details for the app you want to track. Make sure to choose Mobile app at the top and enter the information as required. In the Setting up your property section, enter Clock for the App Name.

Google Analytics New Property

After accepting the terms of service, the website takes you to a page with your tracking ID and the download link for the SDK.

Downloading the Google Analytics iOS SDK

Before you do anything else, write down your app’s Tracking ID, which will be of the form ID UA-XXXXXXXX-Y. Keep the ID handy, because you will need it soon.

Now download the Google Analytics iOS SDK presented on the final summary page. Save this somewhere handy, such as your desktop, so you don’t have to rummage around trying to find it later. Download the version without Admob features.

Google Analytics ID & SDK

Lap 2: Setting Up the Google Analytics SDK

To begin this lap, please download the starter project.

Open the starter project in Xcode and build and run it in the Simulator to verify it is working. You should see a tab-bar-based app with a clock and a stopwatch.

Clock Starter Project Initial Screen

Unzip the Google Analytics SDK you downloaded when creating your tracking ID. You will see the following:

  • GoogleAnalytics
  • GoogleTagManager
  • libGoogleAnalyticsServices.a
  • Readme.txt

Inside the GoogleAnalytics folder, open the Library folder, select the following files and drag them into the Clock group within your starter project.

  • GAI.h
  • GAITracker.h
  • GAITrackedViewController.h
  • GAIDictionaryBuilder.h
  • GAIFields.h
  • GAILogger.h

Google Analytics Add Header Files

Be sure to select the checkboxes: Copy items into destination group’s folder and Add the files to the Clock target.

Now you need to add the Google Anayltics iOS SDK that is named libGoogleAnalyticsServices.a. The Google Analytics SDK also uses the CoreData and SystemConfiguration frameworks (at the moment you have to add these frameworks manually, in the next version hopefully Google will use automatic importing in the next version), so you need to add all of the following to the app’s libraries:

  • libGoogleAnalyticsServices.a
  • CoreData.framework
  • SystemConfiguration.framework
  • libz.dylib

Begin by selecting libGoogleAnalyticsServices.a and dragging it into your Frameworks folder. You can place it anywhere inside your project, but it’s best to keep all of your frameworks in one place.

Google Analytics Adding GA Lib

Next add the CoreData framework. Select the Clock project file in the Project Navigator and then select Build Phases. Expand the Link Binary With Libraries section and click the add (+) button.

Google Analytics Adding Libraries

In the pop-up window, type in CoreData to refine the search and select CoreData.framework from the filtered list.

Google Analytics Add Libraries

Repeat the previous steps to find and add both SystemConfiguration.framework and the libz.dylib library.

Finally, modify Clock-Prefix.pch to import the GAI.h header, as follows:

#import "GAI.h"
#import "GAIFields.h"

You’ve equipped your app with the SDK. Let’s get tracking!

Lap 3: Tracking

As you come around the first bend, you are ahead of all the other runners. Keep up the pace!

Running_for_screen_views

GAI (Google Analytics for iOS) is a top-level class that provides facilities to create trackers and set behavioural flags. This class provides access to a shared instance for convenience, in turn this exposes a default tracker instance. The default tracker is initialized to nil and is set by calling trackerWithTrackingId on the shared GAI instance. Should you wish to change this tracker you can override it as required, this is covered later.

The Google Analytics Tracker is used to track screens, events, transitions, timing, and exceptions. The implementation of this interface is thread-safe, and no calls are expected to block or take a long time. All network and disk activity will take place in the background.

So lets see the tracker in action!

Initializing the Tracker

You will need the tracking ID that you obtained in the first part of the tutorial. First you are going to get the Google Analytics for iOS shared instance and setup a number of behavioural flags along with the tracker. The specific behavioural flags you are going to set are:

  • trackUncaughtExceptions – Tracking uncaught exceptions will flag up any exceptions that you are not dealing with that have caused your application to crash.
  • logLevel – Google Analytics iOS SDK has 4 logging levels: kGAILogLevelError, kGAILogLevelWarning, kGAILogLevelInfo, and kGAILogLevelVerbose. Verbose logging enables all of the various types of log output and prints it to the console in Xcode. This is extremely useful when you first start using Google Analytics for iOS as it lets you see what is going on under the hood.
  • dispatchInterval – By default, this is set to 120, which states that tracking information should be dispatched (uploaded to Google Analytics) automatically every 120 seconds. In this tutorial you will set this to a shorter time period so that you can see the data in your Google Analytics dashboard without having to wait for a prolonged period of time. In a production environment every 120 seconds should be often enough.

To initialize the tracker in your GAI’s sharedInstance, open the app delegate and insert the following into application:didFinishLaunchingWithOptions:, using your own tracking ID in section 4:

// 1
[GAI sharedInstance].trackUncaughtExceptions = YES;
 
// 2
[[GAI sharedInstance].logger setLogLevel:kGAILogLevelVerbose];
 
// 3
[GAI sharedInstance].dispatchInterval = 20;
 
// 4
id<GAITracker> tracker = [[GAI sharedInstance] trackerWithTrackingId:@"UA-XXXXXXX-Y"];

This code will configure and get an instance of your tracker class, so you can start tracking app usage and app events.

Note: Remember to use the tracking ID you obtained when you set up Google Analytics for this app.

Automatic Screen Tracking

Google Analytics was originally built for websites, where it is used to measure pageviews—that is, instances in time where some user views a distinct page on a website. In Google Analytics for mobile, the analog to a webpage is a screen. This will usually represent a single screen of content in your app, but the term can also more generally describe any distinct piece of content your app presents, such as a single component within the screen.

Similarly, the analog to a pageview is a screen view, which represents the act of a user viewing that screen of your app. It’s easy to confuse this sense of “view” with the usual sense of the term in iOS development, where it means a visible UI component within an app rather than the act of a user looking at a part of the app. So stay on your toes when browsing the Google Analytics documentation.

Given all of this, automatic screen tracking means gauging how long users spend looking at the different screens of your app. When users navigate between screens, you will want to record those navigations events, along with the time spent on each screen. In iOS, every screen is managed by a view controller, so you record this information about a screen by updating its view controller.

You can do this automatically by extending GAITrackedViewController. The only manual step required is to set GAITrackedViewController‘s internal property, screenName, which defines the name of the screen for the analytics log.

Let’s use the automatic way to add analytics logging to the screen that displays a clock, which is handled by ClockViewController. First, update ClockViewController.h by importing GAITrackedViewController and extending it as follows:

#import "GAITrackedViewController.h"
 
@interface ClockViewController : GAITrackedViewController
 
@end

Second, update ClockViewController.m to set the screenName property. It’s best to do this in viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.screenName = @"Clock";
}

That’s it! These two small changes allow a lot of things under the hood to just work. Now that the view controller has a name for the associated screen this name will be used within the parent GAITrackedViewController viewWillAppear: method to register a screen view. Within this method a lot of things are at work the parent viewController will:

  1. Get the shared instances’ Default Tracker setup with your tracking ID.
  2. Tell the tracker that it’s tracking the “Clock” screen.
  3. Generate a logging entry containing the time and other information.
  4. Use the Default Tracker object to publish that to the Google Analytics servers for visual exploration. Since we have configured the logging level to output verbose logging you will also see this output to the console.

Not bad for just a couple of lines! Your app is already sending usage data to the Google Analytics servers, but hold your horses just a moment before heading to the GA Dashboard. Let’s add a bit more functionality before checking the results.

Manual Screen Tracking

While automatically recording screen views can be convenient, it is not always possible or desirable to extend the built-in GAITrackedViewController.

In this case, there is a manual approach. You can send information about a screen to Google by explicitly going through the steps that were performed above automatically. You need to get the tracker object, set the field values representing the screen on the tracker object, and then manually build and send the screen view event, or “hit.”

Let’s instrument the StopWatchViewController with this approach. Open StopWatchViewController.m and import GAIDictionaryBuilder at the top of the file:

#import GAIDictionaryBuilder.h

Unlike before, you have to get a handle to the default tracker and give it an identifier for the screen you are currently tracking. You then construct an app view record, that will cause the tracker to record the screen has been viewed and send that to your property on Google Analytics. In the same file, add viewDidAppear as follows:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
 
    id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
    [tracker set:kGAIScreenName value:@"Stopwatch"];
    [tracker send:[[GAIDictionaryBuilder createAppView] build]];
}

Build and run the app! Spend a few moments switching between the two tabs. Every time you switch tabs, you are viewing a new screen. This screen view produces a “hit” event, which the app logs out to the debug console as a timestamp and a dictionary of descriptive fields. You will see below our Stopwatch screen has been recorded with an appview event.

Verbose Console Showing Screen View

From time to time, the debug console will also show you calls to instances of GAIBatchingDispatcher. This is the Google Analytics library automatically sending the log messages to the Google Analytics web service. After you’ve seen those messages in the console, you should be able to see your activity on the Google Analytics website. If you navigate to the real-time area in your properties reporting dashboard you can see how many users are on each screen at any one time and how that changes in real-time.

Real-Time Screen Usage

Note: Pay attention to the date range you are previewing in Google Analytics, it does not include today by default and you will not see the hits you produce until you include today’s date in the range.

Google Analytics provides various views into your data. A good one to try is Standard Reports->Real-Time->Screens, which will show you screen transition events over the last few minutes.

Lap 4: Advanced Configuration

Now that you are on the home straight lets look at recording finer grained interactions such as button presses and the use of multiple trackers.

Setting and Sending Data

You can send specific data to Google Analytics if you want to record finer-grained user interactions. These can range from button presses to implicitly triggered events such as network calls. You can send this data by first setting maps of key-value pairs on the tracker and then sending them to Google using the methods set: and send:, respectively. You already saw an example of this in the previous section.

Recap: In viewDidAppear, you set the screenName and make a request to the tracker to send the information to Google Analytics.

id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAIScreenName value:@"Stopwatch"];
[tracker send:[[GAIDictionaryBuilder createAppView] build]];

Applying Values to Multiple Measuring Events

To capture tap events, you can specify the kGAIHitType in the events parameters. Let’s add a method to log button presses on the Stopwatch screen. You will call this method in the IBActions associated with each of the Stopwatch buttons.

First, add this method to StopWatchViewController.m:

-(void)logButtonPress:(UIButton *)button{
 
     id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
 
    [tracker set:kGAIScreenName value:@"Stopwatch"];
    [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"UX"
                                                          action:@"touch"
                                                           label:[button.titleLabel text]
                                                           value:nil] build]];
    [tracker set:kGAIScreenName value:nil];
}

First you get a handle of the default tracker. As before, you need to set the current screenName on the tracker to ensure the button press is recorded for the correct screen.

However, you’re not recording the act of the user viewing the screen. So instead of sending a screen view, you create a touch event, which is categorized as a UX event in Google’s framework. You send the title of the button as the title of the event. Finally, you remove the screenName set on the tracker to prevent other events from being associated with the wrong screen.

In other words, the tracker object is not just a transparent conduit for passing messages. It carries state, which affects the messages you send. The only values that you should set directly on the tracker are those you want to persist across multiple hits.

Setting a screenName on a tracker makes sense, since you can apply the same value to subsequent screen views and event hits. However, I don’t recommend that you set a field like hit type on the tracker, because it will likely change with each hit.

Call this method in both the startToggle and reset methods to record when a user presses these buttons:

-(IBAction)startToggle:(id)sender{
   [self logButtonPress:(UIButton *)sender];
   ...
}
 
-(IBAction)reset:(id)sender{
   [self logButtonPress:(UIButton *)sender];
   ...
}

Build and run the app. Switch to the Stopwatch page, toggle the stopwatch on and off a few times and then hit the reset button to record some data.

To view this information, go back to Google Analytics and navigate to your app’s statistics. On the left side of the page, expand Behavior->Events->Overview and you will see the events you just initiated.

Google Analytics Events

Using Multiple Trackers

It is possible to use multiple trackers in your app. This can be useful for sending data to different Google Analytics properties associated with the same app. For example, maybe you have one property set up for your marketing team, who require information about how the app is being used, and another set up for your development team, who are looking at problem areas.

Note: In this tutorial you are not going to add multiple trackers to your example. This section just gives you an idea how to implement this in case you need multiple trackers.

Setting up multiple trackers is as simple as initializing trackers with different property IDs:

id<GA Tracker> tracker1 = [[GAI sharedInstance] trackerWithTrackingId:@"UA-XXXX-1"];
 
id<GAITracker> tracker2 = [[GAI sharedInstance] trackerWithName:@"Tracker2"
                                                     trackingId:@"UA-XXXX-2"];

By default, the first tracker initialized becomes the default tracker made available through the GAI shared instance. You have seen how to access this perviously.

id<GA Tracker> tracker = [[GAI sharedInstance] defaultTracker];

If you consider the previous two code blocks together the tracker returned as the default tracker associated with GAI would refer to tracker1. If you want to change the default tracker you can override it like so:

[[GAI sharedInstance] setDefaultTracker:tracker2];

In this case every subsequent call to the default tracker will access tracker2.

Sampling Rate: Avoiding Inconsistencies

Sampling in Google Analytics or in any analytics software refers to the practice of selecting a subset of data from your data set and reporting on the trends available in that sample set. Sampling is widely used in statistical analysis because analyzing a subset of data gives similar results to analyzing all of the data. Sampling speeds up processing for reports when the volume of data is so large as to slow down report queries. This is important if your user-base is extremely large.

Earlier you covered three key terms, Accounts, Properties, and Views. When creating a view on a particular property (think of this as a window into your app’s usage from a given perspective) you must ensure that the data being sent has been sampled at the same rate. If this is not the case, data that is sampled at a higher frequency (events logged more often) will overshadow those sampled at a lower frequency (events logged less often) which can skew your analysis.

To ensure that your tracker is sampling data consistently you should maintain a constant sample rate for each version of the app you release. Updating the tracker with the current version number is easy: add the following code to your AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...
 
    NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
    [tracker set:kGAIAppVersion value:version];
    [tracker set:kGAISampleRate value:@"50.0"];
}

This code will fetch the current app version from your Xcode Info.plist file automatically and set it to the tracker object. It then sets a 50% sampling rate.

Now change the version number of your app to 1.2 in the project settings.

Project Version Number Update

When you run the application the tracker will reflect this change and record all data with the correct version number. The final step is to create a view in Google Analytics to filter the data by version number.

Go back to your Google Analytics dashboard and on the right-hand view drop down choose create new view.

Create a new view on your dashboard

Be sure to to select mobile app and create a name for this new view, here I am using Clock – v1.2.

Complete the settings for the new view

This will successfully create a new view and as next step you have to set a filter to specify what data you want that view to show.

Successfully creating a view

Choose the filters option below the view drop down and choose new filter (red button that comes in).

New filter page

Apply the appropriate filter settings as shown below. You are filtering the data by app version and in this case you are only looking at data from version 1.2.

Filter Settings

Now Build and Run the iOS app and watch the data stream in on your newly created view by selecting Reporting->Real-Time->Screens

Real-time view

Opting Out

Last but not least: you can allow your users to opt-out of anonymous usage logging. You can enable an app-level opt-out that will disable Google Analytics across the entire app by setting an optOut property on the tracker object, like so:

[[GAI sharedInstance] setOptOut:YES];

For your clock app, you are going to ask users to opt in or out on launch. First we will create an alert view and show it inside the AppDelegate when the application finished launching. To correctly handle the alert view you must ensure your AppDelegate implements the UIAlertViewDelegate. In your app delegate, SSAppDelegate.h, add the following code:

#import <UIKit/UIKit.h>
 
@interface SSAppDelegate : UIResponder <UIApplicationDelegate, UIAlertViewDelegate>
 
@property (strong, nonatomic) UIWindow *window;
 
@end

Then in your app delegates methods file SSAppDelegate.m add:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
     ...
 
    UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Google Analytics" message:@"With your permission usage information will be collected to improve the application." delegate:self cancelButtonTitle:@"Opt Out" otherButtonTitles:@"Opt In", nil];
    [av show];
 
    return YES;
}
 
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    switch (buttonIndex) {
        case 0:
            [[GAI sharedInstance] setOptOut:YES];
            break;
        case 1:
            [[GAI sharedInstance] setOptOut:NO];
            break;
 
        default:
            break;
    }
}

This will display the alert view once the application has finished launching giving the user two options:

  1. Opt into logging in which case you will see all of their data on Google Analytics
  2. Opt out of logging in which case nothing will be sent back

Build and run one last time. The app will present you with the choice of opting in or out of usage logging. Be sure not to stab yourself in the back by Opting out of usage tracking while still testing the clock app :]

Opt Out

Where to Go From Here?

You’ve crossed the finish line for this tutorial! I hope what you’ve learned will help you make best use of Google Analytics in your mobile apps. You can download the complete clock project here.

You can do much more once you have data coming in to your Google Analytics account:

  • Experiment with the reports provided for you – you can customize what data is plotted and create more complex reports by crossing a number of tracking parameters
  • Check out the report Behavior / Behaviour flow – it shows you the sequence of how “hits” happened. Effectively if you are conducting an A/B test for a given screen you can check whether the A or B case produces more conversions to your next screen
  • Let your imagination go wild – don’t consider Google Analytics to be just a business tool and try to figure out how it can be useful for you. For example if you are developing a quest game – let GA report every time your player finds an object in the game. This way you’ll figure out if some objects are too hard or too easy to discover on your map! Ka-jing!

If you have any questions, comments or suggestions, feel free to join the discussion below!

Google Analytics for iOS is a post from: Ray Wenderlich

The post Google Analytics for iOS appeared first on Ray Wenderlich.

iOS 6 by Tutorials Second Edition Now Available!

$
0
0
iOS 6 by Tutorials Second Edition Now Available!

iOS 6 by Tutorials Second Edition Now Available!

This is a quick announcement to let you know some good news – the second edition of iOS 6 by Tutorials is now available!

In this new second edition, all of the tutorials in the book have been fully updated to iOS 7 and Xcode 5.

In addition to making sure everything still works in iOS 7, we added notes and information about relevant iOS 7 changes where appropriate. Some chapters required substantial rework due to major changes in iOS 7, such as the Auto Layout and In-App Purchases chapters.

And the best news of all – this second edition is completely free of charge to existing iOS 6 by Tutorials customers, to thank you for supporting this site. You can download the new second edition on your My Loot page.

If you don’t have iOS 6 by Tutorials and want to learn about some cool APIs like Auto Layout, Collection Views, or Passbook – now’s a good time to grab a copy!

We hope you enjoy the new second edition!

iOS 6 by Tutorials Second Edition Now Available! is a post from: Ray Wenderlich

The post iOS 6 by Tutorials Second Edition Now Available! appeared first on Ray Wenderlich.

iOS 7 Best Practices; A Weather App Case Study: Part 1/2

$
0
0
Finished Weather App

Finished Weather App

Every developer has their own ideas as to the best way to create top-notch iOS apps. Some developers take advantage of Auto-Layout, some like to create all of their graphics in code, and some developers even like to code in Vim!

With the recent release of iOS 7 and Xcode 5, I thought it would be a great time to provide a case study using a unique set of approaches and tools to create a basic weather app; you can look upon these as my recommended iOS 7 best practices. The first stepping-stone to iOS development used to be building a Todo List app. However, as iOS has matured new iOS developers are expected to be proficient in modern techniques such as data management and consuming web requests.

In this 2-part tutorial series, you’ll explore how to create your own apps using the following tools and techniques:

This tutorial is designed for intermediate-level developers who know the basics, but haven’t moved into too many advanced topics. This tutorial is also a great start for anyone wanting to explore Functional Programming in Objective-C.

Getting Started

Fire up Xcode and go to File\New\Project. Select Application\Empty Application. Name your project SimpleWeather, click Next, select a directory to save your project, and then click Create.

You now have your base project set up. The next step is to set up your third-party tools — but first make sure you close Xcode so it won’t interfere with the next steps.

Cocoapods

You’ll be using Cocoapods to take care of downloading the code, adding files to your Xcode project, and configuring any project settings that those projects require. Let’s first cover what projects you’ll be using.

Mantle

Mantle is a project by the Github team that helps remove all of the boilerplate code that Objective-C requires to turn JSON data into NSObject subclasses. Mantle also does value transformation, which is a fancy way to turn JSON primitive values (strings, ints, floats) into complex values like NSDate, NSURL, or even custom classes.

LBBlurredImage

LBBlurredImage is a simple project that extends UIImageView and makes blurring images a breeze. You’ll be creating a fancy blur with just a single line of code. If you’re interested in seeing how the blur works, check out the source code.

TSMessages

TSMessages is another wonderfully simple library that takes care of displaying overlay alerts and notifications. When presenting error messages that don’t directly impact the user, it’s better to present an overlay instead of a modal view (like UIAlertView) so that you irritate the user as little as possible.

You’ll use TSMessages when a network connection is lost or the API encounters some other error. If something were to go wrong, you’d see an overlay like the one below:

TSMessages Error

ReactiveCocoa

The last library you’ll use is ReactiveCocoa, also made by the Github team. ReactiveCocoa brings functional programming to Objective-C by following patterns introduced by Reactive Extensions in .NET. You’ll be spending a good portion of your time in Part 2 implementing ReactiveCocoa.

Setting Up Your Cocoapods Libraries

To set up your Cocoapods libraries, first ensure that you have Cocoapods installed. To do that, open the Terminal application, type the following, and hit Enter.

which pod

You should see something similar to the following output:

/usr/bin/pod

Depending on how you manage your Ruby gems, for instance if you use rbenv or RVM, then your path may differ.

If the terminal simply returns to the prompt, or states pod not found, Cocoapods isn’t installed on your machine; check out our tutorial on Cocoapods for installation instructions. It’s also a great resource if you just want to learn more about Cocoapods.

Podfiles are used to tell Cocoapods which Pods, or open-source projects, to import.

To create your first Cocoapod, first use the cd command in Terminal to navigate to the the folder where you saved your Xcode project. Launch the Pico editor by entering the pico command in Terminal.

Once pico has opened, paste in the following lines:

platform :ios, '7.0'
 
pod 'Mantle'
pod 'LBBlurredImage'
pod 'TSMessages'
pod 'ReactiveCocoa'

This file does two things:

  • It tells Cocoapods which platform and which version you’re targeting. Here you’re targeting iOS 7.0.
  • It gives Cocoapods a list of all the projects you want to import and set up.

To save your file, press Control-O, name the file Podfile and hit Enter. Now press Control-X to exit Pico.

To install the four Pods noted in your Podfile, type the following into Terminal and hit Enter.

pod install

Be patient — it could take a minute or two for pod to finish installing the various packages. Your Terminal output should look like the following:

You should see output like this:

$ pod install
Analyzing dependencies
 
CocoaPods 0.28.0 is available.
 
Downloading dependencies
Installing HexColors (2.2.1)
Installing LBBlurredImage (0.1.0)
Installing Mantle (1.3.1)
 
Installing ReactiveCocoa (2.1.7)
Installing TSMessages (0.9.4)
Generating Pods project
Integrating client project
 
[!] From now on use `SimpleWeather.xcworkspace`.

Cocoapods will create a bunch of new files in your project directory; however, the only one that you’ll be concerned about is SimpleWeather.xcworkspace.

Open SimpleWeather.xcworkspace in Xcode. Take a look at your project setup; there’s now a Pods project in the workspace, as well as folders for each of the libraries you imported in the Pods folder, like so:

Cocoapods Project

Make sure you have the SimpleWeather project selected, as shown below:

Select SimpleWeather Project

Build and run your app to make sure everything is working properly:

Blank App

It doesn’t look like much right now, but you’ll add some content shortly.

Note: You might notice a few projects have build warnings. Projects imported with Cocoapods are made by many different developers, and different developers have different tolerances for build warnings. Most of the time you should be able to ignore them. Just make sure there are no compiler errors!

Creating Your Main View Controller

Although the app looks complex, it will be powered by a single view controller. You’ll add that now.

With the SimpleWeather project selected, click File\New\File and select Cocoa Touch\Objective-C class. Name your class WXController and make it a subclass of UIViewController.

Make sure that both Targeted for iPad and With XIB for user interface are both unchecked, as shown below:

Create WXController

Open WXController.m and replace the boilerplate -viewDidLoad method with the following:

- (void)viewDidLoad {
    [super viewDidLoad];
 
    // Remove this later
    self.view.backgroundColor = [UIColor redColor];
}

Now open AppDelegate.m and import the following two classes:

#import "WXController.h"
#import <TSMessage.h>

Sharp-eyed readers will note that WXController is imported with quotes, while TSMessage is imported with angle brackets. What gives?

Look back to when you created the Podfile; you imported TSMessage with Cocoapods. Cocoapods created the TSMessage Pod project and added it to your workspace. Since you’re importing from other projects in the workspace, you use angle brackets instead of quotes.

Replace the contents of -application:didFinishLaunchingWithOptions: with:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // 1
    self.window.rootViewController = [[WXController alloc] init];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    // 2
    [TSMessage setDefaultViewController: self.window.rootViewController];
    return YES;
}

Walking through the numbered comments, you’ll see the following:

  1. Initialize and set the WXController instance as the application’s root view controller. Usually this controller is a UINavigationController or UITabBarController, but in this case you’re using a single instance of WXController.
  2. Set the default view controller to display your TSMessages. By doing this, you won’t need to manually specify which controller to use to display alerts.

Build and run to see your new view controller in action:

WXController

The status bar is a little difficult to read against the red background. Fortunately, there’s an easy way to make the status bar a lot more legible.

There’s a new API in UIViewController in iOS 7 to control the appearance of the status bar. Open WXController and add the following code directly below -viewDidLoad:

- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}

Build and run again and you’ll see the following change to the status bar:

Create WXController with Light Status Bar

Setting Up Your App’s Views

It’s time to make your app come to life. Download the images for this project here and unzip them to a convenient location. This package contains a background image by Flickr user idleformat and weather icons by Dribbble user heeyeun.

Note: The background image is of San Francisco since the iPhone Simulator defaults its geolocation to that city. If you want to personalize your app, feel free to substitute an image of your own hometown — or any image you choose, for that matter.

Switch back back to Xcode and click File\Add Files to “SimpleWeather”…. Locate the Images folder you just unzipped and select it. Check the Copy items into destination group’s folder (if needed) option and click Add.

Open WXController.h and add the following delegate protocols directly below the @interface line:

<UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>

Now open WXController.m. Protip: you can use the hotkey Control-Command-Up to quickly toggle between the .h and .m files.

Add the following import to the top of WXController.m:

#import <LBBlurredImage/UIImageView+LBBlurredImage.h>

LBBlurredImage.h is contained in the LBBlurredImage project you imported with Cocoapods; you’ll use this library to blur your background image.

There should be an empty private interface boilerplate for WXController beneath the imports. Fill it in with the following properties:

@interface WXController ()
 
@property (nonatomic, strong) UIImageView *backgroundImageView;
@property (nonatomic, strong) UIImageView *blurredImageView;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign) CGFloat screenHeight;
 
@end

Now it’s time to create and set the views in your project. “But wait,” you say. “Where are the IBOutlets?”

Be forewarned: all of the views are created and set up… in code! :]

Table Flip

Hang on — don’t freak out just yet. There are lots of ways to create views, and everyone has their favorite. We even held a debate on the RayWenderlich.com team on the different styles and what each person prefers. Storyboards, NIBs, and code all have their pros and cons.

The views in this app aren’t terribly complicated and don’t necessarily have the performance hit that Auto-Layout can cause. However, since this is a case study, you’ll stick to using code.

You’ll create three views and stack them on top of each other to create the effect that you saw in the GIF at the beginning of this tutorial. Here’s an exploded view of what you’ll make, bearing in mind that the table view will be transparent:

Exploded Screens

To provide the dynamic blur effect, you’ll change the alpha of the blurred image as you scroll through your app.

Open up WXController.m and replace the code in -viewDidLoad that sets the background color with the following code:

// 1
self.screenHeight = [UIScreen mainScreen].bounds.size.height;
 
UIImage *background = [UIImage imageNamed:@"bg"];
 
// 2
self.backgroundImageView = [[UIImageView alloc] initWithImage:background];
self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:self.backgroundImageView];
 
// 3
self.blurredImageView = [[UIImageView alloc] init];
self.blurredImageView.contentMode = UIViewContentModeScaleAspectFill;
self.blurredImageView.alpha = 0;
[self.blurredImageView setImageToBlur:background blurRadius:10 completionBlock:nil];
[self.view addSubview:self.blurredImageView];
 
// 4
self.tableView = [[UITableView alloc] init];
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.2];
self.tableView.pagingEnabled = YES;
[self.view addSubview:self.tableView];

This is pretty straightforward code:

  1. Get and store the screen height. You’ll need this later when displaying all of the weather data in a paged manner.
  2. Create a static image background and add it to the view.
  3. Create a blurred background image using LBBlurredImage, and set the alpha to 0 initially so that backgroundImageView is visible at first.
  4. Create a tableview that will handle all the data presentation. WXController will be the delegate and data source, as well as the scroll view delegate. Note that you’re also setting pagingEnabled to YES.

Add the following code for the UITableView delegate and data source to the @implementation block of WXController.m:

// 1
#pragma mark - UITableViewDataSource
 
// 2
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // TODO: Return count of forecast
    return 0;
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 
    if (! cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
    }
 
    // 3
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2];
    cell.textLabel.textColor = [UIColor whiteColor];
    cell.detailTextLabel.textColor = [UIColor whiteColor];
 
    // TODO: Setup the cell
 
    return cell;
}
 
#pragma mark - UITableViewDelegate
 
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // TODO: Determine cell height based on screen
    return 44;
}

Although some of the above code is placeholders, here’s what you have so far:

  1. Pragma marks are a great way to help organize your code..
  2. Your table view has two sections, one for hourly forecasts and one for daily. You always return 2 for the number of table view sections.
  3. Forecast cells shouldn’t be selectable. Give them a semi-transparent black background and white text.
Note: Using the formatted comment // TODO: helps Xcode find code you need to complete later. You can even view TODO items using Show Document Items (Control-6).

Finally, add the following method to WXController.m:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
 
    CGRect bounds = self.view.bounds;
 
    self.backgroundImageView.frame = bounds;
    self.blurredImageView.frame = bounds;
    self.tableView.frame = bounds;
}

Your view controller calls the above method in order to lay out its subviews in WXController.m.

Build and run your app to see how your views stack up — (pardon the pun!) :

Image Background

Look closely and you’ll see the individual cell separators for all of your empty table cells.

Still in -viewDidLoad, add the following code to set up your layout frames and margins:

// 1
CGRect headerFrame = [UIScreen mainScreen].bounds;
// 2
CGFloat inset = 20;
// 3
CGFloat temperatureHeight = 110;
CGFloat hiloHeight = 40;
CGFloat iconHeight = 30;
// 4
CGRect hiloFrame = CGRectMake(inset, 
                              headerFrame.size.height - hiloHeight,
                              headerFrame.size.width - (2 * inset),
                              hiloHeight);
 
CGRect temperatureFrame = CGRectMake(inset, 
                                     headerFrame.size.height - (temperatureHeight + hiloHeight),
                                     headerFrame.size.width - (2 * inset),
                                     temperatureHeight);
 
CGRect iconFrame = CGRectMake(inset, 
                              temperatureFrame.origin.y - iconHeight, 
                              iconHeight, 
                              iconHeight);
// 5
CGRect conditionsFrame = iconFrame;
conditionsFrame.size.width = self.view.bounds.size.width - (((2 * inset) + iconHeight) + 10);
conditionsFrame.origin.x = iconFrame.origin.x + (iconHeight + 10);

This is fairly routine setup code, but here’s what’s going on:

  1. Set the header of your table to be the same size of your screen. You’ll be taking advantage of UITableView’s paging which will page the header and the daily and hourly forecast sections.
  2. Create an inset (or padding) variable so that all your labels are evenly spaced and centered.
  3. Create and initialize the height variables for your various views. Setting these values as constants makes it easy to configure and changing your view setup if required.
  4. Create frames for your labels and icon view based on the constant and inset variables.
  5. Copy the icon frame, adjust it so the text has some room to expand, and move it to the right of the icon. You’ll see how this layout math works once we add the label to the view below.

Add the following code below the frame code in -viewDidLoad:

// 1
UIView *header = [[UIView alloc] initWithFrame:headerFrame];
header.backgroundColor = [UIColor clearColor];
self.tableView.tableHeaderView = header;
 
// 2
// bottom left
UILabel *temperatureLabel = [[UILabel alloc] initWithFrame:temperatureFrame];
temperatureLabel.backgroundColor = [UIColor clearColor];
temperatureLabel.textColor = [UIColor whiteColor];
temperatureLabel.text = @"0°";
temperatureLabel.font = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:120];
[header addSubview:temperatureLabel];
 
// bottom left
UILabel *hiloLabel = [[UILabel alloc] initWithFrame:hiloFrame];
hiloLabel.backgroundColor = [UIColor clearColor];
hiloLabel.textColor = [UIColor whiteColor];
hiloLabel.text = @"0° / 0°";
hiloLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:28];
[header addSubview:hiloLabel];
 
// top
UILabel *cityLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, 30)];
cityLabel.backgroundColor = [UIColor clearColor];
cityLabel.textColor = [UIColor whiteColor];
cityLabel.text = @"Loading...";
cityLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
cityLabel.textAlignment = NSTextAlignmentCenter;
[header addSubview:cityLabel];
 
UILabel *conditionsLabel = [[UILabel alloc] initWithFrame:conditionsFrame];
conditionsLabel.backgroundColor = [UIColor clearColor];
conditionsLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
conditionsLabel.textColor = [UIColor whiteColor];
[header addSubview:conditionsLabel];
 
// 3
// bottom left
UIImageView *iconView = [[UIImageView alloc] initWithFrame:iconFrame];
iconView.contentMode = UIViewContentModeScaleAspectFit;
iconView.backgroundColor = [UIColor clearColor];
[header addSubview:iconView];

That’s quite a chunk of code, but it’s really just doing the heavy lifting of setting up the various controls in your view. In short:

  1. Set the current-conditions view as your table header.
  2. Build each required label to display weather data.
  3. Add an image view for a weather icon.

Build and run your app; you should see all of your views laid out before you. The screenshot below shows all of the labels along with their frames for a visual on manual layout:

Labels and Views

Give the table a nudge with your finger; it should bounce when you scroll it.

Retrieving Weather Data

You’ll notice that the app says “Loading…“, but it’s not really doing anything. Time to get some real weather conditions.

You’re going to pull data from the OpenWeatherMap API. OpenWeatherMap is a really awesome service that aims to provide real-time, accurate, and free weather data to anyone. There are a lot of weather API’s out there, but most of them either use older data formats like XML, or they are paid services — and sometimes quite expensive.

You’ll follow the basic steps below to get weather data for your device’s location:

  1. Find the location of the device
  2. Download JSON data from an API endpoint
  3. Map the JSON to WXConditions and WXDailyForecasts
  4. Inform the UI that we have new data

Get started by creating your weather model and data management classes. Click File\New\File… and select Cocoa Touch\Objective-C class. Name your class WXClient and make it a subclass of NSObject.

Do this three more times to create the following classes:

  • WXManager as a subclass of NSObject
  • WXCondition as a subclass of MTLModel
  • WXDailyForecast as a subclass of WXCondition

All done? Now you can move onto the next section which deals with the mapping and transformation of your weather data.

Creating Your Weather Model

Your models will use Mantle which makes data mapping and value transformation a cinch.

Open up WXCondition.h and modify the interface so that it looks like the one below:

// 1
@interface WXCondition : MTLModel <MTLJSONSerializing>
 
// 2
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSNumber *humidity;
@property (nonatomic, strong) NSNumber *temperature;
@property (nonatomic, strong) NSNumber *tempHigh;
@property (nonatomic, strong) NSNumber *tempLow;
@property (nonatomic, strong) NSString *locationName;
@property (nonatomic, strong) NSDate *sunrise;
@property (nonatomic, strong) NSDate *sunset;
@property (nonatomic, strong) NSString *conditionDescription;
@property (nonatomic, strong) NSString *condition;
@property (nonatomic, strong) NSNumber *windBearing;
@property (nonatomic, strong) NSNumber *windSpeed;
@property (nonatomic, strong) NSString *icon;
 
// 3
- (NSString *)imageName;
 
@end

Again, this is a lot of setup code. Looking at the numbered comments, you’ll see the following:

  1. The MTLJSONSerializing protocol tells the Mantle serializer that this object has instructions on how to map JSON to Objective-C properties.
  2. These are all of your weather data properties. You’ll be using a couple of them, but its nice to have access to all of the data in the event that you want to extend your app down the road.
  3. This is simply a helper method to map weather conditions to image files.

Build and run your app and…it fails. Can you guess why? There’s a spoiler below if you need a little help.

Solution Inside: Solution SelectShow>

Build and run your app now; success. You’ll see a few new warnings, but you can ignore them for now.

First you’ll need to take care of the missing implementation of -imageName.

Open WXCondition.m and add the following methods:

+ (NSDictionary *)imageMap {
    // 1
    static NSDictionary *_imageMap = nil;
    if (! _imageMap) {
        // 2
        _imageMap = @{
                      @"01d" : @"weather-clear",
                      @"02d" : @"weather-few",
                      @"03d" : @"weather-few",
                      @"04d" : @"weather-broken",
                      @"09d" : @"weather-shower",
                      @"10d" : @"weather-rain",
                      @"11d" : @"weather-tstorm",
                      @"13d" : @"weather-snow",
                      @"50d" : @"weather-mist",
                      @"01n" : @"weather-moon",
                      @"02n" : @"weather-few-night",
                      @"03n" : @"weather-few-night",
                      @"04n" : @"weather-broken",
                      @"09n" : @"weather-shower",
                      @"10n" : @"weather-rain-night",
                      @"11n" : @"weather-tstorm",
                      @"13n" : @"weather-snow",
                      @"50n" : @"weather-mist",
                      };
    }
    return _imageMap;
}
 
// 3
- (NSString *)imageName {
    return [WXCondition imageMap][self.icon];
}

Here’s what the above code does:

  1. Create a static NSDictionary since every instance of WXCondition will use the same data mapper.
  2. Map the condition codes to an image file (e.g. “01d” to “weather-clear.png”).
  3. Declare the public message to get an image file name.
Note: You can find a list of all of the condition codes that OpenWeatherMap supports here.

Take a look at the following condensed sample JSON response from OpenWeatherMap:

{
    "dt": 1384279857,
    "id": 5391959,
    "main": {
        "humidity": 69,
        "pressure": 1025,
        "temp": 62.29,
        "temp_max": 69.01,
        "temp_min": 57.2
    },
    "name": "San Francisco",
    "weather": [
        {
            "description": "haze",
            "icon": "50d",
            "id": 721,
            "main": "Haze"
        }
    ]
}

You’ll need to map embedded JSON values to Objective-C properties. Embedded JSON values are elements such as temp, which is you can see above is a child of main.

To do this, you’ll take advantage of Objective-C’s Key-Value Coding and Mantle’s MTLJSONAdapter.

Still in WXCondition.m, setup your “JSON to model properties” mappings by adding the +JSONKeyPathsByPropertyKey method that the MTLJSONSerializing protocol requires.

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"date": @"dt",
             @"locationName": @"name",
             @"humidity": @"main.humidity",
             @"temperature": @"main.temp",
             @"tempHigh": @"main.temp_max",
             @"tempLow": @"main.temp_min",
             @"sunrise": @"sys.sunrise",
             @"sunset": @"sys.sunset",
             @"conditionDescription": @"weather.description",
             @"condition": @"weather.main",
             @"icon": @"weather.icon",
             @"windBearing": @"wind.deg",
             @"windSpeed": @"wind.speed"
             };
}

In this case, the dictionary key is WXCondition‘s property name, while the dictionary value is the keypath from the JSON.

You may have noticed that there’s a glaring issue with the way the JSON data is mapped to the Objective-C properties. The property date is of type NSDate, but the JSON has an NSInteger stored as Unix time. You’ll need to convert between the two somehow.

Mantle has just the feature to solve this problem for you: MTLValueTransformer. This class lets you declare a block detailing how to convert to and from a value.

The syntax for using Mantle’s transformers is a little strange. To create a transformer for a specific property, you add a class method that begins with the property name and ends with JSONTransformer.

It’s probably easier to see it in context than to try and explain it, so add the following transformers for NSDate properties to WXCondition.m.

+ (NSValueTransformer *)dateJSONTransformer {
    // 1
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [NSDate dateWithTimeIntervalSince1970:str.floatValue];
    } reverseBlock:^(NSDate *date) {
        return [NSString stringWithFormat:@"%f",[date timeIntervalSince1970]];
    }];
}
 
// 2
+ (NSValueTransformer *)sunriseJSONTransformer {
    return [self dateJSONTransformer];
}
 
+ (NSValueTransformer *)sunsetJSONTransformer {
    return [self dateJSONTransformer];
}

Here’s how the above transformers work:

  1. You return a MTLValueTransformer using blocks to transform values to and from Objective-C properties.
  2. You only need to detail how to convert between Unix time and NSDate once, so just reuse -dateJSONTransformer for sunrise and sunset.

This next value transformation is a little annoying, but it’s simply a consequence of using OpenWeatherMap’s API and the way they format their JSON responses. The weather key is a JSON array, but you”re only concerned about a single weather condition.

Using the same structure as -dateJSONTransformer in WXCondition.m, can you create a transformer between an NSArray and NSString? The solution is provided below if you can’t quite get it on your own.

Solution Inside: Solution SelectShow>

The final transformer is just a formality. OpenWeatherAPI uses meters-per-second for wind speed. Since your app uses the imperial system, you’ll need to convert this to miles-per-hour.

Add the following transformer method and macro definition to your implementation in WXCondition.m.

#define MPS_TO_MPH 2.23694f
 
+ (NSValueTransformer *)windSpeedJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSNumber *num) {
        return @(num.floatValue*MPS_TO_MPH);
    } reverseBlock:^(NSNumber *speed) {
        return @(speed.floatValue/MPS_TO_MPH);
    }];
}

There’s a small discrepancy with OpenWeatherMap’s API that you’ll have to deal with. Take a look at the temperature portion located between a current conditions response and a daily forecast response:

// current
"main": {
    "grnd_level": 1021.87,
    "humidity": 64,
    "pressure": 1021.87,
    "sea_level": 1030.6,
    "temp": 58.09,
    "temp_max": 58.09,
    "temp_min": 58.09
}
 
// daily forecast
"temp": {
    "day": 58.14,
    "eve": 58.14,
    "max": 58.14,
    "min": 57.18,
    "morn": 58.14,
    "night": 57.18
}

The condition’s first key is main and the high temperature is stored in the key temp_max, while the forecast’s first key is temp and the high is stored as max.

Temperature key differences aside, everything else is the same. So all you really need to do is change the key mapping for daily forecasts.

Open WXDailyForecast.m and override +JSONKeyPathsByPropertyKey as follows:

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    // 1
    NSMutableDictionary *paths = [[super JSONKeyPathsByPropertyKey] mutableCopy];
    // 2
    paths[@"tempHigh"] = @"temp.max";
    paths[@"tempLow"] = @"temp.min";
    // 3
    return paths;
}

Note that this will also override WXCondition’s method. Here’s what the above method does in a nutshell:

  1. Get WXCondition‘s map and create a mutable copy of it.
  2. Change the max and min key maps to what you’ll need for the daily forecast.
  3. Return the new mapping.

Build and run your app; there isn’t anything new to see since the last run, but this is a good spot to check that your app compiles and runs without any errors.

Labels and Views

Where To Go From Here?

Feel free to download a copy of the completed project up to this point.

In this part of the tutorial, you set up your project with Cocoapods, added views to the controller, laid out those views and built models to reflect the weather data you will be fetching. The app still isn’t fully functional, but you’ve survived creating views directly in code, and learned how to map and transform JSON data using Mantle.

Next check out part 2 of this tutorial, where you’ll flesh out your app to fetch data from the weather API and wire up your UI. You’ll be using the new iOS 7 NSURLSession to download data along with ReactiveCocoa to tie together the location finding, weather fetching, and UI updating events.

If you have any questions or comments, please share them with us in the forums!

iOS 7 Best Practices; A Weather App Case Study: Part 1/2 is a post from: Ray Wenderlich

The post iOS 7 Best Practices; A Weather App Case Study: Part 1/2 appeared first on Ray Wenderlich.

iOS 7 Best Practices; A Weather App Case Study: Part 2/2

$
0
0
Finished Weather App

Finished Weather App

In Part 1 of this tutorial series looking at iOS 7 best practices, you set up your project with Cocoapods, added views to the controller and laid them out, and finally built models to reflect the weather data you will be fetching.

In the second and final part of this tutorial series, you’ll fill out the rest of the app to fetch data from the weather API and then wire up the UI. You’ll be exposed to the world of Functional Programming with ReactiveCocoa and rely on it heavily for data fetching and UI updating events.

Getting Started

You have two choices for the starting point of this tutorial: you can either use your completed project from Part 1 of this tutorial, or you can download the completed project here.

You created the weather model for your app in the previous tutorial — now you need to fetch some data for your app using the OpenWeatherMap API. You’ll abstract the data fetching, parsing, and storing with two classes: WXClient and WXManager. You’re going to create the client first, then the manager.

WXClient‘s sole responsibility is to create API requests and parse them; someone else can worry about what to do with the data and how to store it. The design pattern of dividing different types of work between classes is called separation of concerns. This makes your code much easier to understand, extend, and maintain.

Working with ReactiveCocoa

Ensure you’re using the SimpleWeather.xcworkspace file, open WXClient.h and add the following imports:

@import CoreLocation;
#import <ReactiveCocoa/ReactiveCocoa/ReactiveCocoa.h>
Note: You may not have seen the @import directive before; it was introduced with Xcode 5 and is viewed by Apple as a modern, more efficient replacement to #import. There’s a great tutorial that covers the new features of Objective-C in What’s New in Objective-C and Foundation in iOS 7.

Add the following four public methods to the interface declaration in WXClient.h:

@import Foundation;
- (RACSignal *)fetchJSONFromURL:(NSURL *)url;
- (RACSignal *)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate;
- (RACSignal *)fetchHourlyForecastForLocation:(CLLocationCoordinate2D)coordinate;
- (RACSignal *)fetchDailyForecastForLocation:(CLLocationCoordinate2D)coordinate;

Yes, there might be one tiny thing that you don’t recognize in the code above:

RACSignal??

Now seems like a really good time to introduce ReactiveCocoa!

ReactiveCocoa (RAC) is an Objective-C framework for Functional Reactive Programming that provides APIs for composing and transforming streams of values. Instead of focusing on writing serial code — code that executes in an orderly sequence — your code can react to nondeterministic events.

Github provides a great overview of the benefits of ReactiveCocoa, namely :

  • The ability to compose operations on future data.
  • An approach to minimize state and mutability.
  • A declarative way to define behaviors and the relationships between properties.
  • A unified, high-level interface for asynchronous operations.
  • A lovely API built on top of KVO.

For example, you can observe changes on the username property of an object like so:

[RACAble(self.username) subscribeNext:^(NSString *newName) {
    NSLog(@"%@", newName);
}];

The subscribeNext block will be called whenever the value of self.username changes. The new value is passed to the block.

You can also combine signals and reduce their values into a single composite value. The following sample is taken from the Github page on ReactiveCocoa:

[[RACSignal 
    combineLatest:@[ RACAble(self.password), RACAble(self.passwordConfirmation) ] 
           reduce:^(NSString *currentPassword, NSString *currentConfirmPassword) {
               return [NSNumber numberWithBool:[currentConfirmPassword isEqualToString:currentPassword]];
           }] 
    subscribeNext:^(NSNumber *passwordsMatch) {
        self.createEnabled = [passwordsMatch boolValue];
    }];

The RACSignal object captures present and future values. Signals can be chained, combined, and reacted to by observers. A signal won’t actually perform any work until it is subscribed to.

That means calling [mySignal fetchCurrentConditionsForLocation:someLocation]; won’t do anything but create and return a signal. You’ll see how to subscribe and react later on.

Open WXClient.m and add the following imports:

#import "WXCondition.h"
#import "WXDailyForecast.h"

Under the import section, add the following private interface:

@interface WXClient ()
 
@property (nonatomic, strong) NSURLSession *session;
 
@end

This interface has a single property that manages the URL session for your API requests.

Add the following init method between @implementation and @end:

- (id)init {
    if (self = [super init]) {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:config];
    }
    return self;
}

This simply creates the session for you with defaultSessionConfiguration.

Note: If you haven’t encountered NSURLSession before, check out our NSURLSession tutorial to learn more.

Building the Signals

You’ll need a master method to build a signal to fetch data from a URL. You already know that three methods are required for fetching the current conditions, the hourly forecast and the daily forecast.

But instead of writing three separate methods, you can follow the DRY (Don’t Repeat Yourself) software design philosophy to make your code easy to maintain.

Some of the following ReactiveCocoa parts may look rather unfamiliar at first. Don’t worry, you’ll go through it piece by piece.

Add the following method to WXClient.m:

- (RACSignal *)fetchJSONFromURL:(NSURL *)url {
    NSLog(@"Fetching: %@",url.absoluteString);
 
    // 1
    return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 2
        NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            // TODO: Handle retrieved data
        }];
 
        // 3
        [dataTask resume];
 
        // 4
        return [RACDisposable disposableWithBlock:^{
            [dataTask cancel];
        }];
    }] doError:^(NSError *error) {
        // 5
        NSLog(@"%@",error);
    }];
}

Going through the commented sections one by one, you’ll see that the code does the following:

  1. Returns the signal. Remember that this will not execute until this signal is subscribed to. -fetchJSONFromURL: creates an object for other methods and objects to use; this behavior is sometimes called the factory pattern.
  2. Creates an NSURLSessionDataTask (also new to iOS 7) to fetch data from the URL. You’ll add the data parsing later.
  3. Starts the the network request once someone subscribes to the signal.
  4. Creates and returns an RACDisposable object which handles any cleanup when the signal when it is destroyed.
  5. Adds a “side effect” to log any errors that occur. Side effects don’t subscribe to the signal; rather, they return the signal to which they’re attached for method chaining. You’re simply adding a side effect that logs on error.
Note: If you feel like you need a little more background, check out this post by Ash Furrow to gain a better understanding of the core concepts of ReactiveCocoa.

Find the // TODO: Handle retrieved data line in -fetchJSONFromURL: and replace it with the following:

if (! error) {
    NSError *jsonError = nil;
    id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
    if (! jsonError) {
        // 1
        [subscriber sendNext:json];
    }
    else {
        // 2
        [subscriber sendError:jsonError];
    }
}
else {
    // 2
    [subscriber sendError:error];
}
 
// 3
[subscriber sendCompleted];

Here’s what’s happening in the code above in each numbered section:

  1. When JSON data exists and there are no errors, send the subscriber the JSON serialized as either an array or dictionary.
  2. If there is an error in either case, notify the subscriber.
  3. Whether the request passed or failed, let the subscriber know that the request has completed.

The -fetchJSONFromURL: method is a little lengthy, but it makes your specific API request methods quite simple in the end.

Fetching Current Conditions

Still working in WXClient.m, add the following method:

- (RACSignal *)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate {
    // 1
    NSString *urlString = [NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=imperial",coordinate.latitude, coordinate.longitude];
    NSURL *url = [NSURL URLWithString:urlString];
 
    // 2
    return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
        // 3
        return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:json error:nil];
    }];
}

Taking each comment in turn:

  1. Format the URL from a CLLocationCoordinate2D object using its latitude and longitude.
  2. Use the method you just built to create the signal. Since the returned value is a signal, you can call other ReactiveCocoa methods on it. Here you map the returned value — an instance of NSDictionary — into a different value.
  3. Use MTLJSONAdapter to convert the JSON into an WXCondition object, using the MTLJSONSerializing protocol you created for WXCondition.

Fetching the Hourly Forecast

Now add the following method to WXClient.m, which fetches the hourly forecast for a given set of coordinates:

- (RACSignal *)fetchHourlyForecastForLocation:(CLLocationCoordinate2D)coordinate {
    NSString *urlString = [NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&units=imperial&cnt=12",coordinate.latitude, coordinate.longitude];
    NSURL *url = [NSURL URLWithString:urlString];
 
    // 1
    return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
        // 2
        RACSequence *list = [json[@"list"] rac_sequence];
 
        // 3
        return [[list map:^(NSDictionary *item) {
            // 4
            return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:item error:nil];
        // 5
        }] array];
    }];
}

It’s a fairly short method, but there’s a lot going on:

  1. Use -fetchJSONFromURL again and map the JSON as appropriate. Note how much code you’re saving by reusing this call!
  2. Build an RACSequence from the “list” key of the JSON. RACSequences let you perform ReactiveCocoa operations on lists.
  3. Map the new list of objects. This calls -map: on each object in the list, returning a list of new objects.
  4. Use MTLJSONAdapter again to convert the JSON into a WXCondition object.
  5. Using -map on RACSequence returns another RACSequence, so use this convenience method to get the data as an NSArray.

Fetching the Daily Forecast

Finally, add the following method to WXClient.m:

- (RACSignal *)fetchDailyForecastForLocation:(CLLocationCoordinate2D)coordinate {
    NSString *urlString = [NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/forecast/daily?lat=%f&lon=%f&units=imperial&cnt=7",coordinate.latitude, coordinate.longitude];
    NSURL *url = [NSURL URLWithString:urlString];
 
    // Use the generic fetch method and map results to convert into an array of Mantle objects
    return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
        // Build a sequence from the list of raw JSON
        RACSequence *list = [json[@"list"] rac_sequence];
 
        // Use a function to map results from JSON to Mantle objects
        return [[list map:^(NSDictionary *item) {
            return [MTLJSONAdapter modelOfClass:[WXDailyForecast class] fromJSONDictionary:item error:nil];
        }] array];
    }];
}

Does this look familiar? Yup — this method is exactly the same as -fetchHourlyForecastForLocation:, except it uses WXDailyForecast instead of WXCondition and fetches the daily forecast.

Build and run your app; you won’t see anything new at this time, but it’s a good spot to catch your breath and ensure there aren’t any errors or warnings.

Labels and Views

Managing & Storing Your Data

It’s time to flesh out WXManager, the class that brings everything together. This class implements some key functions of your app:

  • It follows the singleton design pattern.
  • It attempts to find the device’s location.
  • After finding the location, it fetches the appropriate weather data.

Open WXManager.h and replace the contents with the following code:

@import Foundation;
@import CoreLocation;
#import <ReactiveCocoa/ReactiveCocoa/ReactiveCocoa.h>
// 1
#import "WXCondition.h"
 
@interface WXManager : NSObject
<CLLocationManagerDelegate>
 
// 2
+ (instancetype)sharedManager;
 
// 3
@property (nonatomic, strong, readonly) CLLocation *currentLocation;
@property (nonatomic, strong, readonly) WXCondition *currentCondition;
@property (nonatomic, strong, readonly) NSArray *hourlyForecast;
@property (nonatomic, strong, readonly) NSArray *dailyForecast;
 
// 4
- (void)findCurrentLocation;
 
@end

There’s nothing earth-shattering here, but here’s a few points to note from the commented sections above:

  1. Note that you’re not importing WXDailyForecast.h; you’ll always use WXCondition as the forecast class. WXDailyForecast only exists to help Mantle transform JSON to Objective-C.
  2. Use instancetype instead of WXManager so subclasses will return the appropriate type.
  3. These properties will store your data. Since WXManager is a singleton, these properties will be accessible anywhere. Set the public properties to readonly as only the manager should ever change these values privately.
  4. This method starts or refreshes the entire location and weather finding process.

Now open WXManager.m and add the following imports to the top of the file:

#import "WXClient.h"
#import <TSMessages/TSMessage.h>

Right beneath the imports, paste in the private interface as follows:

@interface WXManager ()
 
// 1
@property (nonatomic, strong, readwrite) WXCondition *currentCondition;
@property (nonatomic, strong, readwrite) CLLocation *currentLocation;
@property (nonatomic, strong, readwrite) NSArray *hourlyForecast;
@property (nonatomic, strong, readwrite) NSArray *dailyForecast;
 
// 2
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, assign) BOOL isFirstUpdate;
@property (nonatomic, strong) WXClient *client;
 
@end

Here’s the deets on the properties above:

  1. Declare the same properties you added in the public interface, but this time declare them as readwrite so you can change the values behind the scenes.
  2. Declare a few other private properties for location finding and data fetching.

Add the following generic singleton constructor between @implementation and @end:

+ (instancetype)sharedManager {
    static id _sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedManager = [[self alloc] init];
    });
 
    return _sharedManager;
}

Next, you need to set up your properties and observables.

Add the following method to WXManager.h:

- (id)init {
    if (self = [super init]) {
        // 1
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.delegate = self;
 
        // 2
        _client = [[WXClient alloc] init];
 
        // 3
        [[[[RACObserve(self, currentLocation)
            // 4
            ignore:nil]
            // 5
           // Flatten and subscribe to all 3 signals when currentLocation updates
           flattenMap:^(CLLocation *newLocation) {
               return [RACSignal merge:@[
                                         [self updateCurrentConditions],
                                         [self updateDailyForecast],
                                         [self updateHourlyForecast]
                                         ]];
            // 6
           }] deliverOn:RACScheduler.mainThreadScheduler]
           // 7
         subscribeError:^(NSError *error) {
             [TSMessage showNotificationWithTitle:@"Error" 
                                         subtitle:@"There was a problem fetching the latest weather."
                                             type:TSMessageNotificationTypeError];
         }];
    }
    return self;
}

You’re using more ReactiveCocoa methods to observe and react to value changes. Here’s what the method above does:

  1. Creates a location manager and sets it’s delegate to self.
  2. Creates the WXClient object for the manager. This handles all networking and data parsing, following our separation of concerns best practice.
  3. The manager observes the currentLocation key on itself using a ReactiveCocoa macro which returns a signal. This is similar to Key-Value Observing but is far more powerful.
  4. In order to continue down the method chain, currentLocation must not be nil.
  5. -flattenMap: is very similar to -map:, but instead of mapping each value, it flattens the values and returns one object containing all three signals. In this way, you can consider all three processes as a single unit of work.
  6. Deliver the signal to subscribers on the main thread.
  7. It’s not good practice to interact with the UI from inside your model, but for demonstration purposes you’ll display a banner whenever an error occurs.

Next up, in order to display an accurate weather forecast, we need to determine the location of the device.

Finding Your Location

Next you’ll add the code that triggers weather fetching when a location is found.

Add the following code to the implementation in WXManager.m:

- (void)findCurrentLocation {
    self.isFirstUpdate = YES;
    [self.locationManager startUpdatingLocation];
}
 
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
    // 1
    if (self.isFirstUpdate) {
        self.isFirstUpdate = NO;
        return;
    }
 
    CLLocation *location = [locations lastObject];
 
    // 2
    if (location.horizontalAccuracy > 0) {
        // 3
        self.currentLocation = location;
        [self.locationManager stopUpdatingLocation];
    }
}

The methods above are fairly straightforward:

  1. Always ignore the first location update because it is almost always cached.
  2. Once you have a location with the proper accuracy, stop further updates.
  3. Setting the currentLocation key triggers the RACObservable you set earlier in the init implementation.

Retrieve the Weather Data

Finally, it’s time to add the three fetch methods which call methods on the client and save values on the manager. All three of these methods are bundled up and subscribed to by the RACObservable create in the init method added earlier. You’ll return the same signals that the client returns, which can also be subscribed to.

All of the property assignments are happening in side-effects with -doNext:.

Add the following code to WXManager.m:

- (RACSignal *)updateCurrentConditions {
    return [[self.client fetchCurrentConditionsForLocation:self.currentLocation.coordinate] doNext:^(WXCondition *condition) {
        self.currentCondition = condition;
    }];
}
 
- (RACSignal *)updateHourlyForecast {
    return [[self.client fetchHourlyForecastForLocation:self.currentLocation.coordinate] doNext:^(NSArray *conditions) {
        self.hourlyForecast = conditions;
    }];
}
 
- (RACSignal *)updateDailyForecast {
    return [[self.client fetchDailyForecastForLocation:self.currentLocation.coordinate] doNext:^(NSArray *conditions) {
        self.dailyForecast = conditions;
    }];
}

It looks like everything is wired up and ready to go. But wait! The app doesn’t actually tell the manager to do anything yet.

Open up WXController.m and import the manager at the top of the file, like so:

#import "WXManager.h"

Add the following to the end of -viewDidLoad:

[[WXManager sharedManager] findCurrentLocation];

This simply asks the manager class to begin finding the current location of the device.

Build and run your app; you’ll be prompted for permission to use location services. You still won’t see any UI updates, but check the console log and you’ll see something like the following:

2013-11-05 08:38:48.886 WeatherTutorial[17097:70b] Fetching: http://api.openweathermap.org/data/2.5/weather?lat=37.785834&lon=-122.406417&units=imperial
2013-11-05 08:38:48.886 WeatherTutorial[17097:70b] Fetching: http://api.openweathermap.org/data/2.5/forecast/daily?lat=37.785834&lon=-122.406417&units=imperial&cnt=7
2013-11-05 08:38:48.886 WeatherTutorial[17097:70b] Fetching: http://api.openweathermap.org/data/2.5/forecast?lat=37.785834&lon=-122.406417&units=imperial&cnt=12

It looks a little obtuse, but that output means all of your code is working and the network requests are firing properly.

Wiring the Interface

It’s finally time to display all that data you’re fetching, mapping, and storing. You’ll use ReactiveCocoa to observe changes on the WXManager singleton and update the interface when new data arrives.

Still in WXController.m, go to the bottom of -viewDidLoad, and add the following code just above [[WXManager sharedManager] findCurrentLocation]; line:

// 1
[[RACObserve([WXManager sharedManager], currentCondition)
  // 2
  deliverOn:RACScheduler.mainThreadScheduler]
 subscribeNext:^(WXCondition *newCondition) {
     // 3
     temperatureLabel.text = [NSString stringWithFormat:@"%.0f°",newCondition.temperature.floatValue];
     conditionsLabel.text = [newCondition.condition capitalizedString];
     cityLabel.text = [newCondition.locationName capitalizedString];
 
     // 4
     iconView.image = [UIImage imageNamed:[newCondition imageName]];
 }];

Here’s what the above code accomplishes:

  1. Observes the currentCondition key on the WXManager singleton.
  2. Delivers any changes on the main thread since you’re updating the UI.
  3. Updates the text labels with weather data; you’re using newCondition for the text and not the singleton. The subscriber parameter is guaranteed to be the new value.
  4. Uses the mapped image file name to create an image and sets it as the icon for the view.

Build and run your app; you’ll see the the current temperature, current conditions, and an icon representing the current conditions. All of the data is real-time, so your values likely won’t match the ones below. However, if your location is San Francisco, it always seems to be about 65 degrees. Lucky San Franciscans! :]

Wiring up the UI

ReactiveCocoa Bindings

ReactiveCocoa brings its own form of Cocoa Bindings to iOS.

Don’t know what bindings are? In a nutshell, they’re a technology which provides a means of keeping model and view values synchronized without you having to write a lot of “glue code.” They allow you to establish a mediated connection between a view and a piece of data, “binding” them such that a change in one is reflected in the other.

It’s a pretty powerful concept, isn’t it?

Rainbow Vom

Okay, pick your jaw up off the floor. It’s time to move on.

Note: For more examples of powerful bindings, check out the ReactiveCocoa Readme.

Add the following code below the code you added in the previous step:

// 1
RAC(hiloLabel, text) = [[RACSignal combineLatest:@[
                        // 2
                        RACObserve([WXManager sharedManager], currentCondition.tempHigh),
                        RACObserve([WXManager sharedManager], currentCondition.tempLow)]
                        // 3
                        reduce:^(NSNumber *hi, NSNumber *low) {
                            return [NSString  stringWithFormat:@"%.0f° / %.0f°",hi.floatValue,low.floatValue];
                        }]
                        // 4
                        deliverOn:RACScheduler.mainThreadScheduler];

The code above binds high and low temperature values to the hiloLabel‘s text property. Here’s a detailed look at how you accomplish this:

  1. The RAC(…) macro helps keep syntax clean. The returned value from the signal is assigned to the text key of the hiloLabel object.
  2. Observe the high and low temperatures of the currentCondition key. Combine the signals and use the latest values for both. The signal fires when either key changes.
  3. Reduce the values from your combined signals into a single value; note that the parameter order matches the order of your signals.
  4. Again, since you’re working on the UI, deliver everything on the main thread.

Build and run your app; you should see the high/low label in the bottom left update along with the rest of the UI like so:

UI Wiring with Bindings

Displaying Data in the Table View

Now that you’ve fetched all your data, you can display it neatly in the table view. You’ll display the six latest hourly and daily forecasts in a paged table view with header cells as appropriate. The app will appear to have three pages: one for current conditions, one for the hourly forecast, and one for the daily forecasts.

Before you can add cells to the table view, you’ll need to initialize and configure some date formatters.

Go to the private interface at the top of WXController.m and add the following two properties:

@property (nonatomic, strong) NSDateFormatter *hourlyFormatter;
@property (nonatomic, strong) NSDateFormatter *dailyFormatter;

As date formatters are expensive to create, we’ll instantiate them in our init method and store references to them using these properties.

Still in the same file, add the following code directly under the @implementation statement:

- (id)init {
    if (self = [super init]) {
        _hourlyFormatter = [[NSDateFormatter alloc] init];
        _hourlyFormatter.dateFormat = @"h a";
 
        _dailyFormatter = [[NSDateFormatter alloc] init];
        _dailyFormatter.dateFormat = @"EEEE";
    }
    return self;
}

You might wonder why you’re initializing these date formatters in -init and not -viewDidLoad like everything else. Good question!

-viewDidLoad can actually be called several times in the lifecycle of a view controller. NSDateFormatter objects are expensive to initialize, but by placing them in -init you’ll ensure they’re initialized only once by your view controller.

Find tableView:numberOfRowsInSection: in WXController.m and replace the TODO and return lines with the following:

// 1
if (section == 0) {
    return MIN([[WXManager sharedManager].hourlyForecast count], 6) + 1;
}
// 2
return MIN([[WXManager sharedManager].dailyForecast count], 6) + 1;

A relatively short code block, but here’s what it does:

  1. The first section is for the hourly forecast. Use the six latest hourly forecasts and add one more cell for the header.
  2. The next section is for daily forecasts. Use the six latest daily forecasts and add one more cell for the header.
Note: You’re using table cells for headers here instead of the built-in section headers which have sticky-scrolling behavior. The table view is set up with paging enabled and sticky-scrolling behavior would look odd in this context.

Find tableView:cellForRowAtIndexPath: in WXController.m and replace the TODO section with the following:

if (indexPath.section == 0) {
    // 1
    if (indexPath.row == 0) {
        [self configureHeaderCell:cell title:@"Hourly Forecast"];
    }
    else {
        // 2
        WXCondition *weather = [WXManager sharedManager].hourlyForecast[indexPath.row - 1];
        [self configureHourlyCell:cell weather:weather];
    }
}
else if (indexPath.section == 1) {
    // 1
    if (indexPath.row == 0) {
        [self configureHeaderCell:cell title:@"Daily Forecast"];
    }
    else {
        // 3
        WXCondition *weather = [WXManager sharedManager].dailyForecast[indexPath.row - 1];
        [self configureDailyCell:cell weather:weather];
    }
}

Again, this code is fairly straightforward:

  1. The first row of each section is the header cell.
  2. Get the hourly weather and configure the cell using custom configure methods.
  3. Get the daily weather and configure the cell using another custom configure method.

Finally, add the following three methods to WXController.m:

// 1
- (void)configureHeaderCell:(UITableViewCell *)cell title:(NSString *)title {
    cell.textLabel.font = [UIFont fontWithName:@"HelveticaNeue-Medium" size:18];
    cell.textLabel.text = title;
    cell.detailTextLabel.text = @"";
    cell.imageView.image = nil;
}
 
// 2
- (void)configureHourlyCell:(UITableViewCell *)cell weather:(WXCondition *)weather {
    cell.textLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
    cell.detailTextLabel.font = [UIFont fontWithName:@"HelveticaNeue-Medium" size:18];
    cell.textLabel.text = [self.hourlyFormatter stringFromDate:weather.date];
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%.0f°",weather.temperature.floatValue];
    cell.imageView.image = [UIImage imageNamed:[weather imageName]];
    cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
 
// 3
- (void)configureDailyCell:(UITableViewCell *)cell weather:(WXCondition *)weather {
    cell.textLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18];
    cell.detailTextLabel.font = [UIFont fontWithName:@"HelveticaNeue-Medium" size:18];
    cell.textLabel.text = [self.dailyFormatter stringFromDate:weather.date];
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%.0f° / %.0f°",
                                  weather.tempHigh.floatValue,
                                  weather.tempLow.floatValue];
    cell.imageView.image = [UIImage imageNamed:[weather imageName]];
    cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
}

Here’s what the above three methods do:

  1. Configures and adds text to the cell used as the section header. You’ll reuse this for daily and hourly forecast sections.
  2. Formats the cell for an hourly forecast.
  3. Formats the cell for a daily forecast.

Build and run your app; try to scroll your table view and…wait a minute. Nothing is showing up! What gives?

If you’ve used UITableView in the past, you’ve probably run into this very problem before. The table isn’t reloading!

To fix this you need to add another ReactiveCocoa observable on the hourly and daily forecast properties of the manager.

As a self-test, try to write this reusing some of the observables in -viewDidLoad. If you get stuck, the solution is below.

Solution Inside: Solution SelectShow>

Build and run your app once more; scroll the table views and you’ll see all the forecast data populate, as below:

Forecast with Odd Heights

Adding Polish to Your App

The pages for the hourly and daily forecasts aren’t taking up the whole screen. Fortunately, this turns out to be a real easy fix. Earlier in the tutorial you captured the screen height in -viewDidLoad.

Find the table view delegate method -tableView:heightForRowAtIndexPath: in WXController.m and replace the TODO and return lines with the following:

NSInteger cellCount = [self tableView:tableView numberOfRowsInSection:indexPath.section];
return self.screenHeight / (CGFloat)cellCount;

This divides the screen height by the number of cells in each section so the total height of all cells equals the height of the screen.

Build and run your app; the table view now fills the entire screen as shown in the screenshot below:

Forecast with Full Height

The last thing to do is incorporate the blur I mentioned at the beginning of Part 1 of this tutorial. The blur should fill in dynamically as you scroll past the first page of forecast.

Add the following scroll delegate near the bottom of WXController.m:

#pragma mark - UIScrollViewDelegate
 
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // 1
    CGFloat height = scrollView.bounds.size.height;
    CGFloat position = MAX(scrollView.contentOffset.y, 0.0);
    // 2
    CGFloat percent = MIN(position / height, 1.0);
    // 3
    self.blurredImageView.alpha = percent;
}

This method is pretty straightforward:

  1. Get the height of the scroll view and the content offset. Cap the offset at 0 so attempting to scroll past the start of the table won’t affect blurring.
  2. Divide the offset by the height with a maximum of 1 so that your offset is capped at 100%.
  3. Assign the resulting value to the blur image’s alpha property to change how much of the blurred image you’ll see as you scroll.

Build and run your app; scroll your table view and check out the awesome blur effect:

Finished Product

Where To Go From Here?

You’ve accomplished a lot in this tutorial: you created a project using CocoaPods, built a view structure completely in code, created data models and managers, and wired it all together using Functional Programming!

You can download a finished copy of the project here.

There are lots of cool places you could take this app. A neat start would be to use the Flickr API to find background images based on the device’s location.

As well, your app only deals with temperature and conditions; what other weather information could you integrate into your app?

Thanks for following along! If you’ve got any questions or comments, please share them below!

iOS 7 Best Practices; A Weather App Case Study: Part 2/2 is a post from: Ray Wenderlich

The post iOS 7 Best Practices; A Weather App Case Study: Part 2/2 appeared first on Ray Wenderlich.

Reminder: Free Tech Talk on iOS 7 Multitasking Tonight!

$
0
0
Free tech talk on iOS 7 multitasking tonight!

Free tech talk on iOS 7 multitasking tonight!

As you may or may not know, each month we have a Google Hangout with the raywenderlich.com Team where we discuss a technical topic.

By popular request, we are now opening these events to the entire community to watch and participate in with live Q&A.

Tonight is our January Tech Talk, and you’re all invited! Here are the details:

  • When: Today, Jan 7 at 7:00 PM EST
  • What: Multitasking APIs in iOS 7 presentation & followed by live Q&A (come w/ questions)
  • Who: Pietro Rea
  • Where: Google Hangouts Event Page
  • Why: For learning and fun!
  • How: Visit the event page and a video URL should be posted. Follow the instructions there to submit your Q&A (via text) as the talk runs.

We hope to see some of you at the hangout, and hope you enjoy!

Reminder: Free Tech Talk on iOS 7 Multitasking Tonight! is a post from: Ray Wenderlich

The post Reminder: Free Tech Talk on iOS 7 Multitasking Tonight! appeared first on Ray Wenderlich.


iOS 7 Multitasking Tech Talk Video

$
0
0

The first Tuesday of each month, one of the members of the team gives a Tech Talk, and by popular request we’ve started to stream these live.

Last night in our January Tech Talk, Pietro Rea gave a great talk on iOS 7 Multitasking.

Here’s the video for anyone who didn’t get a chance to watch!

Source Code

Here is the source code for the demo from the tech talk.

Want to Join Us Next Month?

Thanks again Pietro for giving a great talk and having the guts to give the talk to a live audience :] And thank you to everyone who attended – we hope you enjoyed it!

Next month, Cesare Rocchi and Orta Therox will give a tech talk on CocoaPods, the best way to get the dependencies for your Objective-C projects installed and up-to-date.

We will be broadcasting this talk live on Tuesday, Feb 4 at 2:00 PM EST, so if you want to join us sign up here! As you watch the talk, you can submit any Q&A you may have live.

Hope to see some of you there! :]

iOS 7 Multitasking Tech Talk Video is a post from: Ray Wenderlich

The post iOS 7 Multitasking Tech Talk Video appeared first on Ray Wenderlich.

Space Game Starter Kit Third Edition (Sprite Kit Version) Now Available!

$
0
0
Fully updated for iOS 7 and Sprite Kit!

Fully updated for iOS 7 and Sprite Kit!

It’s been over two years since I first wrote the Space Game Starter Kit, and a lot has changed since then.

When I first wrote the starter kit, I used Cocos2D-iPhone, the most popular 2D engine at the time. However, this year in iOS 7 Apple has released its own 2D graphics framework called Sprite Kit.

When it comes to making 2D iPhone-specific games, I believe Sprite Kit is the way of the future. So today, I am pleased to announce a brand new third edition for the Space Game Starter Kit, that is fully ported to iOS 7 and Sprite Kit!

It was a ton of work to update the starter kit for Sprite Kit – almost every page needed to change. However, this major update is completely free of charge to existing customers, as a way of saying thank you for supporting what we do on this site!

About the Update

If you’ve read a previous version of the Space Game Starter Kit, I think you’ll enjoy this Sprite Kit update. Since Sprite Kit has a built-in texture packer and particle system generator, there are no longer any third-party tool dependencies to create the game. In addition, Sprite Kit has greatly simplified much of the code—I think you’ll find this version cleaner and easier to follow than earlier versions.

Note that I’ve also included the old second edition of the starter kit for the Cocos2D fans out there in the download. However, from here on out we will no longer be supporting the Cocos2D version since we’re moving to Sprite Kit.

Wondering about the Beat ‘Em Up Game Starter Kit or Platformer Game Starter Kit? Good news – Sprite Kit versions of those will be coming soon too! :]

How To Grab Your Copy

If you’ve previously purchased the Space Game Starter Kit, you can download the latest version for free on your My Loot page. If for some reason the book does not show up on your My Loot page, fill out this form or contact me.

If you don’t have the Space Game Starter Kit yet, it’s a great time to grab a copy!

I hope you all enjoy the third edition!

Space Game Starter Kit Third Edition (Sprite Kit Version) Now Available! is a post from: Ray Wenderlich

The post Space Game Starter Kit Third Edition (Sprite Kit Version) Now Available! appeared first on Ray Wenderlich.

Unity 4.3 2D Tutorial: Getting Started

$
0
0
Learn how to make a 2D game in Unity, using the new built-in 2D toolset introduced in Unity 4.3!

Learn how to make a 2D game in Unity, using the new built-in 2D toolset introduced in Unity 4.3!

If you’ve tried making a 2D game with earlier versions of Unity, you know it was certainly possible, but you also know you had to jump through a few hoops to do it.

Maybe you applied textures to quads, adjusting their offsets with a script to create animations. If you used physics, they were in 3D, so you had to make sure your objects had sufficient depth to interact with each other while ensuring they didn’t accidentally rotate around their x- or y-axes. Or maybe you chose to use one of the various add-ons available on Unity’s Asset Store, for example 2D Toolkit or the Orthello 2D Framework, any one of which includes some great features but also forces you to work within its own set of constraints.

While all of these options are still available, Unity 4.3 introduces native tools that add a new dimension to your workflow options: the 2nd dimension!

This is the first in a planned series of tutorials that explore Unity’s native 2D support. Over the series you’ll create Zombie Conga, a game originally conceived for our Sprite Kit book, iOS Games by Tutorials. You’ll do things differently here, but the end result will be the same – a scrolling 2D game about a happy-go-lucky zombie who just wants to dance, the cats who join him in the afterlife party, and the old ladies who try to put a stop to this adorable abomination.

This Unity 4.3 2D tutorial focuses on Unity’s new asset type – Sprite. You’ll learn everything you need to know about Sprites here, and in future tutorials you’ll learn how to control animations through Unity’s Animators and you’ll get an introduction to Unity’s new 2D physics support.

There’s a lot to cover, so you should get going.

Note: This tutorial assumes you have at least some experience with Unity. You should know the basics of working with Unity’s interface, GameObjects and Components, and you should understand an instruction like, “Add a new cat to your scene by dragging cat from the Project browser into the Hierarchy.”

If you think any of that sounds like crazy talk, or if you’d like a moment to get yourself into the right mindset for dragging cats, you may want to go through a tutorial that gives a more thorough introduction to Unity, such as this one.

Finally, note that the instructions in this tutorial are tailored toward OS X. However, if you’re running on Windows don’t worry – since Unity works the same on Windows most of these instructions will still work just fine. There will be a few minor differences (such as using Windows Explorer instead of Finder) but you’ll get through it. Or just start using OS X – reader’s choice!

Getting Started

Unity introduced native 2D tools in version 4.3 (both free and pro), so be sure you have the latest version installed. You can download it from Unity’s website.

You’ll also need some art to make a 2D game. Fortunately, Mike Berg made some cool images for Zombie Conga. Download Mike’s art here and unzip it to someplace convenient.

Create Your Project

Open Unity and create a new project by choosing File\New Project…. Click Set… in the Create new Project tab of the Project Wizard dialog that appears.

Name the project ZombieConga, choose a folder in which to create it, and click Save.

Finally, choose 2D in the combo box labeled Set up defaults for:, as shown below, and click Create Project:

Default project settings for 2D

The above-mentioned combo box is the first 2D-related feature you’ll come across in Unity. It’s supposed to change the default import settings for your project’s art assets, but so far I haven’t seen it work properly. Fortunately, this isn’t a problem because you can change this setting in your project at any time, and doing so works fine.

To ensure it’s set properly, and so you know how to change it if you ever want to, choose Edit\Project Settings\Editor to open the Editor Settings in the Inspector. In the Default Behavior Mode section, choose 2D for the Mode value, as shown below:

Default Behavior Mode highlighted in editor

The Default Behavior Mode defines the default import settings for your project’s art assets. When set to 3D, Unity assumes you want to create a Texture asset from an imported image file (e.g. a .PNG file); when set to 2D, Unity assumes you want an asset of type Sprite. You’ll read more details about Sprite assets and import settings throughout this tutorial.

The Scene View’s 2D Mode

The next 2D feature you’re faced with is the 2D toggle button in the Scene view’s control bar.

Click the 2D toggle button to enable 2D mode, as shown below:

2D mode button highlighted in Scene view

This button toggles the Scene view’s camera between perspective and orthographic projections. What’s the difference?

When viewed with a perspective projection, objects appear smaller as they move further away from the camera, just like objects in the real world look when you see them with your eyes. However, when viewed with an orthographic projection, an object’s distance from the camera doesn’t affect its size. Therefore, in 2D mode, an object that is further away from the camera will appear behind any closer objects, but its size will remain unchanged regardless of its position.

The following image shows two Scene views, each looking at the same two cubes from the same location. The top view is in 2D mode while the bottom one is not:

Comparison of Scene view in 2D and 3D modes

The previous screenshot also shows how 2D mode hides the Scene Gizmo that lets you change the orientation of the Scene view’s camera. With 2D mode enabled, the orientation is fixed so the positive y-axis points up and the positive x-axis points to the right.

Important: Toggling this setting has no effect on how your game finally appears when played – that’s determined by the camera(s) you set up in your scene – but it can be helpful when arranging objects. You’ll probably move back and forth between these two modes while creating your own 2D games, and even sometimes while creating 3D games, but this tutorial’s screenshots all show the Scene view in 2D mode.

Question: Are you someone who feels better following along with a tutorial when your interface matches the one you see in the screenshots? Then check out the next spoiler to ease your mind!

Solution Inside: Copy my Unity layout, if you want to. SelectShow>

Sprites, Made Easily

How easy is it to add a sprite to your scene using Unity’s new features? Try the following experiment to find out.

Step 1: Drag cat.png from your Finder window into the Scene view, as demonstrated below:

Demo of dragging art directly into Scene

Step 2: Use some of the time you save making your game to send a thank you note to the Unity devs.

Phew! That was pretty tricky! If you got lost, don’t feel bad, just re-read those instructions and try again. ;]

Note: Wondering why there are two cat images shown in the animation above? Don’t worry, I’ll explain that later on.

This demonstration was simplified by relying on Unity’s default import settings, which oftentimes won’t be correct for your images. However, this serves to illustrate a point – Unity’s new features make working in 2D amazingly easy! The rest of this tutorial covers everything you’ll need to know to really get started working with 2D graphics in Unity.

Sprite Assets

Select cat in the Hierarchy and look in the Inspector. Your Inspector most likely won’t show the same position that you see in the following screenshot, but don’t worry about that right now. What’s important to note here is that, in order to display the cat in the scene, Unity attached a Sprite Renderer component to a GameObject.

cat with Sprite Renderer in Inspector

It’s not obvious, but Unity created geometry for the object, too. For each Sprite, Unity creates a mesh that basically fits the non-clear pixels in your image. Notice the blue mesh in the following image of the zombie:

Sprite mesh

By creating a mesh like this rather than applying your sprites as textures on a quad, Unity can improve your scene’s fill rate at render-time. It also makes creating polygon colliders easy, but that will have to wait for a tutorial later in this series.

Note: Don’t let the sudden appearance of the zombie startle you. I just showed the zombie because its mesh was more interesting than the one generated for the cat sprite.

You’ll learn about the Sprite Renderer’s properties throughout this tutorial, but for now, look at the field labeled Sprite. This shows the name of the Sprite asset assigned to this renderer. You can only assign one Sprite at a time, but later you’ll learn how to update this field at runtime to create animations.

As you can see in the following image, the cat GameObject has a Sprite named cat assigned to its renderer:

Sprite field highlighted in inspector

Be sure the Project browser is visible. Then click inside the Sprite field in the Inspector to locate and highlight the Sprite asset in the Project browser, as shown here:

cat sprite highlighted in Project browser

Note: The highlighted border fades away after a few seconds, so if you don’t notice it, click the Sprite field again. Of course, with only one asset in your project, it’s unlikely you’ll miss it. ;]

As you can see in the previous screenshot, Unity highlighted an item named cat inside the Project browser, which is a child of another object, also named cat. Two cats in the Project browser? Yeah, that could be confusing. Here’s what’s going on:

  • The parent cat is the Texture asset. It’s a reference to the original art file you imported, cat.png, and controls the import settings used to create the Sprites from your artwork. As you can see, it shows a nice thumbnail of the file’s contents.
  • The child cat is a Sprite asset that Unity created when it imported cat.png. In this case, there is only one child because Unity only created a single Sprite from the file, but later in the section on slicing sprite sheets you’ll see how to create multiple Sprites from a single image.

Note: While I’ve been claiming Unity renders Sprite objects, the Sprite class actually only contains the information needed to access a Texture2D object, which is what stores the real image data. You can create your own Texture2D objects dynamically if you want to generate Sprites at runtime, but that discussion will have to wait for a future tutorial.

As you saw with cat.png, you can add Sprites to your scene by dragging art assets from the Finder directly into the Scene view (or the Hierarchy, if you’d like). But more commonly, you’ll add assets to your project prior to adding objects to your scene.

Add to your project the remaining image files you downloaded: background.png, enemy.png, and zombie.png.

Solution Inside: Not sure how to add image assets to your project? Find out here. SelectShow>

Add an enemy to your scene by dragging enemy from the Project browser to the Hierarchy.

Just like with cat, there are two items named enemy in the Project browser, but it doesn’t matter which one you choose. That’s because dragging a Sprite asset (the child) always uses that specific Sprite, whereas dragging a Texture asset (the parent) uses the first child Sprite, which is the same thing in a case like this where there is only one child.

Select enemy in the Hierarchy and set its Transform component’s Position to (2, 0, 0), as shown below:

initial enemy position in editor

Before your scene starts getting sloppy, select cat in the Hierarchy and set its Position to (0, 2, 0), like this:

cat's initial position in inspector

Your scene should now be arranged like the following image:

cat and enemy in scene view

Finally, drag background from the Project browser to the Hierarchy, and set its Position to (0,0,0), as shown below:

background sprite's position in inspector

You’ll improve the background’s image quality a bit later, so don’t worry if it doesn’t look quite right. (Hint: Importing background.png is one of those times where Unity’s default settings aren’t correct.) Your Scene view will now look something like this:

scene view showing just background

Don’t be alarmed by the fact that you can no longer see the cat or the old lady in your Scene view. They’re each just taking a brief dirt nap, but you’ll dig them up soon enough. However, before you do that, you need to slice up a corpse! Err, a corpse’s sprites, that is.

Slicing Sprite Sheets

You already imported zombie.png into your project, but this file is different from the other ones you imported. Instead of a single zombie image, it contains several, as shown below:

zombie sprite sheet

Such a file is usually referred to as a sprite sheet, and you’ll want Unity to create a separate Sprite asset for each of the sheet’s individual images.

Expand zombie in the Project browser. As you can see in the following screenshot, Unity created a single child – a Sprite containing the entire image – which is not what you wanted.

zombie as single sprite

Fortunately, Unity offers a simple solution you can use to treat this image as a sprite sheet. Select the top-level zombie in the Project browser to open its Import Settings in the Inspector.

Set Sprite Mode to Multiple (see the following image) and click Apply:

sprite_mode_multiple

Choosing this option caused a new button labeled Sprite Editor to appear. It also removed the Pivot property, because each individual sprite will define its own pivot point elsewhere.

Solution Inside: Not sure about pivot points? Look inside for more info. SelectShow>

Notice in the Project browser (shown in the following image) that the zombie texture asset no longer has any children, as indicated by the lack of a small arrow on its right side:

zombie asset before slicing

In this state, the zombie texture is unusable. If you tried to drag it into the Hierarchy, you would get a message indicating it has no Sprites. That’s because you need to tell Unity how you want to slice the sprite sheet.

With zombie selected in the Project browser, click Sprite Editor in the Inspector to open the following window:

sprite editor

The Sprite Editor lets you define which portions of an image contain its individual sprites. Click the Slice button in the upper left of the window to start defining sprites, as shown below:

slice button in sprite editor

Unity can find your sprites automatically, but you can adjust its results. Start with the default settings shown below and click Slice.

default slice options

Unity uses the transparency in the texture to identify possible sprites and displays a bounding box around each one. In this case, it found the following four sprites:

auto-sliced zombie sprites

In my tests, Unity’s automatic slicing works best when images are laid out with unambiguous empty space between each item. Notice how Unity only finds the smiley face in the following image, but finds three sprites in the image after that:

example of missed sprites

Unity doesn’t find all the sprites because it cannot create mutually exclusive bounding boxes.


example of found sprites

Unity finds all three sprites because it can create a bounding box around each one of them.

The above images point out that you should arrange the images in your sprite sheets carefully. They also point out exactly why Mike had to draw the game’s sprites.

Click on any of the sprites that Unity identified to edit the details of that sprite, including its name, position, bounds, and pivot point. The following image shows the window with the second sprite selected:

sprite selected in sprite editor

You can make changes in the window’s fields, and you can adjust the bounds and pivot point directly within the image.

Normally, after you’ve made changes, you would hit Apply or Revert in the upper right of the Sprite Editor to save or discard them, respectively.

However, while the option to tweak Unity’s findings is great, you won’t need to do that here because you aren’t going to use the sprites it found. The images in zombie.png are arranged in four equally sized rectangles, and Unity has a separate option to handle cases like this one.

Click Slice in the upper left of the Sprite Editor to open the slice settings again, but this time, set Type to Grid. The splice settings change to those shown below:

slice defaults for grid

The Pixel size fields allow you to specify the size of your grid’s cells. X defines the width of each cell; Y defines the height. Unity will use those values to divide the image up equally, starting in the upper left corner of the image.

Set X to 157, and Y to 102, as shown below:

slice grid size settings

Click Slice and Unity finds the following four sprites:

zombie sprites sliced in a grid

You can still select individual cells in the grid and tweak their settings like you could when using Unity’s Automatic slicing option, but that’s unnecessary for these sprites.

Click Apply in the upper-right of the Sprite Editor to commit your changes. Notice how Unity updates the Project browser so that the top-level zombie texture asset now contains four child Sprites, named zombie_0, zombie_1, and so on, as shown below:

sliced zombie sprites in project browser

To see another way to add Sprites to your scene, you’ll create the zombie’s GameObject a bit differently. But be aware that this has nothing to do with the fact that the zombie texture is sliced into multiple Sprites – you could still make it the same way you made the enemy or background objects, by simply dragging an asset into the Hierarchy.

Create a new empty GameObject by choosing GameObject\CreateEmpty. Rename the object zombie, and set its Position to (-2, 0, 0), as shown below:

empty zombie GameObject

With zombie selected in the Hierarchy, add a Sprite Renderer component by clicking Add Component in the Inspector. In the menu that appears, choose Rendering and then choose Sprite Renderer, as shown below:

add sprite renderer component

Click the small circle/target icon on the right of the Sprite Renderer‘s Sprite field to open the Select Sprite dialog. The icon is shown below:

sprite field in editor

The dialog that appears contains two tabs, Assets and Scene. These show you all the Sprites you have in your project and in the current scene, respectively.

Choose the Assets tab and then click on zombie_0 to assign that Sprite to the renderer, as shown below:

sprite selection dialog

Inside the Scene view, you now have a zombie relaxing on the beach, with an old lady and her cat buried somewhere below it. Pleasant.

zombie on beach

With all the necessary sprites in your scene, it’s time to fix a few problems.

Configure Your Game View

The artwork for Zombie Conga was created for an iPhone game, so it’s meant to look good in a specific resolution. To match that environment, set your Game view’s size to a fixed resolution of 1136 x 640.

Solution Inside: Not sure how? Find out here. SelectShow>

Your Game view now looks something like this:

game view with bad camera

Note: Your view may not look exactly like this image, because Unity resizes the Game view to maintain your chosen aspect ratio within the available space. Regardless of its scale, you should see the same amount of the scene in your view.

Obviously, that isn’t quite right. You’re seeing the results of three different problems here, and you’ll correct each one in turn:

  1. The scene’s camera is not set up properly, so the background doesn’t fill the view properly.
  2. The scene is rendering your game objects in the wrong order, so the cat and enemy are both buried in the sand.
  3. The image quality is not very good. This one might be hard to detect with the current camera settings, especially if you aren’t familiar with how the background image should look. But you can trust me, right?

Start by fixing the camera.

Fix Your Camera’s Projection

In 2D games, you’ll usually want the camera to use an orthographic projection rather than a perspective one. You already read about these two projections earlier in this tutorial regarding the Scene view’s 2D mode, but what you may not have realized is that Unity may default your game’s cameras to use a perspective projection.

Select Main Camera in the Hierarchy. Then, inside its Camera component, make sure the Projection is set to Orthographic.

Center the camera vertically on the scene by setting its Transform component’s Position to (0, 0, -10). Your Inspector now looks like this:

camera settings in Inspector
And your Game view now looks like this:

game view with incomplete orthographic settings

Right now it’s not much different from how it looked with a perspective projection. If sprites don’t change size based on their distance from the camera, how do you zoom in so that the background fills the screen? You could try scaling your GameObjects, but there’s a much better option – change the camera’s Size property.

The camera’s Size defines the dimensions of its viewport. It’s the number of units from the center of the view to the top of it. In other words, it’s half the height of the view. The width of the view is calculated at run time based on the view’s aspect ratio, as shown below:

view size explained

In this case, you want the background image to fill the screen perfectly from top to bottom, but allow it to scroll horizontally. The background image is 640 pixels tall, so half of that would be 320 pixels. So that’s your size, right?

Not quite.

Select the top-level background in the Project browser to see its Import Settings in the Inspector.

Look at the Sprite Renderer’s Pixels to Units property. It is currently set to the default value of 100, shown below:

pixels to units in Inspector

In Unity, units do not necessarily correspond to pixels on the screen. Instead, you usually size your objects relative to each other, possibly assuming a scale such as 1 unit = 1 meter. For Sprites, Unity uses Pixels to Units to determine their unscaled size in units.

For example, consider a Sprite imported from a 500 pixels wide image. The following table shows the different widths your GameObject would have when rendering that Sprite at different scales along the x-axis, using different values for Pixels to Units:

size comparisons with varying scales and pixels to units

background.png is 640 pixels tall, and the background Sprite has a Pixel to Unit ratio of 100, so the background object in the Hierarchy will be 6.4 units tall. However, the orthographic camera’s Size property measures half the height of the screen, so it should be half the height of the background, in units, or 3.2.

Select Main Camera in the Hierarchy and set the Size property of the Camera component to 3.2, shown below:

camera with correct size in Inspector

Now your background fills the Game view properly, as shown below:

game view with correct camera settings

With the background image appearing properly, now you should be able to see problems with the image quality. The following two images point out some areas of the current view compared to what they should look like:

Overly compressed beach because of current settings.

The beach with your current settings.


Beach with improved texture settings.

The beach with the desired settings.

The problems shown above are the result of over compressing the background texture during import. You can fix that by changing the file’s Import Settings.

Correct Your Import Settings

Select the top-level background in the Project browser to view its Import Settings again, but this time look at the Preview pane at the bottom of the Inspector.

The Preview pane displays the texture generated from the import, along with the texture’s dimensions, color information, and memory usage. As you can see in the screenshot below, the background texture is currently sized at 1024 x 320 pixels. But background.png is 2048 x 640 pixels! That means Unity shrank the original image by 50% in order to fit it in a 1024 x 1024 texture.

preview of background texture with default settings

To fix your texture, look at the Max Size and Format settings in the tabs at the bottom of the Import Settings, shown below:

import settings default tab

Max Size defines the maximum allowed size of the generated texture, defined as a square, and it defaults to 1024 pixels. Meanwhile, Format specifies the color depth of the image and defaults to Compressed.

You can set different values for each target platform (e.g. iOS, Web, Android), but for this app you’ll just deal with the Default tab.

In the Default tab, change Max Size to 2048 and click Apply. Your Import Settings should look like this:

Final background Import Settings

Immediately you’ll notice both the Scene and Game views look nicer because the background is less compressed. The following image shows the Game view:

game view with correct settings

Notice in the Inspector‘s Preview area shown below that the background texture is now 0.6 MB, up from its earlier 160 KB:

background texture preview highlighting size

Increasing the size of the texture increased its memory footprint by 4 times (the numbers you see are rounded a bit).

For some textures, you may want to adjust their Format value to improve their color quality, but that will increase the size even further. For example, if you try changing background‘s Format to 16 bits, you’ll see the texture grows to 2.5 MB, while changing it to Truecolor results in a texture of 3.8 MB.

However, if you look at the following two versions of the background, you’ll notice that the Compressed setting results in an image that looks pretty good compared to the Truecolor version:

Background with compressed texture

Background with Compressed texture.


Background with Truecolor texture.

Background with Truecolor texture.

Because the compressed image looks good enough while saving so much memory, leave Format set to Compressed. In your own games, try different combinations of these settings and choose the one that results in the smallest texture that still produces your desired results.

Ok, the camera is set up and the background looks good. Now you need to find that old lady and her kitty cat.

Controlling Draw Order

You still can’t see the cat or the enemy sprites because the scene is drawing them behind the background sprite. You could adjust the Z positions of your game objects, so that objects closer to the camera render in front of objects that are further away from it. In fact, that’s a perfectly good way to do things and is self explanatory. However, Unity now supports a great feature for ordering sprites that you should try: Sorting Layers.

Select cat in the Hierarchy and notice its Sprite Renderer’s Sorting Layer value is set to Default, as shown below:

default sorting layer in inspector

Click the Sorting Layer drop down box and you’ll be presented with a list of all the sorting layers defined in your project, which right now is only Default.

You’ll also see an option called Add Sorting Layer…. Click it.

This brings up the same Tags & Layers editor that you can get to from various other places in Unity, but with the Sorting Layers group open while the Tags and Layers groups are conveniently closed. See the following image:

tags and layers editor

Click + in the Sorting Layers group to create a new sorting layer and name it Cats. Do that two more times to create a sorting layer named Enemies and one named Zombie. Your editor should now look like the following screenshot:

Editor showing sorting layers

These layers define the draw order – Layer 0, named Default, is the furthest in the back, with Layer 1, named Cats, in front of it, and so on.

Right now, each of the GameObjects you’ve added is using the Default Sorting Layer. For the background object, that’s fine because you want it in the back anyway, but you need to change the Sorting Layer for the other sprites.

Select cat in the Hierarchy and set its Sorting Layer to Cats. You’ll immediately notice that the cat is now visible in both the Scene and Game views.

cat with new sorting layer

With the cat on the Cats sorting layer…


Cat visible because it's using correct sorting layer

…the cat is now visible in the scene

Select enemy in the Hierarchy and set its Sorting Layer to Enemies. This way, the old ladies will walk on top of the cats. Who knows, maybe they’ll trip?

Finally, select zombie in the Hierarchy and set its Sorting Layer to Zombie to ensure your player renders on top of all the other sprites. Your Game view now looks like this:

game view with correct draw order

Note: The Sprite Renderer also has a property named Order in Layer. You can use this to set a specific sort order to GameObjects within the same Sorting Layer.

You won’t use this in Zombie Conga because I haven’t seen any Z-fighting problems in my tests. It seems that Unity renders the sprites within the layer based on when they were added to the scene, so the newest sprite added to a layer is always on top. A sprite displayed on top of another sprite in one frame won’t suddenly appear behind it in the next frame, and that behavior is good enough for this game.

Using Scripts with Sprites

You’ve got some sprites strewn about the beach, but they don’t do anything. To finish up this part of the tutorial series, you’ll write two small scripts: one to animate the zombie and one to allow the player to control the zombie’s movement. You’ll add the rest of the game behavior in later installments of this series.

Note: You’ll write your scripts using C# (pronounced “see-sharp”), but it should be easy to convert this code to Unity’s variant of JavaScript if you prefer that language. Feel free to ask questions in the comments section if you need any help. (I’ve never used Boo, the other language Unity supports, so you’re on your own with that one.)

Animating Sprites

First you’ll add a simple script to animate the zombie. Select zombie in the Hierarchy and click Add Component in the Inspector. Choose New Script in the menu that appears, then name the script ZombieAnimator, choose CSharp as the Language, and click Create and Add. The following animation demonstrates these steps:

Demo of adding a new script

Note: You’ll replace this script-based animation with a Unity Animator in Part 2 of this tutorial series, but this example demonstrates how to access a SpriteRenderer from your scripts.

Open ZombieAnimator.cs in MonoDevelop, the code editor that ships with Unity. There are several ways to do so, but it’s easiest to double-click ZombieAnimator wherever happens to be most convenient, either in the Inspector with zombie selected or in the Project browser, as shown in the following images:

SpriteAnimator script in Inspector

SpriteAnimator script in Inspector.


SpriteAnimator in Project browser

SpriteAnimator in Project browser.

In Zombie Conga your zombie will simply walk, mindlessly, undeterred; much like you’d expect a good zombie to do. To achieve this simple animation, you’ll need a list of Sprites and a speed at which to cycle through them. To store that information, add the following public instance variables to ZombieAnimator:

public Sprite[] sprites;
public float framesPerSecond;

Note: In C# you place instance variables within the bounds of the curly braces that mark the class definition, but outside of any function. While it technically doesn’t matter, it’s usually good practice to place them at the top of the class before any function definitions.

Public variables are exposed within Unity’s editor, so you’ll be able to modify their values in the GUI without changing your code – even while running the scene! You’ll see in a moment how easy that makes it to tweak in-game values and get them just right.

You’re going to render the animation by assigning different Sprites to your GameObject‘s SpriteRenderer component. Rather than getting the component in every call to Update, you’ll cache it in an instance variable when the script first starts running.

Add the following private variable to ZombieAnimator:

private SpriteRenderer spriteRenderer;

Private variables are not exposed within Unity’s editor. In this case, you’ll initialize the variable in code by adding the following line to Start:

spriteRenderer = renderer as SpriteRenderer;

Your script subclasses MonoBehaviour, which gives it access to a variable named renderer. For GameObjects that display Sprites, renderer will be a SpriteRenderer object. Therefore, you cast renderer to type SpriteRenderer before storing it.

Note: While it’s always better to test your game’s performance to see what optimizations are necessary, it is often a good idea to store references to objects that you’re script will access frequently. Common examples of this are a GameObject‘s Transform, the scene’s main Camera, or any objects you might have to otherwise use a command like GameObject.Find to access. To keep things easier to understand, this is the only time you’ll cache an object in this tutorial, even in cases where it would make sense.

To finish this short script, add the following few lines to Update:

int index = (int)(Time.timeSinceLevelLoad * framesPerSecond);
index = index % sprites.Length;
spriteRenderer.sprite = sprites[ index ];

This takes the number of seconds since the level loaded (consult the Time class docs for more info) and multiplies it by the number of frames that should render per second. If the frames were stored in an infinitely long array, that would give you the index into the array for the current frame.

However, since you know your array won’t be infinite, you need to loop back to the start when you reach its end. You do that by performing a modulus (%) operation, which performs an integer division between two numbers and returns the remainder.

In other words, you’re getting a whole number between 0 and one less than the size of the array, which will be a valid index into the sprites array (assuming the array doesn’t have a length of zero, of course).

You’re done with MonoDevelop for now, so save your script (File\Save) and switch back to Unity.

Note: Unity compiles your scripts automatically, so if any errors appear in the Console, correct them before moving on.

Select zombie in the Hierarchy and notice the Zombie Animator (Script) component now displays fields for your two public variables. Thanks, Unity!

public variables in inspector

Right now, the Sprites array field (Unity capitalizes your variable names, and adds spaces between words) has no items. Your Sprite assets are named zombie_0 through zombie_3. These images are meant to be displayed in a specific order, so you’ll want the Sprites array to contain the necessary Sprites for a single cycle of animation, and then your script will loop through those Sprites forever.

In order to define that single animation cycle, you’ll need to add the following six elements to the Sprites array: zombie_0, zombie_1, zombie_2, zombie_3, zombie_2, zombie_1. There are several different ways you can do this, but here is my favorite:

With zombie still selected in the Hierarchy, click the lock button in the upper right of the Inspector so that it displays as locked, as shown below:

Inspector locked

This will keep the current Inspector information displayed even if you select another object in the project, which is useful in cases like this.

With the zombie texture expanded in the Project browser, left click zombie_0 to select it, then shift+left click zombie_3. That selects all four zombie Sprites.

Now drag your selected objects over to the Inspector and hover anywhere over the Sprites row in the Zombie Animator (Script) component. You should see a green plus icon appear under your cursor when you are in a good spot, as shown below:

cursor with plus indicator

Release the mouse button and Unity automatically increases the size of the Sprites array and adds your selected items to it.

Note: It doesn’t matter whether or not you have the Sprites item expanded when you perform the previous steps. The screenshot above shows it expanded only because Unity automatically expands any closed item if you hover over it for long enough during a drag action. If you were trying to take a timed screenshot, for example. :]

Your Zombie Animator (Script) component now looks like this:

Sprites array with four elements

Now select only zombie_2 in the Project browser and drag it over in the same way. Unity increases the size of the Sprites array again and appends your new element.

Do this one more time for zombie_1, and your Sprites array should now contain all six elements in the correct order, like this:

Sprites array with all six elements

Before you move on, click the lock button in the upper right of the Inspector again so that it displays as unlocked, as shown below:

Inspector unlocked

If you had forgotten to do that, you’d get annoyed later when Unity started ignoring all your selections. ;]

Finally, set Frames Per Second to 10, as shown below:

zombie animation frames per second

Run the scene and marvel at your shuffling zombie!

zombie walk animation

Note: You can adjust Frames Per Second in the Inspector while playing the scene to find a speed you like, but Unity resets your values when you stop running. Therefore, be sure to remember what value you liked so you can change it for real after you’ve stopped playing.

Now that you’ve managed to animate (or is it reanimate?) the zombie, he’ll be looking to party. The next section shows you how to create a simple controller script so you can point him point him in the right direction.

Controlling Sprites

Select zombie in the Hierarchy and add a new C# script component named ZombieController, just like how you added ZombieAnimator.

Do you like an old school, plodding zombie, or one of those new-fangled runners? To get it just right, you’ll want to tweak your zombie’s movement speed while you test your scene, which calls for a public variable in your script.

Open ZombieController.cs in MonoDevelop and add the following variable to it:

public float moveSpeed;

moveSpeed will store the number of units – not pixels – that the zombie should move per second. Because you’re Sprite sizes are 1 unit for every 100 pixels, you’ll probably want to keep this value fairly low.

As you can see in the following animations, you’ll make the zombie in Zombie Conga move in a straight line toward, and then past, the location of the latest mouse click (or the latest mouse location in cases where the user holds down the mouse button while dragging):

Zombie walking toward and then past where you click.

Zombie walking toward and then past where you click. “Over here, zombie! No, over here!”

Zombie following the cursor as it is dragged.

Zombie following the cursor as it is dragged. “Who’s the good little zombie who wants to bite the yummy cursor? You are! Yes, you are.”

You most likely won’t have an input event every frame, so you’ll need to store the direction in which the zombie is headed whenever the destination changes. To accomplish this, you’ll calculate a normalized vector (a vector of length 1) that points toward the input location from the zombie.

Add the following variable to ZombieController:

private Vector3 moveDirection;

You’re making a 2D game, but Unity is still working with a 3D coordinate system. As such, Transforms store their positions as Vector3 objects. While you could use a Vector2 here because you know the zombie will never change its position on the z-axis, you’re storing the zombie’s direction of movement in a Vector3 to avoid having to cast between the two types later in the script. It’s really a matter of personal preference.

Add the following code to Update in order to update moveDirection whenever the game receives an input event:

// 1
Vector3 currentPosition = transform.position;
// 2
if( Input.GetButton("Fire1") ) {
  // 3
  Vector3 moveToward = Camera.main.ScreenToWorldPoint( Input.mousePosition );
  // 4
  moveDirection = moveToward - currentPosition;
  moveDirection.z = 0; 
  moveDirection.Normalize();
}

Here’s what you’re doing with the preceding code:

  1. Since you’ll be using the zombie’s current position a few times in this method, you copy the position to a local variable.
  2. Then you check to ensure the Fire1 button is currently pressed, because you don’t want to calculate a new direction for the zombie otherwise. See the upcoming note for more information about Input and Fire1.
  3. Using the scene’s main (and in this case, its only) Camera, you convert the current mouse position to a world coordinate. With an orthographic projection, the z value in the position passed to ScreenToWorldPoint has no effect on the resulting x and y values, so here it’s safe to pass the mouse position directly.
  4. You calculate the direction to move by subtracting the zombie’s current position from the target location. Because you don’t want the zombie changing its position along the z-axis, you set moveDirection‘s z value to 0, meaning, “Move zero units along the z-axis.” Calling Normalize ensures moveDirection has a length of 1 (also known as “unit length”). Unit length vectors are convenient because you can multiply them by a scalar value (like moveSpeed) to make a vector pointing in the same direction, but a certain length (like a moveSpeed-long vector pointing from the zombie in the direction toward the mouse cursor). You’ll use this next.

Note: You use methods on Input to access input data in a generic way. A project defines various input names by default, referred to as axes, such as Horizontal, Vertical, and Jump. Horizontal monitors the joystick’s position along its x-axis, as well as the state of the left and right arrow buttons on the keyboard. If you needed to know when your game received input in a horizontal direction, you could simply check for the value of the Horizontal axis without concerning yourself with the specifics of where the input originated.

Fire1 is one of the virtual button axes defined by default. It registers events for button 0 on a joystick or mouse, and the left control key on the keyboard. Input.GetButton returns true while the specified virtual button is down, so the code you wrote will update moveDirection every frame that the mouse button is down (not just on the frame when it was initially pressed). Yes, this also means you’ll be able to change the zombie’s direction by pressing your keyboard’s left control key, as long as you remember that you still need the mouse to steer!

If you want to see or customize your project’s input options, go to Edit\Project Settings\Input to bring up the InputManager‘s settings in the Inspector.

Now start the zombie walking by adding the following code to the end of Update:

Vector3 target = moveDirection * moveSpeed + currentPosition;
transform.position = Vector3.Lerp( currentPosition, target, Time.deltaTime );

The first line calculates a target location that is moveSpeed units away from the zombie’s current position. That is, it finds the point the zombie would reach if it traveled from its current position in the direction pointed to by moveDirection for a duration of one second.

The second line uses Vector3.Lerp to determine the zombie’s new location along the path between its current and target locations. Lerp is a convenient method that interpolates between two values based on a third value. Lerp clamps the third value between zero and one and interprets it as a fraction along the path between the two points. That means a value of 0 would return currentPosition, a value of 1 would return target, and a value of 0.5 would return the midpoint between them.

In your case, you use Time.deltaTime as the third value because it is a fraction of one second and you know it will most likely be less than one, giving you some point along the path that is not quite at the end point. Your game would need to be running horrendously for Time.deltaTime to be anywhere near 1, so you should get nice, smooth motion.

Save your script in MonoDevelop and switch back to Unity.

Run the scene and click somewhere to get the zombie walking. Or not. You never set ZombieController‘s moveSpeed, so it’s “moving” at a rate of zero!

While the scene is still running, select zombie in the Hierarchy and find the Zombie Controller (Script) component in the Inspector. Change Move Speed to 2, click on the beach, and your zombie should be on his way!

zombie starting to walk

Adjust Move Speed in the Inspector until you’re happy with it. Depending on the speed you choose, you may want to adjust the Frames Per Second on the Zombie Animator (Script) component, too. Otherwise his animation may not match his motion.

When you find values you like, remember them and stop the scene. Then set the values in the Inspector again so they’ll be correct the next time you run.

While you were busy tweaking his ambulatory system, you probably noticed a few things wrong with your zombie.

  1. When you start playing, his legs are moving but he’s not going anywhere.
  2. He happily walks right off the screen.
  3. He doesn’t look where he’s going.

You’ll fix the zombie so he no longer wanders off screen in a later installment of this tutorial series, so ignore that issue for now. Besides, if he leaves the screen, simply click on the beach again and he’ll come strolling back eventually. Zombies are good like that.

Go back to ZombieController.cs in MonoDevelop.

The script uses moveDirection when moving the zombie, but you only assign it a value when you get an input event. To spur the zombie forward when the scene starts, you simply need to initialize moveDirection to point him in the right direction.

Add the following line inside Start:

moveDirection = Vector3.right;

This points in the direction of the positive x-axis. In other words, it points toward the right side of the screen.

Save ZombieController.cs and then play your scene again in Unity. Now your zombie is off and running! Next you’ll make sure he faces the right direction so he doesn’t trip.

Back inside ZombieController.cs in MonoDevelop, add a public variable to ZombieController so you can tweak the rate at which the zombie turns.

public float turnSpeed;

You’ll use turnSpeed in your calculations to control how quickly the zombie reorients himself to a new direction.

Unity uses quaternions internally to represent rotations. If you’re curious about the details of the math, check out this info. Then, after you’ve cleaned up the mess from your head exploding, take solace in the fact that you really don’t need to know anything about quaternions when working with 2D.

That’s because the Quaternion.Euler method lets you create a Quaternion object from an Euler angle. Euler angles are the ones most people are accustomed to, consisting of individual x, y and z rotations. While they aren’t ideal for 3D work because of problems like gimbal lock, Euler angles are just fine for 2D games where you probably only want to rotate around the z-axis.

Note: To learn more about Quaternions, check out our OpenGL ES Transformations with Gestures tutorial.

Add the following code to the end of Update:

float targetAngle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
transform.rotation = 
  Quaternion.Slerp( transform.rotation, 
                    Quaternion.Euler( 0, 0, targetAngle ), 
                    turnSpeed * Time.deltaTime );

First you use Mathf.Atan2 to find the angle between the x-axis and moveDirection. Mathf.Atan2 returns the angle in radians, so you convert it to degrees by multiplying by Mathf.Rad2Deg.

You use Quaternion.Slerp to turn towards the target angle you calculated. Quaternion.Slerp performs a spherical linear interpolation between the two angles you specify. It’s similar to what you did earlier with Vector3.Lerp, except you’re calculating a new rotation instead of a new position.

Earlier, when you called Vector3.Lerp, you used moveSpeed to adjust the distance the zombie traveled. In this case, you’re using turnSpeed to do something similar; the larger its value, the faster the zombie will arrive at the target angle.

Note: Some of you might be thinking to yourselves, “Hey, that math’s no good! I want the zombie to take the shortest path to the new angle, and just finding the arctangent doesn’t guarantee that! You, sir, are a charlatan!”

To you I would suggest some calming yoga and maybe a bit more fiber in your diet. I would then point out that Quarternion.Slerp is awesome and always interpolates through the shortest route between two angles.

That’s it for ZombieController.cs. Save it and switch back to Unity.

Select zombie in the Hierarchy. Set Turn Speed to 5, as shown below:

turn speed set in inspector

Run the game and click around on the beach. No matter how hard you try, zombies never get dizzy!

zombie turning like crazy

That’s all the work you’ll do for Zombie Conga in this tutorial. Save your scene by going to File\Save Scene as…. Name the scene CongaScene and click Save.

The next section describes a feature useful for 2D games that is only available in the paid version of Unity. It isn’t necessary for Zombie Conga, but it’s something you’ll probably want to know about for more complex projects — Sprite Packing.

Sprite Packing – For Professionals Only (Sort of)

Note: This section describes a feature that is only available in Unity Pro. You can certainly still use texture atlases in the free version of Unity, but you’ll need to use separate tools to create and support them. You could also use sprite slicing like you did with zombie.png to achieve the same runtime benefit you’d get from texture atlases, because all of the Sprites you create from a single art asset will use the same texture by default, but it would require you to organize and access your assets in ways that may not be intuitive and therefore will not be covered here.

Play your scene and check out its rendering statistics by clicking the Stats button in the Game view’s control bar, as shown below:

Stats button in Game view control bar

Notice that the game is currently making four draw calls and saving none by batching. Not cool!

Game view stats showing no batching

Of course, this makes sense, because it’s rendering exactly four Sprites in the scene, and each one uses a unique texture. While a real game with only four draw calls would probably be fine, that number will likely grow as the number of objects and effects in your scene increases. Too many draw calls hurts performance, so you’ll probably need to be a bit more careful about how you organize your Sprite textures. Fortunately, there’s a basic technique you can use to help: pack your sprite images into texture atlases.

Texture atlases — large textures made up of several smaller textures, used to optimize rendering calls to the GPU — aren’t anything new, but before now you had to create them yourself. Now Unity can build them for you!

Note: As of Unity 4.3.2, Unity’s ability to create texture atlases, which they refer to as Sprite Packing, is still in Developer Preview mode. But it’s a great tool, so why not learn about it right now?

In order to pack your Sprites into texture atlases, you first need to modify their Import Settings.

Select the top level cat in the Project browser to open the Import Settings for cat.png. Notice the property named Packing Tag. Packing Tag defines the name of the texture atlas to which you want this asset’s sprites added, and it can be any string you’d like.

Set Packing Tag to toons by typing in the text field, as shown below:

Packing tag in Inspector

Don’t forget to hit Apply whenever you make changes to any asset’s Import Settings, or the changes will not take effect. Don’t worry: you’ll get a warning dialog if you select another object in your project while you have uncommitted changes.

Now repeat that process to set the Packing Tag to toons for the enemy and zombie assets as well.

Open the Sprite Packer window by choosing Window\Sprite Packer. The menu item may read Window\Sprite Packer (Developer Preview) if it’s still in beta, but you get the idea.
What’s this, an error?

Sprite Packing disabled message

As you can see, Sprite packing is disabled by default. To enable it, go to Edit\Project Settings\Editor and set the Mode for Sprite Packer to Always Enabled, as shown below:

Sprite Packer mode enabled

Choosing Always Enabled packs your Sprites when you play the game from within Unity as well as when you export a build of your project; you also have the option of only packing your Sprites for builds.

Choose Window\Sprite Packer again, and this time you see an empty Sprite Packer window, like this:

Empty Sprite Packer window

It currently warns you that Sprite packing is a developer preview feature.

Note: If Sprite packing is no longer in beta, than you won’t see the yellow message shown in the above image.

Press Pack in the upper left and you’ll see a new texture with your sprites arranged within it, as shown below:

toons atlas in Sprite Packer

Play your game again and check the stats in the Game view. As you can see in the following screenshot, Unity is now using only two draw calls, saving two by batching. It all adds up!

Game view Stats with batching

This makes sense, because even though you still have four Sprites in the scene, three of them are now sharing the same texture! And to get that performance savings, which would be more evident with more Sprites on screen simultaneously, you barely had to do anything at all! Sweet.

Sprite Packer — Options and Issues

There are a few other things you may run into when packing Sprites. The top of the Sprite Packer window contains a control bar, shown below:

Sprite Packer control bar

It shows:

  1. The current atlas you’re viewing. This drop down contains each of the Packing Tags you’ve used in your project, allowing you to choose which one to view.
  2. The current page in the atlas. The atlas you created only contains one page, but if it had more, this dropdown would let you choose which one to view. More on this in a bit.
  3. The packing policy used to arrange the sprites in the atlas. There is only one policy available at this time – DefaultPackerPolicy – but you can create your own by implementing the IPackerPolicy interface. This is an advanced feature beyond the scope of this tutorial.

The notion of a current atlas makes sense, but what you might not expect is that Unity sometimes splits up the atlas you intended to create into multiple atlases, appending group numbers to their names. This occurs when the Import Settings for the component textures don’t match.

For example, the following image shows what the toons texture atlas would look like if zombie.png had been imported with a color Format of 16 bits while cat.png and enemy.png used a Format of Compressed. Notice how it results in two atlases, named toons (Group 1) and toons (Group 2):

toons atlas divided into groups

Even though all three Sprites have the same Packing Tag, Unity created multiple atlases out of them. To ensure you’re getting the best performance from your atlases, be sure you don’t use incompatible import settings for sprites that you intend to store in the same atlas.

As for the current page, Unity creates multiple pages for an atlas if there are too many to fit within its texture size limit. If you end up with an atlas with multiple pages, Unity has basically created different texture atlases. You generally want to arrange your textures in such a way to ensure the ones most likely to be used together end up on the same page to reduce draw calls.

Note: I must admit, I haven’t figured out how Unity decides the size limit for a texture atlas, or if you can modify it. If anyone knows, please post in the comment section and I’ll update this text. Thanks!

Where To Go From Here?

I hope you enjoyed this Unity 4.3 2D Tutorial. If you didn’t enjoy it, I hope you at least learned something. If you learned nothing, maybe you should get in touch with us about writing some tutorials of your own? ;]

You can download the completed project here.

At this point although there’s still a lot left to do with Zombie Conga, you’ve actually already seen enough to go write a lot of other 2D games. You’ll finish Zombie Conga in future tutorials, exploring how to control animations with Unity Animators, introducing yourself to Unity’s 2D physics engine, and getting some more scripting practice along the way.

In the meantime, there are some great resources for you to explore on Unity’s website, including:

As always, please consider discussing this post in the Comments section.

Unity 4.3 2D Tutorial: Getting Started is a post from: Ray Wenderlich

The post Unity 4.3 2D Tutorial: Getting Started appeared first on Ray Wenderlich.

Introducing the raywenderlich.com Objective-C Style Guide

$
0
0
Check out our official Objective-C style guide!

Check out our official Objective-C style guide!

One of the goals of this web site is to continuously improve the way we make our tutorials and books. One of the things we want to improve upon in the coming year is improving our consistency across tutorials and books by different authors.

To aid with this goal, the team and I are very pleased to announce the official raywenderlich.com Objective-C style guide!

This style guide is different than other style guides out there, in that its focus is on readability for printed books and the web. Many of the decisions were made with an eye toward conserving space for print, easy legibility, and tutorial writing.

So without further ado, check out the style guide or keep reading for the highlights of the guide, who worked on it, and what the most controversial bits were!

How Was This Guide Developed?

This style guide was developed as a group effort from across the team.

I (Nicholas Waynik) served as the project manager for the style guide. I put together the initial draft and posted it to Github for review. If a team member wanted to change something they simply submitted a pull request. I’d then go through and accept, comment, or reject any pull requests. Github Issues were used for changes requiring debate or group input. Once the team collectively agreed upon a change it was then made.

The style guide team members included: Soheil Moayedi Azarpour, Ricardo Rendon Cepeda, Tony Dahbura, Colin Eberhardt, Matt Galloway, Greg Heo, Matthijs Hollemans, Christopher LaPollo, Saul Mora, Andy Pereira, Mic Pringle, Pietro Rea, Cesare Rocchi, Marin Todorov, Nicholas Waynik, and Ray Wenderlich.

Guide Highlights and Controversies

There are some parts of the style guide that we agreed upon without a hitch:

  • Naming and Methods. Naming variables and methods well substantially aids readability and understanding, well worth the cost of printed space.
  • Constants and Enums. We recommend using static variables and the new NS_ENUM keyword.
  • Dot Notation and Literals. We’re a big fan of dot notation and literal syntax on raywenderlich.com for enhanced conciseness and readability.
  • The Smiley Face. There is only one true smiley face :]

However, there were some parts where the discussions got quite heated! If you’ve ever had a discussion with fellow programmers on the proper placement of a curly brace, I’m sure you know what I mean :]

The part of the guide that was the most controversial by far was the answer to this question: Should you prefer private instance variables, or private properties?

  • Main argument for preferring private instance variables: Do the simplest possible thing that works (and prefer saving space). Why add the overhead/complexity of private properties if you don’t need them?
  • Main argument for preferring private properties: Properties bring a lot of Objective-C goodness such as key-value coding, future proofness, a little more consistent overall.

Both approaches are completely valid ways of doing things, and both sides had valid points. However, we felt we should choose one for consistency’s sake.

After a heated 118-comment discussion, StackOverflow discussion, and very close vote, private properties won the day – at least for now :]

How We’ll Use This Guide

We will strongly recommend tutorial authors on our site use this style guide moving forward. However, note that not every tutorial will be 100% compliant to the style guide – it’s asking a bit much for authors and editors to do that for free tutorials.

However, we will enforce and authors and editors use the style guide in future books and starter kits we develop on this site – as we always aim for a higher bar with those.

Comments on the Guide?

As we learned from developing this guide, there are always going to be strong opinions on code style issues.

And we’d love to hear yours! Do you love/hate any decisions we’ve made in the style guide? Post comments on this thread or file GitHub pull requests – I’ll be watching for them, and if you can convince me (and the rest of the team), we’ll change the guide.

We hope you enjoy our Objective-C style guide – and stay tuned for some more consistent tutorials and books! :]

Introducing the raywenderlich.com Objective-C Style Guide is a post from: Ray Wenderlich

The post Introducing the raywenderlich.com Objective-C Style Guide appeared first on Ray Wenderlich.

Objective-C Style and Runtime: The raywenderlich.com Podcast Episode 2

$
0
0
Episode 2: Objective-C Style and Runtime

Episode 2: Objective-C Style and Runtime

Good news – episode 2 of the raywenderlich.com podcast is now available! And best of all – we fixed our mic issues this time :]

[Subscribe in iTunes]

Here’s what is covered in this episode:

  • News: Objective-C style discussion with Nick Waynik, official raywenderlich.com style guide lead
  • What’s new in raywenderlich.com: Best new tutorials, book updates, and looking ahead
  • Interview: Matt Galloway, author of Essential Objective-C 2.0 and co-author of iOS 6 and 7 by Tutorials
  • Non-Tech Talk: Good resources to learn iOS Development
  • Tech Talk: Objective-C runtime discussion with Matt Galloway

Links and References

Our Sponsor

News

What’s New on raywenderlich.com

Non-Tech Talk

Interview and Tech Talk: Matt Galloway

Contact Us

Where To Go From Here?

We hope you enjoy our brand new podcast! We’ll be posting an episode each month to start, and maybe more frequently in the future.

Note that we have a vote for what you want to see in next month’s episode up on the sidebar, please vote and let us know what you think!

We’d love to hear what you think about the podcast, and any suggestions on what you’d like to hear in future episodes. Feel free to drop a comment here, or email us anytime at podcast@raywenderlich.com!

Objective-C Style and Runtime: The raywenderlich.com Podcast Episode 2 is a post from: Ray Wenderlich

The post Objective-C Style and Runtime: The raywenderlich.com Podcast Episode 2 appeared first on Ray Wenderlich.

Airplay Tutorial: An Apple TV Multiplayer Quiz Game

$
0
0
Learn how to make a multiplayer quiz game with different displays for your iPhone and Apple TV!

Learn how to make a multiplayer quiz game with different displays for your iPhone and Apple TV!

Apple introduced AirPlay in iOS 5, allowing your iOS devices to stream content to an Apple TV. This opened up a lot of gaming possibilities, such as using an Apple TV as one display and your iOS device as another.

GameKit was introduced back in iOS 3 (or was it still “iPhoneOS” back then?) and even though it has evolved a lot over the years, it had one interesting capability from the start called peer-to-peer connectivity. This can be used as a communication channel for multiplayer gaming.

In this tutorial you’ll see how to use AirPlay and the peer-to-peer connectivity feature of GameKit to create a trivia game that displays the question and answers through an Apple TV. Each player will use their own iOS device to answer the questions, and the first player to answer correctly wins the point!

The trivia game uses the Sprite Kit framework to handle the drawing and the UI. As covering Sprite Kit in depth is not the goal of this tutorial, it’s okay if you’re not familiar with it. If you’re curious, you can check out a few Sprite Kit tutorials from our site.

You won’t need an Apple TV either – you can use the simulator to mimic the external display if need be.

Getting Started

To get started, download the starter project and unzip the file.

Build and run your project; you should see the following screen:

gkairplay-starter

Feel free to take a peek through the starter project. You’ll see that it includes the code necessary for the main game logic of the quiz game and its user interface, but no code related to Airplay or multiplayer logic yet.

Once you’re ready taking a look through, it’s time to start learning about AirPlay and GameKit!

Setting Up a Secondary Screen

First of all, note that by default your iOS device supports screen mirroring to an external display (like an Apple TV) without you having to write one line of code. You simply swipe up from the bottom of the screen, tap the AirPlay button, and then select your external device and it just works:

Mirroring

However, often in games you want to have your iOS device show one thing, and your external display show something else. For example, in this quiz game we want the Apple TV to show one screen (the question) and the iOS devices to show a different screen (buttons to select the answers). Doing this requires some code, so that is the focus of this tutorial.

Also note that AirPlay doesn’t have a specific API to output to an Apple TV; instead, it uses the generic external display API. This means using the same API you can either connect wirelessly to an AppleTV over AirPlay, or manually connect to an external TV or monitor using one of the cables that Apple sells for this purpose.

So, if you want to display different things to different screens (i.e. not mirroring), and regardless of what type of external display you’re connecting to (Apple TV or something else), the first thing you’ll need to do is to detect whether a new external display is available.

Open ATViewController.m and add the following methods to the end of the class implementation:

#pragma mark - AirPlay and extended display
 
- (void)setupOutputScreen
{
  // Register for screen notifications
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  [center addObserver:self selector:@selector(screenDidConnect:) name:UIScreenDidConnectNotification object:nil];
  [center addObserver:self selector:@selector(screenDidDisconnect:) name:UIScreenDidDisconnectNotification object:nil];
  [center addObserver:self selector:@selector(screenModeDidChange:) name:UIScreenModeDidChangeNotification object:nil];
 
  // Setup screen mirroring for an existing screen
  NSArray *connectedScreens = [UIScreen screens];
  if ([connectedScreens count] > 1) {
    UIScreen *mainScreen = [UIScreen mainScreen];
    for (UIScreen *aScreen in connectedScreens) {
      if (aScreen != mainScreen) {
        // We've found an external screen !
        [self setupMirroringForScreen:aScreen];
        break;
      }
    }
  }
}
 
- (void)screenDidConnect:(NSNotification *)aNotification
{
}
 
- (void)screenDidDisconnect:(NSNotification *)aNotification
{
}
 
- (void)screenModeDidChange:(NSNotification *)aNotification
{
}
 
- (void)setupMirroringForScreen:(UIScreen *)anExternalScreen
{
}
 
- (void)disableMirroringOnCurrentScreen
{
}

setupOutputScreen observes three notifications to tell you when an external display is connected, disconnected or changed. However, the notifications only cover changes to the display state — they won’t tell you if you already have a display plugged in.

To cover the case of displays that are already connected, you need to loop through [UIScreen screens] which returns an array of all screens connected to the device. If you find a screen that is NOT the main screen, then you can assume this is the external display. Once you populate the empty setupMirroringForScreen: method, it will send a different scene to that screen.

Your next task is to populate all the empty methods you added above starting with screenDidConnect:.

Add the following code to screenDidConnect::

  NSLog(@"A new screen got connected: %@", [aNotification object]);
  [self setupMirroringForScreen:[aNotification object]];

The object property of the notification contains the UIScreen object of the new connected display. When you receive the notification, simply log the change and call the same setupMirroringForScreen: to set up the mirroring.

Add the following code to screenDidDisconnect:

  NSLog(@"A screen got disconnected: %@", [aNotification object]);
  [self disableMirroringOnCurrentScreen];

Here you’re simply performing the reverse of screenDidConnect:: log the notification and disable mirroring of the display. disableMirroringOnCurrentScreen is still just a shell — you’ll flesh it out later.

Next, add the following code to screenModeDidChange::

  NSLog(@"A screen mode changed: %@", [aNotification object]);
  [self disableMirroringOnCurrentScreen];
  [self setupMirroringForScreen:[aNotification object]];

This method performs a reset by disabling the screen and setting it up again. This ensures the new screen mode and settings are the ones used in the scene.

Before you fill in the logic behind setupMirroringForScreen: you’ll need some properties to store the states of your various objects.

Add the following code to the top of ATViewController.m:

#import "ATAirPlayScene.h"

Next, find the following line, located just below the includes:

@property (nonatomic, strong) ATMyScene *scene;

…and add the following properties directly below that line:

@property (nonatomic, strong) UIWindow *mirroredWindow;
@property (nonatomic, strong) UIScreen *mirroredScreen;
@property (nonatomic, strong) SKView *mirroredScreenView;
@property (nonatomic, strong) ATAirPlayScene *mirroredScene;

These three properties store your secondary UIWindow and UIScreen objects, the corresponding SKView for that screen, and the Sprite Kit scene with the interface that displays the question and answers to the external display.

Add the following code to setupMirroringForScreen:

  self.mirroredScreen = anExternalScreen;
 
  // Find max resolution
  CGSize max = {0, 0};
  UIScreenMode *maxScreenMode = nil;
 
  for (UIScreenMode *current in self.mirroredScreen.availableModes) {
    if (maxScreenMode == nil || current.size.height > max.height || current.size.width > max.width) {
      max = current.size;
      maxScreenMode = current;
    }
  }
 
  self.mirroredScreen.currentMode = maxScreenMode;

In the code above, you first store the screen sent to the method in mirroredScreen for later use. Next, you loop through the screen’s availableModes to find the maximum supported screen mode and then set that as the screen’s currentMode.

This method is not quite complete; there’s still a little to add.

Add the following code directly after the code you added above:

  // Setup window in external screen
  self.mirroredWindow = [[UIWindow alloc] initWithFrame:self.mirroredScreen.bounds];
  self.mirroredWindow.hidden = NO;
  self.mirroredWindow.layer.contentsGravity = kCAGravityResizeAspect;
  self.mirroredWindow.screen = self.mirroredScreen;
 
  self.mirroredScreenView = [[SKView alloc] initWithFrame:self.mirroredScreen.bounds];
 
  // Create and configure the scene.
  self.mirroredScene = [ATAirPlayScene sceneWithSize:self.mirroredScreenView.bounds.size];
  self.mirroredScene.scaleMode = SKSceneScaleModeAspectFill;
 
  // Present the scene.
  [self.mirroredScreenView presentScene:self.mirroredScene];
 
  [self.mirroredWindow addSubview:self.mirroredScreenView];

The above code illustrates how easy it is to present something to the new screen. First, you create a new UIWindow with the size of the secondary screen. Since windows are set to hidden by default, you need to un-hide them by setting the property to NO.

According to CALayer class reference, contentsGravity “specifies how the layer’s contents are positioned or scaled within its bounds”. In your implementation of screenModeDidChange: you disable and set up the window again when the screen changes, so you only have to set the contentsGravity to aspect fill. Finally, you set the screen of this new window to the passed-in screen.

Next you create a SKView with the same size as the window. SKView is Sprite Kit’s UIView subclass; if you were creating a project without Sprite Kit, you’d create a new UIView instance here instead of SKView.

Finally, you create a new ATAirPlayScene instance, instruct Sprite Kit to present this scene in the newly created view, and add the view to the new window.

There’s only one empty method remaining: disableMirroringOnCurrentScreen.

Add the following code to disableMirroringOnCurrentScreen:

  [self.mirroredScreenView removeFromSuperview];
  self.mirroredScreenView = nil;
  self.mirroredScreen = nil;
  self.mirroredScene = nil;
  self.mirroredWindow = nil;
 
  [self.scene enableStartGameButton:NO];

This method cleans up all the properties you created in the previous method. You also call enableStartGameButton: to disable the start button; you haven’t yet seen this but you’ll come across it later as part of the game logic.

This button is only enabled on the device with the secondary display and only when there’s more than one player connected. If you lose a display, then you need to disable this button.

The final piece is to get the ball rolling and call the setupOutputScreen you just wrote. To do this, add the following line to the end of viewDidLoad:

  [self setupOutputScreen];

Build and run your project; you should see the same screen as before:

gkairplay-starter

In the simulator menu, choose Hardware\TV Out\640×480, and a new window opens with the simulated TV output.

At this point, a bug in the simulator may cause the app to crash. This is only an issue in the simulator and won’t happen when you use a real Apple TV or a cable, so don’t worry too much about it. Run the project again without quitting the simulator and you should now see the following on both displays:

iOS_Simulator_-_iPhone_Retina__3.5-inch____iOS_7.0.3__11B508_-5

If you want to see this on your Apple TV, run the project on a physical device, open Control Center and choose your Apple TV in the AirPlay menu.

Connecting Other Players

Now comes the fun part: getting more devices to connect and play the game.

The main class of GameKit’s peer-to-peer communication is GKSession. Quoting from the documentation: “A GKSession object provides the ability to discover and connect to nearby iOS devices using Bluetooth or Wi-fi.”

As with most communication protocols, a GameKit session has the concept of a “server” and a “client”. From the documentation: “Sessions can be configured to broadcast a session ID (as a server), to search for other peers advertising with that session ID (as a client), or to act as both a server and a client simultaneously (as a peer)”.

In your game, since there should be only one person connected to an external display, your device will start a session as a server whenever a device connects to an external display. If a device isn’t connected to a display, it starts a session as a client and starts looking for a server.

Why not just use straight peer-to-peer networking? If every device was a peer — that is, a client and a server simultaneously — it would be incredibly complex to manage all connected device and control gameplay. Having a single server to control the game greatly simplifies the game logic.

Add the following code to the bottom of ATViewController.m:

#pragma mark - GKSession master/slave
 
- (BOOL)isServer
{
    return self.mirroredScreen != nil;
}

The above code uses the mirroredScreen property that is set and cleared by the secondary screen notifications to determine if this device is a server or not.

Before you can start the GKSession, there’s a few more things that you’ll need. A GKSession reports peer discovery and communication using a delegate. Therefore, you need to implement the delegate protocol in a class that will receive all these events.

Since ATViewController is controlling your game, this is the best class to act as the delegate of the GKSession.

Open ATViewController.h and add the following header right after the SpriteKit header:

#import <GameKit/GameKit.h>

Now, find the following line:

@interface ATViewController : UIViewController

…and add the GKSessionDelegate protocol to it so that it looks like the line below:

@interface ATViewController : UIViewController <GKSessionDelegate>

Go back to ATViewController.m and add the following protocol method stubs to the end of the class:

#pragma mark - GKSessionDelegate
 
/* Indicates a state change for the given peer.
 */
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
}
 
/* Indicates a connection request was received from another peer.
 
 Accept by calling -acceptConnectionFromPeer:
 Deny by calling -denyConnectionFromPeer:
 */
- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID
{
}
 
/* Indicates a connection error occurred with a peer, which includes connection request failures, or disconnects due to timeouts.
 */
- (void)session:(GKSession *)session connectionWithPeerFailed:(NSString *)peerID withError:(NSError *)error
{
}
 
/* Indicates an error occurred with the session such as failing to make available.
 */
- (void)session:(GKSession *)session didFailWithError:(NSError *)error
{
}
 
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context
{
}

You’ll populate these methods later; you’ve simply added them now to avoid compilation errors.

For more details on the delegate methods of GKSessionDelegate, check out the official GKSessionDelegate Apple Docs.)

Before you set up a GKSession, you’ll need to add a few properties to store some several objects.

Add the following properties near the top of the file, in the same block as the other properties:

@property (nonatomic, strong) GKSession *gkSession;
@property (nonatomic, strong) NSMutableDictionary *peersToNames;
@property (nonatomic, assign) BOOL gameStarted;

The first property holds the GKSession; the second is a dictionary that stores the ID of your peers and their respective advertised names; and the last one is a boolean that indicates if the game has started yet. You’ll use all of these properties in the following steps.

Add the following code immediately after the stub methods you added above:

- (void)startGKSession
{
    // Just in case we're restarting the session as server
    self.gkSession.available = NO;
    self.gkSession = nil;
 
    // Configure GameKit session.
    self.gkSession = [[GKSession alloc] initWithSessionID:@"AirTrivia"
                            displayName:[[UIDevice currentDevice] name]
                            sessionMode:self.isServer ? GKSessionModeServer : GKSessionModeClient];
    [self.gkSession setDataReceiveHandler:self withContext:nil];
    self.gkSession.delegate = self;
    self.gkSession.available = YES;
 
    self.peersToNames = [[NSMutableDictionary alloc] init];
    if (self.isServer)
    {
        self.peersToNames[self.gkSession.peerID] = self.gkSession.displayName;
    }
}

The first few lines are simply cleanup code for the case where the session is being restarted.

Next, you initialize the session object. SessionID is an ID unique to this app so that multiple devices with all kinds of GameKit apps can find each other.

The displayName parameter tells GKSession how this device should be identified to other peers. You can put whatever you like in this parameter, but here you’ll just use the device name for simplicity. The last parameter specifies the sessionMode, which indicates whether the device is a server or a client.

Once the GKSession is initialized, you tell your GKSession that ATViewController will be responsible for retrieving data from all peers and will also be the delegate for all events. Next, you set the session to be available; this signals GKSession to begin broadcasting over Wi-Fi and Bluetooth to try to find peers.

Finally, you initialize the peerToNames dictionary that tracks the other devices. If the current device is a server, it should be added to the dictionary to start.

Now you need to call this method in viewDidLoad to start the session when the game starts.

Add the following line to the end of viewDidLoad:

  [self startGKSession];

You also need to call this method when the device switches between client and server modes.

Add the same line to the end of setupMirroringForScreen::

  [self startGKSession];

Now that the Game Kit setup is complete, it’s time to start filling in those delegate methods!

Add the following code to session:peer:didChangeState::

  BOOL refresh = NO;
  switch (state)
  {
    case GKPeerStateAvailable:
      if (!self.gameStarted)
      {
        [self.gkSession connectToPeer:peerID withTimeout:60.0];
      }
      break;
 
    case GKPeerStateConnected:
      if (!self.gameStarted)
      {
        self.peersToNames[peerID] = [self.gkSession displayNameForPeer:peerID];
        refresh = YES;
      }
      break;
 
    case GKPeerStateDisconnected:
    case GKPeerStateUnavailable:
      [self.peersToNames removeObjectForKey:peerID];
      refresh = YES;
      break;
 
    default:
      break;
  }
 
  if (refresh && !self.gameStarted)
  {
    [self.mirroredScene refreshPeers:self.peersToNames];
    [self.scene enableStartGameButton:self.peersToNames.count >= 2];
  }

This method executes whenever a peer changes state. Possible states for peers are:

  • GKPeerStateAvailable: A new peer has been found and is available; in this case, you call connectToPeer:withTimeout: of GKSession. If the connection is successful you will get another state change callback with GKPeerStateConnected.
  • GKPeerStateConnected: The peer is now connected. In this case you add the peer name to the peerToNames dictionary and set the refresh flag to YES.
  • GKPeerStateDisconnected and GKPeerStateUnavailable: A peer has disconnected for some reason or has become unavailable. In this case you remove the name from the peerToNames dictionary and set the refresh flag to YES.

Finally, if the game has not started yet and the refresh flag is YES, send the updated peerToNames dictionary to the scene on the secondary screen and instruct scene on the device to enable the start game button if there are at least two connected players.

In order to establish a connection, one peer needs to ask the other to connect, as it’s being done in the method above when a peer is made available. The other side has to accept this connection request in order for the two to communicate.

Add the following code to session:didReceiveConnectionRequestFromPeer::

    if (!self.gameStarted)
    {
        NSError *error = nil;
        [self.gkSession acceptConnectionFromPeer:peerID error:&error];
        if (error)
        {
            NSLog(@"Error accepting connection with %@: %@", peerID, error);
        }
    }
    else
    {
        [self.gkSession denyConnectionFromPeer:peerID];
    }

This delegate method executes on one device when the other device calls connectToPeer:withTimeout:. If the game has not yet started, accept the connection and report any errors that might occur. If the game has already started, refuse the connection.

When one device accepts the connection, the other will receive another state change notification of GKPeerStateConnected and the new device will be added to the list of players.

To test this, you’ll need to run two copies of the app: one as the server, and another as the client. The easiest way to do this is run the simulator as a server and have a physical device run another copy of the app as the client.

If you don’t have a paid developer account and can’t run apps on a device, you can try to run two simulators on the same machine in a pinch. It’s not impossible, but it’s not the most straightforward task, either. If you want to go this route, take a look at this Stack Overflow answer for ways to accomplish this.

Build and run the app on your simulator; you’ll see the same familiar starting screen:

iOS_Simulator_-_iPhone_Retina__3.5-inch____iOS_7.0.3__11B508_-5

Now start the app on your device. If your device and your computer are on the same network, you should see something similar to the following on your simulator:

iOS_Simulator_-_iPhone_Retina__4-inch____iOS_7.0.3__11B508_-3

The device name appears on the “TV” and the iOS Simulator now shows a “Start Game” button. Your device will still display the “Waiting for players!” label.

The next step is to add more communication between the server and the client so that the gameplay can start.

Communicating With Other Devices

Now that you have two copies of the app open, it’s time to add some communication between them with GKSession as the intermediary.

GKSession has two methods to send data to peers, namely:


(BOOL)sendData:(NSData *)data toPeers:(NSArray *)peers withDataMode:(GKSendDataMode)mode error:(NSError **)error

…and…

(BOOL)sendDataToAllPeers:(NSData *)data withDataMode:(GKSendDataMode)mode error:(NSError **)error

Both methods send data wrapped in an NSData object to one or more peers. For this project you’re going to use the first method to see how the the more complicated method works. Another advantage of the first method is that you can send a message to yourself. Although it sounds strange, this comes in handy when the server triggers its own response, as if a client sent some data.

The server can have many peers (including itself) but a client will only ever have one peer: the server. In both cases, using the method that sends a message to all peers covers the common case for both server and client.

An NSData object can hold any kind of data; therefore you’ll be sending commands as NSString objects wrapped in NSData, and vice-versa when receiving data, to help with debugging.

Add the following method to the bottom of ATViewController.m:

#pragma mark - Peer communication
 
- (void)sendToAllPeers:(NSString *)command
{
  NSError *error = nil;
  [self.gkSession sendData:[command dataUsingEncoding:NSUTF8StringEncoding]
                   toPeers:self.peersToNames.allKeys
              withDataMode:GKSendDataReliable
                     error:&error];
  if (error)
  {
    NSLog(@"Error sending command %@ to peers: %@", command, error);
  }
}

As the name suggests, this method sends an NSString to all connected peers. The NSString instance method dataUsingEncoding: converts the string into a null-terminated UTF-8 stream of bytes in an NSData object.

On the receiving end, the GKSession delegate callback receiveData:fromPeer:inSession:context: you added in the previous section is still empty. Your job is to add the receiving logic.

Add the following code to receiveData:fromPeer:inSession:context::

  NSString *commandReceived = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  NSLog(@"Command %@ received from %@ (%@)", commandReceived, peer, self.peersToNames[peer]);

So far you’re simply decoding the raw data back to an NSString and logging the result.

To test this, you can send a command and check the results in the console.

Add the following code to the bottom of startGame in ATViewController.m:

  [self sendToAllPeers:@"TEST COMMAND"];

You call startGame when the user taps the “Start Game” button on the server device. This instructs the server to send this command to all peers.

Build and run the app; first on the simulator, then on the device. The final device you start will be the one logging to the visible console, and you want to make sure the message makes it to the device.

Once the app is running on the simulator, tap the “Start Game” button. You should see the following messages appear on the console:

AirTrivia.xcodeproj_—_ATAirPlayScene.m

Wasn’t that easy? Now that you have messages moving around, you only need to add a few commands and wire things up to create the trivia game.

Adding the Game Logic

Since this is a trivia game, you’re going to need a few questions with multiple answers. The easiest ones I could find with no weird licenses attached was an exercise page for a CS course on Georgia Tech called Trivia Database Starter.

I converted the CSV to a friendly plist, which you’ll find in the project as questions.plist. The plist contains an array of arrays. Every inner array has the question as the first element, the right answer as the second (no peeking!) and some wrong answers after that.

Open ATViewController.m and add the following properties to the existing block of properties at the top of the file:

@property (nonatomic, strong) NSMutableArray *questions;
@property (nonatomic, strong) NSMutableDictionary *peersToPoints;
@property (nonatomic, assign) NSInteger currentQuestionAnswer;
@property (nonatomic, assign) NSInteger currentQuestionAnswersReceived;
@property (nonatomic, assign) NSInteger maxPoints;

Here’s what each property stores:

  • questions – The remaining questions and answers. It’s mutable, as each time a question is asked it will be removed from the array. Then you won’t repeat a question, and you’ll know when to end the game.
  • peersToPoints – The current score, stored as the number of points for each peer.
  • currentQuestionAnswer – The index of the correct answer for the current question.
  • currentQuestionAnswersReceived – A count of how many answers have been received.
  • maxPoints – The current high score, to make it easy to find the winner in peerToPoints later on.

All of the game properties are ready; now you can add the code to start the gameplay.

Remove the test line you added previously and add the following code to startGame in its place:

  if (!self.gameStarted)
  {
    self.gameStarted = YES;
    self.maxPoints = 0;
 
    self.questions = [[NSArray arrayWithContentsOfFile:
          [[NSBundle mainBundle] pathForResource:@"questions" ofType:@"plist"]] mutableCopy];
    self.peersToPoints = [[NSMutableDictionary alloc] initWithCapacity:self.peersToNames.count];
    for (NSString *peerID in self.peersToNames)
    {
      self.peersToPoints[peerID] = @0;
    }
  }

If the game has not yet started, set the flag to YES and reset maxPoints. You then load in the list of questions from the plist. You’ll need a mutable copy of the questions so they can be removed from the array as they’re used. Then you intialize peersToPoints so that everyone starts with 0 points.

There aren’t any commands yet, so the game is ready to begin, but it hasn’t really started yet. You’ll do that next.

First, add the following command constants to the top of ATViewController.m after all of the includes:

static NSString * const kCommandQuestion = @"question:";
static NSString * const kCommandEndQuestion = @"endquestion";
static NSString * const kCommandAnswer = @"answer:";

You’ll see in a moment how these are used.

Next, add the following method immediately after startGame:

- (void)startQuestion
{
  // 1
  int questionIndex = arc4random_uniform((int)[self.questions count]);
  NSMutableArray *questionArray = [self.questions[questionIndex] mutableCopy];
  [self.questions removeObjectAtIndex:questionIndex];
 
  // 2
  NSString *question = questionArray[0];
  [questionArray removeObjectAtIndex:0];
 
  // 3
  NSMutableArray *answers = [[NSMutableArray alloc] initWithCapacity:[questionArray count]];
  self.currentQuestionAnswer = -1;
  self.currentQuestionAnswersReceived = 0;
 
  while ([questionArray count] > 0)
  {
    // 4
    int answerIndex = arc4random_uniform((int)[questionArray count]);
    if (answerIndex == 0 && self.currentQuestionAnswer == -1)
    {
      self.currentQuestionAnswer = [answers count];
    }
    [answers addObject:questionArray[answerIndex]];
    [questionArray removeObjectAtIndex:answerIndex];
  }
 
  // 5
  [self sendToAllPeers:[kCommandQuestion stringByAppendingString:
            [NSString stringWithFormat:@"%lu", (unsigned long)[answers count]]]];
  [self.scene startQuestionWithAnswerCount:[answers count]];
  [self.mirroredScene startQuestion:question withAnswers:answers];
}

Here’s how starting a new trivia question works:

  1. First, choose a random question from the list of remaining questions. questionArray holds a copy the question data; the selected question is removed from the master list.
  2. The question text is the first element of the array, followed by the possible answers. Here you store the question text and remove it from the array. Now questionArray contains the answers, with the correct answer as the first element.
  3. Initialize a mutable array to hold the shuffled list of answers, and reset a few properties.
  4. Inside the loop, randomly choose an answer from the array. If it’s the first element — i.e., the correct answer — and the first element has not yet been removed, store the answer’s index. Then add it to the shuffled answers array and remove it from the available answers array.
  5. Finally, send the Question command to all peers along with the number of possible answers. As an example, the command will look something like “question:4″. You then update the scene and send the question and the shuffled list of answers to the scene on the secondary screen.

Next, add a call to the above method to the end of startGame inside the if block:

  [self startQuestion];

This takes care of the server actions at the start of the game.

Now, your clients need to act accordingly when they receive a command from the server.

Add the code below to the bottom of receiveData:fromPeer:inSession:context::

  if ([commandReceived hasPrefix:kCommandQuestion] && !self.isServer)
  {
    NSString *answersString = [commandReceived substringFromIndex:kCommandQuestion.length];
    [self.scene startQuestionWithAnswerCount:[answersString integerValue]];
  }

Assuming there will never be more than nine possible answers, the final character of string like “question:4″ will represent the number of answers. answersString stores this character, which you convert to a numeric value and pass to startQuestionWithAnswerCount: so that the scene can present the number of answer buttons specified.

Build and run your project; first on the simulator, then on your device. As soon as you see the “Start Game” button, tap it. You should see something like the following on the simulator:

iOS_Simulator_-_iPhone_Retina__3.5-inch____iOS_7.0.3__11B508_-6

The screen on the device should display the same elements as the simulator’s primary screen. You might get a different number of buttons than shown here depending on the question. Tap on a button on the device or simulator, and the screen will change to the following:

iOS_Simulator_-_iPhone_Retina__3.5-inch____iOS_7.0.3__11B508_-7

But right now, nothing else happens. This is because ATMyScene only has logic to remove the buttons and call a method in ATViewController when you click on an answer — but that method in ATViewController is still empty.

Find sendAnswer: in ATViewController.m and add the following code to it:

  [self sendToAllPeers:[kCommandAnswer stringByAppendingString:
    [NSString stringWithFormat:@"%ld", (long)answer]]];

The above code is very straightforward; you only need to send the Answer command with the selected answer index.

The server picks up this message inside receiveData:fromPeer:inSession:context:.

Add the following code to the end of that method:

  if ([commandReceived hasPrefix:kCommandAnswer] && self.isServer)
  {
    NSString *answerString = [commandReceived substringFromIndex:kCommandAnswer.length];
    NSInteger answer = [answerString integerValue];
    if (answer == self.currentQuestionAnswer && self.currentQuestionAnswer >= 0)
    {
      self.currentQuestionAnswer = -1;
      NSInteger points = 1 + [self.peersToPoints[peer] integerValue];
      if (points > self.maxPoints)
      {
        self.maxPoints = points;
      }
      self.peersToPoints[peer] = @(points);
      [self endQuestion:peer];
    }
    else if (++self.currentQuestionAnswersReceived == self.peersToNames.count)
    {
      [self endQuestion:nil];
    }
  }

Here, you check to see if the command is the “answer” command. If the answer given is the correct one, reset currentQuestionAnswer to -1 to prepare for the next question. After giving the player a point, you may need to update maxPoints if the player’s score is the new high score. Finally, you call the stubbed-out endQuestion:.

If the answer is incorrect, but the number of answers received is the same as the number of players, the current roundof questions is over and you call endQuestion: with a nil argument.

Implementing endQuestion: is the next obvious step.

Add the following code directly after receiveData:fromPeer:inSession:context::

- (void)endQuestion:(NSString *)winnerPeerID
{
  [self sendToAllPeers:kCommandEndQuestion];
 
  NSMutableDictionary *namesToPoints = [[NSMutableDictionary alloc] initWithCapacity:self.peersToNames.count];
  for (NSString *peerID in self.peersToNames)
  {
    namesToPoints[self.peersToNames[peerID]] = self.peersToPoints[peerID];
  }
  [self.mirroredScene endQuestionWithPoints:namesToPoints
                                     winner:winnerPeerID ? self.peersToNames[winnerPeerID] : nil];
  [self.scene endQuestion];
 
  dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC);
  dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self startQuestion];
  });
}

This method first sends the command that tells the clients to end the current question; you’ll deal with that in a moment. Next, it creates a dictionary that maps names to points and sends it to the secondary screen scene to show the current standings and which player, if any, guessed the correct answer. Finally, it schedules a block to run four seconds later that calls startQuestion to show the next question and start the loop again.

The clients will need to deal with the end question command.

Add the following code to the end of receiveData:fromPeer:inSession:context::

  if ([commandReceived isEqualToString:kCommandEndQuestion] && !self.isServer)
  {
    [self.scene endQuestion];
  }

When a client receives this command, it simply needs to call endQuestion to end the question and hide the answer buttons.

Build and run your app; first on the simulator, and then on your device. Start the game and try answering some questions correctly and incorrectly. If you answer incorrectly, you’ll need to do it on both the device and the simulator to make the app move to the next question.

You should see screens like the following during the gameplay:

iOS_Simulator_-_iPhone_Retina__3.5-inch____iOS_7.0.3__11B508_-8

iOS_Simulator_-_iPhone_Retina__3.5-inch____iOS_7.0.3__11B508_-9

If you play long enough you’ll encounter a crash on the simulator. This is because there’s no code to handle the end-game condition when there are no more questions. That’s the final piece of the puzzle!

Add the following code to the beginning of startQuestion:

  if (self.questions.count == 0)
  {
    NSMutableString *winner = [[NSMutableString alloc] init];
    for (NSString *peerID in self.peersToPoints)
    {
      NSInteger points = [self.peersToPoints[peerID] integerValue];
      if (points == self.maxPoints)
      {
        if (winner.length) {
          [winner appendFormat:@", %@", self.peersToNames[peerID]];
        } else {
          [winner appendString:self.peersToNames[peerID]];
        }
      }
    }
    [self.mirroredScene setGameOver:winner];
    return;
  }

If you run out of questions, this method composes a string with the winner — or winners — and displays in on the secondary monitor.

Build and run your app one more time; run through the game and when you reach the end, you should see a screen like the following:

iOS_Simulator_-_iPhone_Retina__3.5-inch____iOS_7.0.3__11B508_-10

So, how’s your knowledge of CS trivia? ;]

Where To Go From Here?

Congratulations — you’ve just built a multiplayer client/server game using Game Kit that features a shared secondary display! Here’s the final project for you to download.

Now that you have the fundamentals of client-server game programming in Game Kit, you should be able to build more apps that take advantage of a second screen; games, in particular, can benefit greatly from this type ref gaming paradigm. You can use the screen as your game view, the device as a controller, and perhaps add some information as you would typically see in a HUD.

GameKit’s peer-to-peer communication opens the door to a lot of multiplayer games or apps; you can see how easy it is to implement multiplayer using Apple’s APIs. It’s also worth knowing that GKSession has been deprecated in iOS7 and has been replaced by the Multipeer Connectivity Framework. The two frameworks have a lot of similarities so your knowledge of GKSession will translate really well to the new MCSession class and its companion classes.

I hope you enjoyed the tutorial. You’re now ready to go off on your own and create wonderful things with AirPlay and GameKit’s peer-to-peer communication.

If you have any questions or comments, please share them in the discussion below!

Airplay Tutorial: An Apple TV Multiplayer Quiz Game is a post from: Ray Wenderlich

The post Airplay Tutorial: An Apple TV Multiplayer Quiz Game appeared first on Ray Wenderlich.

Custom Control for iOS Tutorial: A Reusable Knob

$
0
0

Custom UI controls are extremely useful when you need some new functionality in your app — especially when they’re generic enough to be reusable in other apps. Colin Eberhart has an excellent tutorial providing an introduction to custom UI Controls, in which you walk through the creation of a custom control derived from UISlider which allows a range to be selected, as opposed to a single value.

This custom UI tutorial for iOS 7 takes that concept a bit further and covers the creation of a control related to UISlider, by creating a circular version, inspired by a control knob, such as those found on a sound desk:

sound_desk_knob

UIKit provides the UISlider control, which lets you set a floating point value within a specified range. If you’ve used any iOS device, then you’ve probably used a UISlider to set volume, brightness, or any one of a multitude of other variables. The project you’ll build today will have exactly the same functionality, but instead of being a straight line, will instead be of circular form – like the aforementioned knob.

Getting Started

First, download the starter project here. This is a simple single-view application. The storyboard has a few controls that are wired up to the main view controller. You’ll use these controls later in the tutorial to demonstrate the different features of the knob control.

Build and run your project just to get a sense of how everything looks before you dive into the coding portion; it should look like the screenshot below:

starter-screenshot

To create the class for the knob control, click File\New\File… and select iOS\Cocoa Touch\Objective-C class. On the next screen, specify the name as RWKnobControl and have the class inherit from UIControl. Click Next, choose the “KnobControl” directory and click Create.

Before you can write any code for the new control, you must first add it to the view controller so you can see how it evolves visually.

Open up RWViewController.m and add the following import to the top of the file:

#import "RWKnobControl.h"

In the same file, just after the @interface private extension, add the following instance variable:

@interface RWViewController () {
    RWKnobControl *_knobControl;
}
@end

This variable maintains a reference to the knob control.

Now replace viewDidLoad: with the following code:

- (void)viewDidLoad
{
    [super viewDidLoad];
    _knobControl = [[RWKnobControl alloc] initWithFrame:self.knobPlaceholder.bounds];
    [self.knobPlaceholder addSubview:_knobControl];
}

This creates the knob control and adds it to the placeholder on the storyboard. The knobPlaceholder property is already wired up as a IBOutlet.

Open RWKnobControl.m and replace the boiler-plate initWithFrame: with the following code:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor blueColor];
    }
    return self;
}

This sets the background color of the knob control so that you can see it on the screen.

Build and run your app and you’ll see the following:

buildandrun1

Okay, you have the basic building blocks in place for your app. Time to work on the API for your control!

Designing Your Control’s API

Your main reason for creating a custom UI control is to create a handy and reusable component. It’s worth taking a bit of time up-front to plan a good API for your control; developers using your component should understand how to use it from looking at the API alone, without any need to open up the source code. This means that you’ll need to document your API as well!

The header file associated with your custom control represents the API. In this case, it’s RWKnobControl.h.

Open RWKnobControl.h and add the following code between the @interface and @end statements:

#pragma mark - Knob value
/**
 Contains the current value
 */
@property (nonatomic, assign) CGFloat value;
 
/**
 Sets the value the knob should represent, with optional animation of the change.
 */
- (void)setValue:(CGFloat)value animated:(BOOL)animated;
 
#pragma mark - Value Limits
/**
 The minimum value of the knob. Defaults to 0.
 */
@property (nonatomic, assign) CGFloat minimumValue;
 
/**
 The maximum value of the knob. Defaults to 1.
 */
@property (nonatomic, assign) CGFloat maximumValue;
 
#pragma mark - Knob Behavior
/**
 Contains a Boolean value indicating whether changes in the value of the knob
 generate continuous update events. The default value is `YES`.
 */
@property (nonatomic, assign, getter = isContinuous) BOOL continuous;
  • value, minimumValue and maximumValue simply set the basic operating parameters of your control.
  • setValue:animated: and continuous are copied directly from the API of UISlider; since your knob control will function similarly to UISlider, the API should match as well.
  • setValue:animated: lets you set the value of the control programmatically, while the additional BOOL parameter indicates whether or not the change in value is to be animated.
  • If continuous is set to YES, then the control calls back repeatedly as the value changes; if it is set to NO, the the control only calls back once after the user has finished interacting with the control.
If you want to use a different name for an accessor method, it’s possible to specify a custom name by adding attributes to the property declaration. In the case of Boolean properties (properties that have a YES or NO value), it’s customary for the getter method to start with the word “is.” The getter method for the property named continuous, for example, will be called isContinuous

You’ll ensure that these properties behave appropriately as you fill out the knob control implementation later on in this tutorial.

Although there are only five lines of code above, it looks much longer due to the additional comments in the code. These might seem superfluous, but Xcode can pick them up and show them in a tooltip, like so:

doc_tooltip

Code-completion tips like this are a huge time-saver for the developers that use your control, whether that’s you, your teammates or other people!

Open RWKnobControl.m and add the following method and property overrides after initWithFrame::

#pragma mark - API Methods
- (void)setValue:(CGFloat)value animated:(BOOL)animated
{
    if(value != _value) {
        // Save the value to the backing ivar
        // Make sure we limit it to the requested bounds
        _value = MIN(self.maximumValue, MAX(self.minimumValue, value));
    }
}
 
#pragma mark - Property overrides
- (void)setValue:(CGFloat)value
{
    // Chain with the animation method version
    [self setValue:value animated:NO];
}

You override the setter for the value property in order to pass it’s value directly on to the setValue:animated: method. This currently does nothing more than ensure that the value is bounded within the limits associated with the control.

Your API documentation comments specify several defaults; in order to implement them, update initWithFrame: of RWKnobControl.m as follows:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor blueColor];
 
        _minimumValue = 0.0;
        _maximumValue = 1.0;
        _value = 0.0;
        _continuous = YES;
    }
    return self;
}

Now that you’ve defined the API of your control, it’s time to get cracking on the visual design.

Setting the Appearance of Your Control

Colin’s tutorial compares CoreGraphics and images as two potential methods to set the appearance of your custom control. However, that’s not an exhaustive list; in this custom UI tutorial for iOS 7, you’ll explore a third option to control the visuals of your control: CoreAnimation layers.

Whenever you use a UIView, it’s backed by a CALayer, which helps iOS optimize the rendering on the graphics chip. CALayer objects manage visual content and are designed to be incredibly efficient for all types of animations.

Your knob control will be made up of two CALayer objects: one for the track, and one for the pointer itself. This will result in extremely good performance for your animation, as you’ll see later.

The diagram below illustrates the basic construction of your knob control:

CALayerDiagram

The blue and red squares represent the two CALayer objects; the blue layer contains the track of the knob control, and the red layer the pointer. When overlaid, the two layers create the desired appearance of a moving knob. The difference in coloring above is only to illustrate the different layers of the control — you won’t do this in the control you’ll build.

The reason to use two separate layers becomes obvious when the pointer moves to represent a new value. All you need to do is rotate the layer containing the pointer, which is represented by the red layer in the diagram above.

It’s cheap and easy to rotate layers in CoreAnimation. If you chose to implement this using CoreGraphics and override drawRect:, the entire knob control would be re-rendered in every step of the animation. This is a very expensive operation, and will likely result in sluggish animation, particularly if changes to the control’s value invoke other re-calculations within your app.

Make a new class to contain all of the code associated with rendering the control.

Click File\New\File… and select iOS\Cocoa Touch\Objective-C class. Name the class RWKnobRenderer and ensure that it subclasses NSObject. Click Next and save the file in the default location.

Open RWKnobRenderer.h and add the following code between the @interface and @end statements:

#pragma mark - Properties associated with all parts of the renderer
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) CGFloat lineWidth;
 
#pragma mark - Properties associated with the background track
@property (nonatomic, readonly, strong) CAShapeLayer *trackLayer;
@property (nonatomic, assign) CGFloat startAngle;
@property (nonatomic, assign) CGFloat endAngle;
 
#pragma mark - Properties associated with the pointer element
@property (nonatomic, readonly, strong) CAShapeLayer *pointerLayer;
@property (nonatomic, assign) CGFloat pointerAngle;
@property (nonatomic, assign) CGFloat pointerLength;

Most of these properties deal with the visual appearance of the knob, with two CAShapeLayer properties representing the two layers which make up the overall appearance of the control.

Switch to RWKnobRenderer.m and add the following method between the @implementation and @end statements:

- (id)init
{
    self = [super init];
    if (self) {
        _trackLayer = [CAShapeLayer layer];
        _trackLayer.fillColor = [UIColor clearColor].CGColor;
        _pointerLayer = [CAShapeLayer layer];
        _pointerLayer.fillColor = [UIColor clearColor].CGColor;
    }
    return self;
}

This creates the two layers and sets their appearance as transparent.

The two shapes which make up the overall knob will be created from CAShapeLayer objects. These are a special subclass of CALayer that draw a bezier path using anti-aliasing and some optimized rasterization. This makes CAShapeLayer an extremely efficient way to draw arbitrary shapes.

Add the following two methods to RWKnobRenderer.m directly after init:

- (void)updateTrackShape
{
    CGPoint center = CGPointMake(CGRectGetWidth(self.trackLayer.bounds)/2,
                                 CGRectGetHeight(self.trackLayer.bounds)/2);
    CGFloat offset = MAX(self.pointerLength, self.lineWidth / 2.f);
    CGFloat radius = MIN(CGRectGetHeight(self.trackLayer.bounds),
                         CGRectGetWidth(self.trackLayer.bounds)) / 2 - offset;
    UIBezierPath *ring = [UIBezierPath bezierPathWithArcCenter:center
                                                        radius:radius
                                                    startAngle:self.startAngle
                                                      endAngle:self.endAngle
                                                     clockwise:YES];
    self.trackLayer.path = ring.CGPath;
}
 
- (void)updatePointerShape
{
    UIBezierPath *pointer = [UIBezierPath bezierPath];
    [pointer moveToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds) - self.pointerLength - self.lineWidth/2.f,
                                     CGRectGetHeight(self.pointerLayer.bounds) / 2.f)];
    [pointer addLineToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds),
                                        CGRectGetHeight(self.pointerLayer.bounds) / 2.f)];
    self.pointerLayer.path = pointer.CGPath;
}

updateTrackShape: creates an arc between the startAngle and endAngle values with a radius that ensures the pointer will fit within the layer, and positions it on the center of the trackLayer. Once you create the UIBezierPath, you then use the CGPath property to set the path on the appropriate CAShapeLayer.

CGPathRef is the CoreGraphics equivalent of UIBezierPath. Since UIBezierPath has a nicer Objective-C API, you use that to initially create the path, and then convert it to CoreGraphics.

updatePointerShape creates the path for the pointer at the position where angle is equal to zero. Again, you create a UIBezierPath, convert it to a CGPathRef and assign it to the path property of your CAShapeLayer. Since the pointer is a simple straight line, all you need to draw the pointer are moveToPoint: and addLineToPoint:.

Calling these methods redraws the two layers; this must happen when any of the properties used by these methods are modified. To do that, you’ll need to override some of the setters for the properties you added to the API for the renderer to use.

Add the following code after the updatePointerShape method in RWKnobRenderer.m:

- (void)setPointerLength:(CGFloat)pointerLength
{
    if(pointerLength != _pointerLength) {
        _pointerLength = pointerLength;
        [self updateTrackShape];
        [self updatePointerShape];
    }
}
 
- (void)setLineWidth:(CGFloat)lineWidth
{
    if(lineWidth != _lineWidth)
    {
        _lineWidth = lineWidth;
        self.trackLayer.lineWidth = lineWidth;
        self.pointerLayer.lineWidth = lineWidth;
        [self updateTrackShape];
        [self updatePointerShape];
    }
}
 
- (void)setStartAngle:(CGFloat)startAngle
{
    if(startAngle != _startAngle) {
        _startAngle = startAngle;
        [self updateTrackShape];
    }
}
 
- (void)setEndAngle:(CGFloat)endAngle
{
    if(endAngle != _endAngle) {
        _endAngle = endAngle;
        [self updateTrackShape];
    }
}

setPointerLength: and setLineWidth: affect both the track and the pointer, so once the mechanics of saving the new value into the backing instance variable have been dealt with, you call both updateTrackShape and updatePointerShape. However, the start and end angles only matter to the track, so changes to these properties only call updateTrackShape.

Okay — your knob updates appropriately once these properties are set. However, the color property has yet to be hooked up into the CAShapeLayer rendering.

Add the following method right after setEndAngle: in RWKnobRenderer.m:

- (void)setColor:(UIColor *)color
{
    if(color != _color) {
        _color = color;
        self.trackLayer.strokeColor = color.CGColor;
        self.pointerLayer.strokeColor = color.CGColor;
    }
}

This is very similar to the other property overrides; this time, rather than having to redraw the paths associated with each of the shape layers, you’ve set the strokeColor of the track and pointer to match the one provided. CAShapeLayer requires a CGColorRef, not a UIColor, so you use the CGColor property instead.

You may have noticed that the two methods for updating the shape layer paths rely on one more property which has never been set — namely, the bounds of each of the shape layers. Since your CAShapeLayer was created with alloc-init, it currently has a zero-sized bounds.

Add the following method signature to RWKnobRenderer.h before the @end statement:

- (void)updateWithBounds:(CGRect)bounds;

Switch to RWKnobRenderer.m and add the following implementation just before the @end statement:

- (void)updateWithBounds:(CGRect)bounds
{
    self.trackLayer.bounds = bounds;
    self.trackLayer.position = CGPointMake(CGRectGetWidth(bounds)/2.0, CGRectGetHeight(bounds)/2.0);
    [self updateTrackShape];
 
    self.pointerLayer.bounds = self.trackLayer.bounds;
    self.pointerLayer.position = self.trackLayer.position;
    [self updatePointerShape];
}

The above method takes a bounds rectangle, resizes the layers to match and positions the layers in the center of the bounding rectangle. As you’ve changed a property that affects the paths, you must call the update methods for each layer.

Although the renderer isn’t quite complete, there’s enough here to demonstrate the progress of your control. Switch to RWKnobControl.m and add the following import statement to the top of the file:

#import "RWKnobRenderer.h"

Next, add an instance variable to hold an instance of your renderer by replacing the @implementation line with the following:

@implementation RWKnobControl {
    RWKnobRenderer *_knobRenderer;
}

Still in the same file, add the following method before the @end statement:

- (void)createKnobUI
{
    _knobRenderer = [[RWKnobRenderer alloc] init];
    [_knobRenderer updateWithBounds:self.bounds];
    _knobRenderer.color = self.tintColor;
    _knobRenderer.startAngle = -M_PI * 11 / 8.0;
    _knobRenderer.endAngle = M_PI * 3 / 8.0;
    _knobRenderer.pointerAngle = _knobRenderer.startAngle;
    [self.layer addSublayer:_knobRenderer.trackLayer];
    [self.layer addSublayer:_knobRenderer.pointerLayer];
}

The above method creates an instance of RWKnobRenderer, sets its size using updateWithBounds:, then adds the two layers as sublayers of the control’s layer. You’ve temporarily set the startAngle and endAngle properties above just so that your view will render.

An empty view would be taking the iOS 7 design philosophy a step too far, so you need to make sure createKnobUI is called when the knob control is being constructed. Add the following line to initWithFrame: directly after _continuous = YES:

[self createKnobUI];

You can also remove the following line from the same method since it is no longer required:

self.backgroundColor = [UIColor blueColor];

Your initWithFrame: should now look like this:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _minimumValue = 0.0;
        _maximumValue = 1.0;
        _value = 0.0;
        _continuous = YES;
        [self createKnobUI];
    }
    return self;
}

Build and run your app, and your control should look like the one below:

buildandrun2

It’s not yet complete, but you can see the basic framework of your control taking shape.

Exposing Appearance Properties in the API

Currently, the developer has no way of changing the control’s appearance, since all of the properties which govern the look of the control are hidden away on the renderer and aren’t exposed on the control’s API.

To fix this, add the following code just before the @end statement of RWKnobControl.h:

/**
 Specifies the angle of the start of the knob control track. Defaults to -11π/8
 */
@property (nonatomic, assign) CGFloat startAngle;
 
/**
 Specifies the end angle of the knob control track. Defaults to 3π/8
 */
@property (nonatomic, assign) CGFloat endAngle;
 
/**
 Specifies the width in points of the knob control track. Defaults to 2.0
 */
@property (nonatomic, assign) CGFloat lineWidth;
 
/**
 Specifies the length in points of the pointer on the knob. Defaults to 6.0
 */
@property (nonatomic, assign) CGFloat pointerLength;

Just as before, there are plenty of comments to assist developers via tooltips when they use the control. The four properties are fairly straightforward and simply proxy for the properties in the renderer. Since the control itself doesn’t actually need backing variables for these properties, it can rely on the renderer to store the values instead.

Switch to RWKnobControl.m and add the following just before initWithFrame::

@dynamic lineWidth;
@dynamic startAngle;
@dynamic endAngle;
@dynamic pointerLength;

The @dynamic statement tells the compiler not to bother synthesizing the properties since getters and setters will be provided manually. To do that, add the following code after setValue::

- (CGFloat)lineWidth
{
    return _knobRenderer.lineWidth;
}
 
- (void)setLineWidth:(CGFloat)lineWidth
{
    _knobRenderer.lineWidth = lineWidth;
}
 
- (CGFloat)startAngle
{
    return _knobRenderer.startAngle;
}
 
- (void)setStartAngle:(CGFloat)startAngle
{
    _knobRenderer.startAngle = startAngle;
}
 
- (CGFloat)endAngle
{
    return _knobRenderer.endAngle;
}
 
- (void)setEndAngle:(CGFloat)endAngle
{
    _knobRenderer.endAngle = endAngle;
}
 
- (CGFloat)pointerLength
{
    return _knobRenderer.pointerLength;
}
 
- (void)setPointerLength:(CGFloat)pointerLength
{
    _knobRenderer.pointerLength = pointerLength;
}

The above code looks a little tedious, but it’s actually quite simple: for each of the visualization properties you created on the knob control, pass the setters and getters straight through to their equivalent properties on the renderer. Easy peasy! :]

Since you included defaults in the commented documentation for your API, you’ll need to be a good control developer and set them.

Update createKnobUI to match the code below:

- (void)createKnobUI
{
    _knobRenderer = [[RWKnobRenderer alloc] init];
    [_knobRenderer updateWithBounds:self.bounds];
    _knobRenderer.color = self.tintColor;
    // Set some defaults
    _knobRenderer.startAngle = -M_PI * 11 / 8.0;
    _knobRenderer.endAngle = M_PI * 3 / 8.0;
    _knobRenderer.pointerAngle = _knobRenderer.startAngle;
    _knobRenderer.lineWidth = 2.0;
    _knobRenderer.pointerLength = 6.0;
    // Add the layers
    [self.layer addSublayer:_knobRenderer.trackLayer];
    [self.layer addSublayer:_knobRenderer.pointerLayer];
}

The above code simply adds two lines to the set of defaults: lineWidth and pointerLength.

Build and run your project; the control looks a bit more useful now:

buildandrun3

To test that the new API bits are working as expected, add the following code to the end of viewDidLoad in RWViewController.m:

_knobControl.lineWidth = 4.0;
_knobControl.pointerLength = 8.0;

Build and run your project again; you’ll see that the line thickness and the length of the pointer have both increased, as shown below:

buildandrun4

Changing Your Control’s Color

You may have noticed that you didn’t create any color properties on the public API of the control — and for good reason. iOS 7 provides a new property on UIView: tintColor. In fact, you’re already using it to set the color of the knob in the first place — check the _knobRenderer.color = self.tintColor; line in createKnobUI if you don’t believe us. :]

So you might expect that adding the following line to the end of viewDidLoad inside RWViewController will change the color of the control:

self.view.tintColor = [UIColor redColor];

If you add the code above and build and run your project, you’ll quickly be disappointed. However, the UIButton has updated appropriately, as demonstrated below:

pre-tint-color

Although you’re setting the renderer’s color when the UI is created, it won’t be updated when the tintColor changes. Luckily, this is really easy to fix.

Add the following method to RWKnobControl.m, just after setValue::

- (void)tintColorDidChange
{
    _knobRenderer.color = self.tintColor;
}

Whenever you change the tintColor property of a view, the render calls tintColorDidChange on all views beneath the current view in the view hierarchy that haven’t had their tintColor property set manually. So to listen for tintColor updates anywhere above the view in the current hierarchy, all you have to do is implement tintColorDidChange in your code and update the view’s appearance appropriately.

Build and run your project; you’ll see that the red tint has been picked up by your control as shown below:

buildandrun5

Setting the Control’s Value Programmatically

Although your knob looks pretty nice, it doesn’t actually do anything. In this next phase you’ll modify the control to respond to programmatic interactions — that is, when the value property of the control changes.

At the moment, the value of the control is saved when the value property is modified directly or when you call setValue:animated:. However, there isn’t any communication with the renderer, and the control won’t re-render.

The renderer has no concept of value; it deals entirely in angles. You’ll need to update setValue:animated: in RWKnobControl so that it converts the value to an angle and passes it to the renderer.

Open RWKnobControl.m and update setValue:animated: so that it matches the following code:

- (void)setValue:(CGFloat)value animated:(BOOL)animated
{
    if(value != _value) {
        // Save the value to the backing ivar
        // Make sure we limit it to the requested bounds
        _value = MIN(self.maximumValue, MAX(self.minimumValue, value));
 
        // Now let's update the knob with the correct angle
        CGFloat angleRange = self.endAngle - self.startAngle;
        CGFloat valueRange = self.maximumValue - self.minimumValue;
        CGFloat angleForValue = (_value - self.minimumValue) / valueRange * angleRange + self.startAngle;
        _knobRenderer.pointerAngle = angleForValue;
    }
}

The code above now works out the appropriate angle for the given value by mapping the minimum and maximum value range to the minimum and maximum angle range and sets the pointerAngle property on the renderer. Note you’re ignoring the value of animated at the moment — you’ll fix this in the next section.

Although the pointerAngle property is being updated, it doesn’t yet have any effect on your control. When the pointer angle is set, the layer containing the pointer should rotate to the specified angle to give the impression that the pointer has moved.

Add the following method before the @end statement in RWKnobRenderer.m:

- (void)setPointerAngle:(CGFloat)pointerAngle
{
    self.pointerLayer.transform = CATransform3DMakeRotation(pointerAngle, 0, 0, 1);
}

This simply creates a rotation transform which rotates the layer around the z-axis by the specified angle.

The transform property of CALayer expects to be passed a CATransform3D, not a CGAffineTransform like UIView. This means that you can perform transformations in three dimensions.

CGAffineTransform uses a 3×3 matrix and CATransform3D uses a 4×4 matrix; transforming z-axis requires the extra values. At their core, 3D transformations are simply matrices being multiplied by other matrices. You can read more about matrix multiplication in this Wikipedia article.

To demonstrate that your transforms work, you’re going to link the UISlider present in the starter project with the knob control in the view controller. As you adjust the control, the value will change appropriately.

The UISlider has already been linked to handleValueChanged:, so all you need to do is replace handleValueChanged: in RWViewController.m with the following:

- (IBAction)handleValueChanged:(id)sender {
    _knobControl.value = self.valueSlider.value;
}

Build and run your project; change the value of the UISlider and you’ll see the pointer on the knob control move to match as shown below:

buildandrun6

There’s a little bonus here — your control is animating, despite the fact that you haven’t started coding any of the animations yet! What gives?

CoreAnimation is quietly calling implicit animations on your behalf. When you change certain properties of CALayer — including transform — the layer animates smoothly from the current value to the new value.

Usually this functionality is really cool; you get nice looking animations without doing any work. However, you want a little more control, so you’ll animate things yourself.

Update setPointerAngle: in RWKnobRenderer.m as follows:

- (void)setPointerAngle:(CGFloat)pointerAngle
{
    [CATransaction new];
    [CATransaction setDisableActions:YES];
    self.pointerLayer.transform = CATransform3DMakeRotation(pointerAngle, 0, 0, 1);
    [CATransaction commit];
}

To prevent these implicit animations, you wrap the property change in a CATransaction and disable animations for that interaction.

Build and run your app once more; you’ll see that as you move the UISlider, the knob follows instantaneously.

Animating Changes in the Control’s Value

At the moment, setting animated = YES has no effect on your control. To enable this bit of functionality, you need to add the concept of animating angle changes to the renderer.

Add the following method signature to RWKnobRenderer.h, just before the @end statement:

- (void)setPointerAngle:(CGFloat)pointerAngle animated:(BOOL)animated;

Add the following implementation before the @end statement of RWKnobRenderer.m:

- (void)setPointerAngle:(CGFloat)pointerAngle animated:(BOOL)animated
{
    [CATransaction new];
    [CATransaction setDisableActions:YES];
    self.pointerLayer.transform = CATransform3DMakeRotation(pointerAngle, 0, 0, 1);
    if(animated) {
        // Provide an animation
        // Key-frame animation to ensure rotates in correct direction
        CGFloat midAngle = (MAX(pointerAngle, _pointerAngle) -
                            MIN(pointerAngle, _pointerAngle) ) / 2.f +
                            MIN(pointerAngle, _pointerAngle);
        CAKeyframeAnimation *animation = [CAKeyframeAnimation 
                                           animationWithKeyPath:@"transform.rotation.z"];
        animation.duration = 0.25f;
        animation.values = @[@(_pointerAngle), @(midAngle), @(pointerAngle)];
        animation.keyTimes = @[@(0), @(0.5), @(1.0)];
        animation.timingFunction = [CAMediaTimingFunction 
                                     functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        [self.pointerLayer addAnimation:animation forKey:nil];
    }
    [CATransaction commit];
    _pointerAngle = pointerAngle;
}

This method looks complex, but it’s fairly simple when you break it down. If you ignore the if(animated) section, then the method is identical to setAngle: which you coded earlier.

The difference here is when animated is equal to YES; if you had left this section with its implicit animation, the direction of rotation would be chosen to minimize the distance travelled. This means that animating between 0.98 and 0.1 wouldn’t rotate your layer counter-clockwise, but instead rotate clockwise over the end of the track, and into the bottom, which is not what you want!

In order to specify the rotation direction, you need to use a key-frame animation. That’s simply an animation which has additional points to animate through — not just the start and end points.

CoreAnimation supports key frame animation; in the above method, you’ve created a new CAKeyFrameAnimation and specified that the property to animate is the rotation around the z-axis with transform.rotation.z as its keypath.

Next, you specify three angles through which the layer should rotate: the start point, the mid-point and finally the end point. Along with that, there’s an array specifying the normalized times at which to reach those values. Adding the animation to the layer ensures that once the transaction is committed then the animation will start.

Now you can use standard method chaining: replace setPointerAngle: with the following:

- (void)setPointerAngle:(CGFloat)pointerAngle
{
    [self setPointerAngle:pointerAngle animated:NO];
}

Now that the renderer knows how to animate your control, you can update setValue:animated: of RWKnobControl.m to use your renderer rather than the property.

Replace the line below:

_knobRenderer.pointerAngle = angleForValue;

with the following:

[_knobRenderer setPointerAngle:angleForValue animated:animated];

In order to see this new functionality in action, you can use the “Random Value” button which is part of the app’s main view controller. This button causes the slider and knob controls to move to a random value, and uses the current setting of the animate UISwitch to determine whether or not the change to the new value should be instantaneous or animated.

Update handleRandomButtonPressed: of RWViewController.m to match the following:

- (IBAction)handleRandomButtonPressed:(id)sender {
    // Generate random value
    CGFloat randomValue = (arc4random() % 101) / 100.f;
    // Then set it on the two controls
    [_knobControl setValue:randomValue animated:self.animateSwitch.on];
    [self.valueSlider setValue:randomValue animated:self.animateSwitch.on];
}

The above method generates a random value between 0.00 and 1.00 and sets the value on both controls. It then inspects the on property of animateSwitch to determine whether or not to animate the transition to the new value.

Build and run your app; tap the Random Value button a few times with the animate switch toggled on, then tap the Random Value button a few times with the animate switch toggled off to see the difference the animated parameter makes.

Using Key-value Observing

Key-value observing (or KVO for short) lets you receive notification of changes to properties of NSObject instances. Although it’s not necessarily the interaction pattern of choice with a UI control, it’s certainly a viable option — with some interesting consequences!

To demonstrate this, you’re going to wire up the large label in the app’s main view controller to show the current value selected by the knob control.

Open up RWViewController.m and add the following to the end of viewDidLoad:

[_knobControl addObserver:self forKeyPath:@"value" options:0 context:NULL];

This registers that the view controller would like to be informed whenever the value property on the _knobControl object changes.

To receive these notifications, add the following method before the @end statement in RWViewController.m:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if(object == _knobControl && [keyPath isEqualToString:@"value"]) {
        self.valueLabel.text = [NSString stringWithFormat:@"%0.2f", _knobControl.value];
    }
}

This method first checks that the notification is from the correct property on the knob control then updates the text label to display the new value.

Build and run your app; move the UISlider control around and you’ll see the label’s value update as shown below:

buildandrun8

That looks fine. However, click the Random Value button, and even though the slider and knob control both update, the value in the label doesn’t! What’s going on?

The controls in your app use different methods to set the value on the knob control. UISlider uses the setValue: property setter method, whereas the random value button uses the setValue:animated: method of your API.

Key-value observation is part of NSObject, true, but it’s only wired to the synthesized setter methods — that is, setValue:. It’s not wired to the new method you added to deal with the animation parameter.

In order to fix this, you need to specify that the knob control will manage its own KVO notifications for the value property.

Add the following method before the end of the @end statement in RWKnobControl.m:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    if ([key isEqualToString:@"value"]) {
        return NO;
    } else {
        return [super automaticallyNotifiesObserversForKey:key];
    }
}

This method exists in NSObject and lets you override the default behavior of the object. Here you check for a specific key, and if it’s equal to value, then return NO, specifying that the notifications will be handled manually.

NSObject has a couple of methods to call that manually fire the notifications: willChangeValueForKey: and didChangeValueForKey:.

In RWKnobControl.m, update setValue:animated: so that it matches the following:

- (void)setValue:(CGFloat)value animated:(BOOL)animated
{
    if(value != _value) {
        [self willChangeValueForKey:@"value"];
        // Save the value to the backing ivar
        // Make sure we limit it to the requested bounds
        _value = MIN(self.maximumValue, MAX(self.minimumValue, value));
 
        // Now let's update the knob with the correct angle
        CGFloat angleRange = self.endAngle - self.startAngle;
        CGFloat valueRange = self.maximumValue - self.minimumValue;
        CGFloat angleForValue = (_value - self.minimumValue) / valueRange * angleRange + self.startAngle;
        [_knobRenderer setPointerAngle:angleForValue animated:animated];
        [self didChangeValueForKey:@"value"];
    }
}

The above code adds only two lines to the previous implementation: one call each to willChangeValueForKey: and didChangeValueForKey:. Before you set the value, you call willChangeValueForKey:, and then didChangeValueForKey: once the value has been set.

Build and run your app; tap the Random Value button, and you’ll see that the value of the label now updates as expected. The control is now sending KVO notifications for both of the value change methods.

Responding to Touch Interaction

The knob control you’ve built responds extremely well to programmatic interaction, but that alone isn’t terribly very useful for a UI control. In this final section you’ll see how to add touch interaction using a custom gesture recognizer.

When you touch the screen of an iOS device, a series of UITouch events are delivered to appropriate objects by the operating system. When a touch occurs inside of a view with one or more gesture recognizers attached, the touch event is delivered to the gesture recognizers for interpretation. Gesture recognizers determine whether a given sequence of touch events matches a specific pattern; if so, they send an action message to a specified target.

Apple provides a set of pre-defined gesture recognizers, such as tap, pan and pinch. However, there’s nothing to handle the single-finger rotation you need for the knob control. Looks like it’s up to you to create your own custom gesture recognizer.

Create a new class by clicking File\New\File… and selecting iOS\Cocoa Touch\Objective-C class. On the next screen, specify the name as RWRotationGestureRecognizer, and that the class will inherit from UIPanGestureRecognizer. Choose the KnobControl directory and click Create on the final screen of the wizard.

This custom gesture recognizer will behave like a pan gesture recognizer; it will track a single finger dragging across the screen and update the location as required. For this reason, it subclasses UIPanGestureRecognizer.

Open RWRotationGestureRecognizer.h and update the interface with the following new property:

@interface RWRotationGestureRecognizer : UIPanGestureRecognizer
 
@property (nonatomic, assign) CGFloat touchAngle;
 
@end

touchAngle represents the angle of the line which joins the current touch point to the center of the view to which the gesture recognizer is attached, as demonstrated in the following diagram:

GestureRecogniserDiagram

There are three methods of interest when subclassing UIGestureRecognizer; they represent the time that the touches begin, the time they move and the time they end.

Add the following import statement to the top of RWRotationGestureRecognizer.m:

#import <UIKit/UIGestureRecognizerSubclass.h>

You’re only interested when the gesture starts and when the user’s finger moves on the screen.

Add the following two methods between the @implementation and @end statements in RWRotationGestureRecognizer.m:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    [self updateTouchAngleWithTouches:touches];
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    [self updateTouchAngleWithTouches:touches];
}

Both of these methods call through to their super equivalent, and then call a utility function. Which you’ll add now, immediately below the methods you added above:

- (void)updateTouchAngleWithTouches:(NSSet *)touches
{
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self.view];
 
    self.touchAngle = [self calculateAngleToPoint:touchPoint];
}
 
- (CGFloat)calculateAngleToPoint:(CGPoint)point
{
    // Offset by the center
    CGPoint centerOffset = CGPointMake(point.x - CGRectGetMidX(self.view.bounds),
                                       point.y - CGRectGetMidY(self.view.bounds));
    return atan2(centerOffset.y, centerOffset.x);
}

updateTouchAngleWithTouches: takes the NSSet of touches and extracts one using anyObject. It then uses locationInView: to translate the touch point into the coordinate system of the view associated with this gesture recognizer. It then updates the new touchAngle property using calculateAngleToPoint:, which uses some simple geometry to find find the angle as demonstrated below:

AngleCalculation

x and y represent the horizontal and vertical positions of the touch point within the control. The tangent of the touch angle is equal to h / w, so to calculate the touchAngle all you need to do is establish the following lengths:

  • h = y - (view height) / 2 (since the angle should increase in a clockwise direction)
  • w = x - (view width) / 2

calculateAngleToPoint: performs this calculation for you, and returns the angle required.

Your gesture recognizer should only work with one touch at a time. Add the following constructor override immediately after the @implementation statement:

- (id)initWithTarget:(id)target action:(SEL)action
{
    self = [super initWithTarget:target action:action];
    if(self) {
        self.maximumNumberOfTouches = 1;
        self.minimumNumberOfTouches = 1;
    }
    return self;
}

This constructor sets 1 as the the default number of touches to which the recognizer will respond.

Wiring Up the Custom Gesture Recognizer

Now that you’ve completed the custom gesture recognizer, you just need to wire it up to the knob control. Add the following import to the top of RWKnobControl.m:

#import "RWRotationGestureRecognizer.h"

Next, add an instance variable to represent the gesture recognizer by updating the @implementation statement to look like the following:

@implementation RWKnobControl {
    RWKnobRenderer *_knobRenderer;
    RWRotationGestureRecognizer *_gestureRecognizer;
}

Add the gesture recognizer inside the if statement of initWithFrame:as follows:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        _minimumValue = 0.0;
        _maximumValue = 1.0;
        _value = 0.0;
        _continuous = YES;
        _gestureRecognizer = [[RWRotationGestureRecognizer alloc] initWithTarget:self 
                                                                          action:@selector(handleGesture:)];
        [self addGestureRecognizer:_gestureRecognizer];
 
        [self createKnobUI];
    }
    return self;
}

The two lines added create a gesture recognizer in the familiar way: simply create a recognizer, specify where it should callback when activated then add it to the view.

Still in RWKnobControl.m, add the following method before the @end statement to handle the callbacks from the gesture you’ve just created:

- (void)handleGesture:(RWRotationGestureRecognizer *)gesture
{
    // 1. Mid-point angle
    CGFloat midPointAngle = (2 * M_PI + self.startAngle - self.endAngle) / 2
                             + self.endAngle;
 
    // 2. Ensure the angle is within a suitable range
    CGFloat boundedAngle = gesture.touchAngle;
    if(boundedAngle > midPointAngle) {
        boundedAngle -= 2 * M_PI;
    } else if (boundedAngle < (midPointAngle - 2 * M_PI)) {
        boundedAngle += 2 * M_PI;
    }
    // 3. Bound the angle to within the suitable range
    boundedAngle = MIN(self.endAngle, MAX(self.startAngle, boundedAngle));
 
    // 4. Convert the angle to a value
    CGFloat angleRange = self.endAngle - self.startAngle;
    CGFloat valueRange = self.maximumValue - self.minimumValue;
    CGFloat valueForAngle = (boundedAngle - self.startAngle) / angleRange
                             * valueRange + self.minimumValue;
 
    // 5. Set the control to this value
    self.value = valueForAngle;
}

This method looks quite long and complicated, but the concept is pretty simple – it simply extracts the angle from the custom gesture recognizer, converts it to the value represented by this angle on the knob control, and then sets the value which triggers the UI updates.

Going through the commented sections of the code above, you’ll find the following:

  1. First, you calculate the angle which represents the ‘midpoint’ between the start and end angles. This is the angle which is not part of the knob track, and instead represents the angle at which the pointer should flip between the maximum and minimum values.
  2. The angle calculated by the gesture recognizer will be between and π, since it uses the inverse tangent function. However, the angle required for the track should be continuous between the startAngle and the endAngle. Therefore, create a new boundedAngle variable and adjust it to ensure that it remains within the allowed ranges.
  3. Update boundedAngle so that it sits inside the specified bounds of the angles.
  4. Convert the angle to a value, just as you converted it in the setValue:animated: method before.
  5. Finally, set the knob control’s value to calculated value.

Build and run your app; play around with your knob control to see the gesture recognizer in action. The pointer will follow your finger as you move it around the control – how cool is that? :]

Sending Action Notifications

As you move the pointer around, you’ll notice that the UISlider doesn’t update. You’ll wire this up to use the target-action pattern which is an inherent part of UIControl.

Open RWViewController.m and add the following code to the end of viewDidLoad:

// Hooks up the knob control
[_knobControl addTarget:self
                 action:@selector(handleValueChanged:)
       forControlEvents:UIControlEventValueChanged];

This is the standard code you’ve used before to add a listener to a UIControl; here you’re listening for value-changed events.

handleValueChanged: currently only handles changes in the value of valueSlider. Update handleValueChanged: as shown below:

- (IBAction)handleValueChanged:(id)sender {
    if(sender == self.valueSlider) {
        _knobControl.value = self.valueSlider.value;
    } else if(sender == _knobControl) {
        self.valueSlider.value = _knobControl.value;
    }
}

handleValueChanged:now checks which control invoked it and sets the value on the other control to match. If the user changes the value on the knob control, then the slider updates appropriately, and vice versa.

Build and run your app; move the knob around and…nothing has changed. Whoops. You haven’t actually fired the event from within the knob control itself.

Time to fix that!

Open RWKnobControl.m and add the following code to the end of handleGesture::

// Notify of value change
if (self.continuous) {
    [self sendActionsForControlEvents:UIControlEventValueChanged];
} else {
    // Only send an update if the gesture has completed
    if(_gestureRecognizer.state == UIGestureRecognizerStateEnded
       || _gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }
}

At the beginning of this tutorial you added the continuous property to the API so that the knob control API would resemble that of UISlider. This is the first and only place that you need to use it.

If continuous is set to YES, then the event should be fired every time that the gesture sends an update, so call sendActionsForControlEvents:.

In continuous is set to NO, then the event should only fire when the gesture ends or is cancelled. Since the control is only concerned with value changes, the only event you need to handle is UIControlEventValueChanged.

Build and run your app again; move the knob around once again and you’ll see the UISlider move to match the value on the knob. Success!

Where to Go From Here?

Your knob control is now fully functional and you can drop it into your apps to enhance their look and feel. However, there’s a lot of ways that you could extend your control:

  • Add extra configurability to the appearance of the control – perhaps you could allow an image to be used for the pointer.
  • Integrate a label displaying the current value of the control into the center of the knob.
  • Ensure that a user can only interact with the control if their first touch is on the pointer.
  • At the moment, if you resize the knob control, the layers won’t be re-rendered. You can add this functionality with just a few lines of code.

These suggestions are quite good fun, and will help you hone your skills with the different features of iOS you’ve encountered in this custom UI tutorial for iOS 7. And the best part is that you can apply what you’ve learned in other controls that you build.

You can download a zip file of the completed project, or alternatively access the git repository on GitHub. The git repo has commits for every build-and-run step, so you can check your code as you go.

I’d love to hear your comments or questions in the forums below!

Custom Control for iOS Tutorial: A Reusable Knob is a post from: Ray Wenderlich

The post Custom Control for iOS Tutorial: A Reusable Knob appeared first on Ray Wenderlich.


Readers’ App Reviews – January 2014

$
0
0
FamilyCircus

New Year, New Apps!

The new year is here, and it’s brought with it a bunch of awesome new apps!

This month, we have some great apps to check out:

  • A couple a dev tools you’ll want to have
  • A few apps you wish you had at Christmas
  • More fun games; you guys just can’t stop!
  • And some great apps for kids.

Keep reading for another year of awesome apps from your fellow readers.

Your Amazing Family Circus

FamilyCircus
Your Amazing Family Circus was a huge hit with my nephew.

This is an adorable app that lets you customize an interactive story with photos and names from family members. Ever wonder what crazy Uncle Bob would look like as a toy soldier?

The story is very cute and rather funny. They gone out of their way to add lots of interactions on each page to let you play with the characters and props.

Seeing family members faces inside a book is great experience. This is customization done right.

Snow Me

SnowMe
Down here in the South, snow is a rare sight. But who doesn’t love snow?

Snow Me makes our southern dreams come true! Snow me lets you take pictures with a live snow effect and a few filters to capture the mood. Just shake your phone like a snow globe and watch the white come down!

Its a clean, easy to use app thats certain to bring some joy to the season.

Music4Kids

Music4Kids
Music4Kids is a fantastic application that will help teach kids how to read and write music.

Notes slide across the screen on a bar and kids can simply tap notes to play them. If they do it in order you’ll hear a fun song. There are over 140 musical challenges to keep you busy and learning.

But the fun doesn’t stop there, there is a composing section too where you can make your own music. Just tap on the bar where you’d like a note, and drag them around until you’ve created your masterpiece. Mario Paint, step aside!

Tileout

Tileout
Tileout is as addictive as it is simple.

Tileout is a puzzle game where you win by process of elimination. Hold on now, you aren’t an expert yet. Order matters. You don’t win unless you can manage to leave only one of each color. But you can only remove a tile if its touching a matching tile.

Hundreds of levels, colorful graphics, and Game Center make this a home run.

Universal Fonts

Universal Fonts
Universal Fonts is an app to help you step up your boring iMessages and Twitter posts.

Universal Fonts lets you type anything you want, in 40 fonts, and share it anywhere! Your next iMessage to your mom is going to blow her mind!

Universal fonts can be used anywhere, from your contact entries if you like messing with Siri, to your calendar if you want to spruce up that meeting.

Pixol

Pixol
Pixol is a photo puzzle game with great polish and hand picked muses.

Pixol uses the Instagram feeds of handpicked photographers who take great shots. You’ll get to decode their images while Pixol gives you the challenge. Puzzle pieces fade, slide, shake, and more to keep you on your toes.

You can even play with your own Instagram photos or those of your friends. Its a great way to enjoy Instagram and have some fun at the same time.

App Hatcher

App hatcher
We all have endless app ideas. What if there was a tool that could help us catelog, prioritize, and get them done?

Whats that? You say such an app already exists? No you don’t, I saw it first! App Hatcher is an app we all need in our pocket.

App Hatcher lets you keep track of app meta data as it strikes you from names to keywords. It also lets you keep a todo list of features so you can keep track while working on a different app everyday.

App Hatcher a simple app, and just what we need to keep those app a month resolutions I know we all made. ;]

Nasty Cat Christmas Mission

Nasty Cat
I knew there was something keeping Santa away from my chimney!

Nasty Cat Christmas Mission is the reason we’re all dog people. This evil little cat is swatting away tons of presents before they reach the chimney! But I can see why he does it, its a ton of fun.

This game is cute and addictive as you pounce from roof top to roof top in search of toys parachuting down. Santa isn’t going down without a fight though, the presents get bigger, faster, and before long fill the sky. You’ll have to be crafty with your powerups if you want to ruin Christmas this year.

Sumit & Be Fast

Sumit
Do you want to be able to add numbers at the speed of light AND have fun?

Perfect, Sumit & Be Fast is for you. The game is simple to play, but your brain will be sore. Just tap bubbles to create the required sum. But you’re on a deadline, literally, if a bubble touches the bottom you’re dead! Do your math quickly or face the consequences.

Oh and if you thought you already knew addition, don’t worry because before long the numbers will be spinning, fading, and all around making math harder than you ever knew.

Procuses

Procuses
“Thats a developer thing.” ~ Procuses

My favorite excuse for everything I do. And now it can be yours too! Procuses gives us the excuses we all need to get away with it!

And its not just for developers, designers and project managers can get in on the procrastination too! I don’t think I’ll be getting much done this week. ;]

Honorable Mentions

I had some apps I couldn’t review. Don’t feel bad, it wasn’t a popularity contest or even a rating contest. Forum members get priority, among other factors. I make time to try out every app that comes across my screen, one day I’ll find time to write about them all too!

Deep Sleep Alarm

Motion Control Tile Matching Game (MCTMG)

Light Square

When Is It

Name your color

PricePer

Zombie World

Best word

smart Decisions

Alter Me

Dodge The Balls

Copalive

UC Browser+ HD

InstaFusion Image Blender

Totally Catholic Trivia, Full Version

SafetyNet

PaintMyMinis

El Tesoro de Martín

Superbloke

Where To Go From Here?

As expected, I really enjoyed your apps – it’s great fun to see what fellow readers make each month.

If you’ve never made an app, we’ve got you covered! Check out our free tutorials to become an iOS star. What are you waiting for – I want to see your app next month!

If you’ve already made the next great app, let me know about it! Submit here!

Readers’ App Reviews – January 2014 is a post from: Ray Wenderlich

The post Readers’ App Reviews – January 2014 appeared first on Ray Wenderlich.

iOS 7 by Tutorials Print Version Now Available!

$
0
0
iOS 7 by Tutorials Print Version

iOS 7 by Tutorials Print Version Now Available!

Good news everyone – the print version of iOS 7 by Tutorials is now available for purchase!

We’re really excited about how the book has turned out – it looks great in print.

Keep reading for some more pictures of the book, learn about a discount for iOS 7 by Tutorials PDF customers, and enter for a book giveaway!

The Book

The print version is pretty huge – 803 pages! Here’s a picture of what it looks like next to my iPhone 5:

Side view of iOS 7 by Tutorials

We wanted to keep the book just one volume, so we made the 5 bonus chapters downloadable so everything else would fit comfortably. Reading is pleasing to the eye, and it’s really handy to be able to thumb through to whatever section you’re looking for.

Inside iOS 7 by Tutorials

Note that on our store page, we have two options for each product:

  • Just the PDF. Nothing’s changed here – if you prefer electronic books you can buy just the PDF same way you usually would.
  • The PDF + Print Version Bundle. This gives you the best of both worlds. It also saves you a lot of money – you’re effectively getting the printed books at a big discount compared to buying them separately.

Note we don’t offer an option for just the printed books. This is because we like to keep our books as up-to-date as we can, and we want to be able to give all our customers access to these updates (in PDF form). For example, iOS Apprentice PDF customers have received 4 major updates since it was first released (from iOS 3->4->5->6->7)!

Discount for iOS 7 by Tutorials PDF Customers

If you bought the PDF version of iOS 7 by Tutorials, you are eligible for a special discount on the print version, to effectively “upgrade” your order to the PDF+Print bundle.

I will send all PDF version customers an email with instructions for how to upgrade your purchase in a few minutes.

If for some reason you do not get the email, just contact me and we’ll get it sorted.

Note this discount will expire at the end of this month, so if you’re interested snag it fast! :]

iOS 7 by Tutorials Back Cover

The Giveaway

One last thing. To celebrate the launch, we’re giving away a copy of the book!

All you have to do to enter is leave a comment on this post – next Monday we’ll choose a random winner.

That’s it – we hope you all enjoy the print version of iOS 7 by Tutorials! If you’d like to pick up a copy just check out our store page.

iOS 7 by Tutorials Print Version Now Available! is a post from: Ray Wenderlich

The post iOS 7 by Tutorials Print Version Now Available! appeared first on Ray Wenderlich.

How To Make A Simple iPhone Game with Cocos2D 3.0 Tutorial

$
0
0
Pew pew ninja strike!

Pew pew ninja strike!

Note from Ray: This tutorial is a 2014 reboot of the classic How To Make A Simple iPhone Game Cocos2D 2.0 Tutorial.

What better way to celebrate the brand new 3.0 release of Cocos2D than with a fresh update to a classic tutorial!

Cocos2D 3.0 is the latest version of the hugely popular open source iOS framework for developing 2D games, including thousands of App Store games and many top 10 hits.

It has great sprite support, a deeply integrated version of the excellent Chipmunk2D physics library, OpenAL sound library, fun effects, and loads more.

In this Cocos2D 3.0 tutorial for beginners, you will learn how to create a simple and fun 2D game for your iPhone from start to finish. If you’ve previously followed the Cocos2D 2.0 Tutorial, then this may feel familiar however you will be utilizing the integrated physics engine to take it to the next level.

You can either follow along with this tutorial or just jump straight to the sample project source at the end. And yes, there will be blood, I mean ninjas!

Cocos2D vs Sprite Kit

Before you get started, you may be thinking, “Hey, why bother with Cocos2D now that I have Apple’s Sprite Kit? Do I really want to try anything else?”

Well, as with any game framework, there are a few pros and cons of Cocos2D.

Cocos2D Pros

  • Pro: You are not locked into the iOS ecosystem, want to port your game to Android? It’s your choice and you can now typically do it with Cocos2D in one line!
  • Pro: You can write your custom OpenGL effects code.
  • Pro: It’s Open Source, so if you want to change something or find out how it works, you can dig right into the source code.

Cocos2D Cons

  • Con: It’s not built into Xcode, so you will have to download and run the Cocos2D installer.
  • Con: It doesn’t currently have a built-in texture and particle tool editor (however, it does have great 3rd Party tool support).

Cocos2D has an established community and there are loads of existing tutorials, books and code samples out there. Cocos2D is developed and maintained by game developers who aim to make it easier for you to get on with making great games.

Installing Cocos2D

The new Cocos2D 3.0 Installer

Cocos2D 3.0 comes with a new installer, so getting started has never been easier!

Just download the latest Cocos2D installer (version 3 or later), open the DMG and double click the installer. This will automatically install the Cocos2D templates for Xcode and build the Cocos2D Xcode documentation.

You will see a bunch of messages as the installer runs, once finished it will open up the Cocos2D welcome page. Congrats – you’re now ready to work with Cocos2D!

Hello World

Let’s start by getting a simple Hello World project up and running by using the Cocos2D template that was installed in the previous step.

Open Xcode, select File\New Project, select the iOS\cocos2d v3.x\cocos2d iOS template and click Next:

Template Selection

Enter Cocos2DSimpleGame for the Product Name, select iPhone for Devices and click Next:

Project Options

Choose somewhere on your drive to save the project and click Create. Then click the play button to build & run the project as-is. You should see the following:

Default Template Screenshot

Tap the Simple Sprite button to reveal another test scene, which you will be working with in this tutorial:

Spinning Logo for Cocos2D 3.0

Cocos2D is organized into the concept of scenes which you can think about kind of like
“screens” for a game. The first screen with the HelloWorld menu is the IntroScene, and the second screen with the spinning Cocos2D logo is the HelloWorldScene. Let’s take a closer look at this.

Enter the Ninja!

Before the Ninja can make his grand entrance, you will need some artwork to work with…

First step, download the resource pack for this project. Unzip the file, and drag the ResourcePack folder into the Xcode project. Make sure that the “Copy items into destination group’s folder (if needed)” option is checked and that your Cocos2DSimpleGame target is selected.

Second, open HelloWorldScene.m. Remember, this is the code for the screen with the spinning Cocos2D logo, and this will be a nice spot to start building the game. Take a look at the template code before you modify it:

@implementation HelloWorldScene {
    // 1
    CCSprite *_sprite;
}
- (id)init {
    // 2
    self = [super init];
    if (!self) return(nil);
 
    // 3
    self.userInteractionEnabled = YES;
 
    // 4
    CCNodeColor *background = [CCNodeColor nodeWithColor:[CCColor colorWithRed:0.2f green:0.2f blue:0.2f alpha:1.0f]];
    [self addChild:background];
 
    // 5
    _sprite = [CCSprite spriteWithImageNamed:@"Icon-72.png"];
    _sprite.position  = ccp(self.contentSize.width/2,self.contentSize.height/2);
    [self addChild:_sprite];
 
    // 6
    CCActionRotateBy* actionSpin = [CCActionRotateBy actionWithDuration:1.5f angle:360];
    [_sprite runAction:[CCActionRepeatForever actionWithAction:actionSpin]];
 
    // 7
    CCButton *backButton = [CCButton buttonWithTitle:@"[ Menu ]" fontName:@"Verdana-Bold" fontSize:18.0f];
    backButton.positionType = CCPositionTypeNormalized;
    backButton.position = ccp(0.85f, 0.95f); // Top Right of screen
    [backButton setTarget:self selector:@selector(onBackClicked:)];
    [self addChild:backButton];
 
    return self;
}

Let’s go over this step-by-step:

  1. Declare a private instance variable for the sprite for the Cocos2D logo, makes it easy to access the sprite later on.
  2. Initialization of the HelloWorld scene.
  3. This is how you configure a scene to receive touch events in Cocos2D. This means that the touchBegan:withEvent: method will be called, which you’ll see later on in this file.
  4. Creates a CCNodeColor, which is simply a node that displays a single color (dark gray in this case). Once this node is created, it needs to be added to the scene with the addChild: method so you can see it. Now your scene has a background color!
  5. Create a CCSprite with the spriteWithImageNamed: method to load the image resource. The position of the sprite is set to the center of the screen by using the dimensions of the screen. Again this needs added to the scene using the addChild: method.
  6. Create a CCActionRotateBy action that will be used to spin the sprite 360 degrees and using the CCActionRepeatForever to repeat the rotation action. This action is then applied to the sprite using the runAction method. Actions are a very powerful feature of Cocos2D that will be discussed later.
  7. Create a CCButton that will navigate back to the IntroScene when clicked, you can be use this as a simple way to restart the scene.

OK, great! As a first step, let’s replace the spinning Cocos2D logo with a spinning ninja instead.

How do you think you’d do that? Here are some hints:

  • Have a look in the ResourcePack that you recently added to the project to find the ninja image.
  • You only have to modify one line of code!

Try and do it yourself if you can, but if you get stuck here’s the solution:

Solution Inside SelectShow>

That was easy, _sprite is a pretty obvious name however it may get a little confusing if you start using _sprite1, _sprite2 so change the name _sprite to _player. Find the first entry in @implementation:

@implementation HelloWorldScene {
    // 1
    CCSprite *_sprite;
}

And change this to:

@implementation HelloWorldScene {
    // 1
    CCSprite *_player;
}

Shortly after you do this, Xcode will flag the code as having 5 errors and highlight these lines in red. No need to worry, it’s just informing you that _sprite is not longer valid as you renamed the object to _player. So go ahead and change all the other _sprite references to _player.

Let’s see what ninjas do when they are not fighting monsters, build and run the project.

Spinning Ninja!

The spinning Cocos2D logo is now replaced with a spinning ninja. Thankfully ninjas can not get dizzy.

However, a ninja does train their entire life for combat, so next you will want to add some monsters to challenge your ninja!

A Wild Monster Appears

Next you want to add some monsters into your scene. A static monster is of course no challenge to an experienced ninja, so to make things a bit more interesting you will add some movement to the monsters. You will create the monsters slightly off screen to the right and set up a CCAction for them, telling them to move from right to left.

Add the following code to HelloWorldScene.m:

- (void)addMonster:(CCTime)dt {
 
    CCSprite *monster = [CCSprite spriteWithImageNamed:@"monster.png"];
 
    // 1
    int minY = monster.contentSize.height / 2;
    int maxY = self.contentSize.height - monster.contentSize.height / 2;
    int rangeY = maxY - minY;
    int randomY = (arc4random() % rangeY) + minY;
 
    // 2
    monster.position = CGPointMake(self.contentSize.width + monster.contentSize.width/2, randomY);
    [self addChild:monster];
 
    // 3
    int minDuration = 2.0;
    int maxDuration = 4.0;
    int rangeDuration = maxDuration - minDuration;
    int randomDuration = (arc4random() % rangeDuration) + minDuration;
 
    // 4
    CCAction *actionMove = [CCActionMoveTo actionWithDuration:randomDuration position:CGPointMake(-monster.contentSize.width/2, randomY)];
    CCAction *actionRemove = [CCActionRemove action];
    [monster runAction:[CCActionSequence actionWithArray:@[actionMove,actionRemove]]];
}

Let’s go over this step-by-step:

  1. Define a vertical range for the monster to spawn. When you place a sprite, by default you are placing the center of the sprite. So here you take the screen height and reduce it by the monsters height, so the monster doesn’t end up being cut off.
  2. Position the monster slightly off screen to the right, take the screen width and add the monster’s width to ensure it’s just hidden off screen.
  3. Now you want to decide how long it takes (in seconds) for the monster to move across the screen, let’s randomise it to make the monsters less predictable.
  4. You will use the Cocos2D action CCActionMoveTo: to animate moving the monster from the starting point (slightly off screen right) to the target destination point (slightly off screen left) causing it to move quickly across the screen from right to left.

You have already seen actions in action with the spinning however Cocos2D provides a lot of extremely handy built-in actions, such as move actions, rotate actions, fade actions, animation actions and many more. Here you use three actions on the monster:

  • CCActionMoveTo: This action is used to control the movement of the monster, in this case moving across the screen from right to left. The duration of the action is also specified which controls how long it takes for the monster to move across the screen. The shorter the duration the faster the monster will appear to move. In this case you randomize the duration between 2 and 4 seconds.
  • CCActionRemove: Handy action that removes a node from its parent, effectively “deleting it” from the scene. You use this action to remove the monster from the scene when it is no longer visible (slightly off screen left). This is very important because otherwise you would eventually have tens of thousands of monsters trying to take over your iPhone and consume all device resources.
  • CCActionSequence: The sequence action allows you to chain together a sequence of actions that are performed in order, one at a time. This way you can have the CCActionMoveTo action perform first and once it is complete perform the next action CCActionRemove. This is a very powerful action allowing you to create complex animation sequences.

Great, so now you have a method to add a monster to your scene. However one monster is hardly a challenge for an experienced ninja, let’s create a timed monster spawn method.

Cocos2D features a scheduler that allows you to setup callbacks every X.X seconds, so you can setup a monsters spawn generator to add new monsters every X seconds.

Open up HelloWorldScene.m and add the following code just after [super onEnter]; in the onEnter method.

[self schedule:@selector(addMonster:) interval:1.5];

This will add a Cocos2D scheduler timer to call the previously added addMonster: method every 1.5 seconds.

Note that when you created the addMonster method, there was an additional dt parameter. This stands for delta time, and represents the time difference between the previous and the current frame. The scheduler requires that every method it calls takes this as a parameter, however you will not use it in this tutorial.

Before you see these monsters in action, you will make a couple of changes. Let’s bring everyone out of the shadows a little. Why not change the background color from a dark grey to a lighter grey?

Solution Inside SelectShow>

Your ninja’s head must be spinning by now, can you stop him spinning and also move him over to the left a bit so he can prepare himself for the oncoming frontal assault?

Solution Inside SelectShow>

Build and run, and you should now see monsters fly across the screen!

Monsters Attack

Hadouken

Unfortunately your ninja is still not at a high enough level to cast a fireball, so you will need to rely on your expert throwing skills to defeat those evil (or possibly just misunderstood) monsters.

Grab yourself a shuriken and let’s add some projectile action.

You will be using the CCActionMoveTo: again, it’s not however quite as simple as moving to the touch point and starting from the _player.position. You want to throw the projectile across the screen in the direction of the touch. So you have to use a little math.

You have a smaller triangle created by the x and y offset from the origin point to the touch point. You just need to make a big triangle with the same ratio – and you know you want one of the endpoints to be off the screen.

To perform these calculations, it really helps if you have some basic vector math routines available (like methods to easily add and subtract vectors). Cocos2D includes a handy set of vector manipulation functions such as ccpAdd and ccpSub vectors.

If you’re unsure about any of the following calculations, check out this quick vector math explanation. I would personally recommend the excellent Khan Academy video tutorials on the subject.

You got the touch!

The HelloWorldScene templates already comes touch enabled as you saw in the init method.

// 3
self.userInteractionEnabled = YES;

To handle these touches you typically need to create a touchBegan: method however the default template kindly comes with a simple touchBegan method example.

Open HelloWorldScene.m and replace the current touchBegan: method with the following snippet:

- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    // 1
    CGPoint touchLocation = [touch locationInNode:self];
 
    // 2
    CGPoint offset    = ccpSub(touchLocation, _player.position);
    float   ratio     = offset.y/offset.x;
    int     targetX   = _player.contentSize.width/2 + self.contentSize.width;
    int     targetY   = (targetX*ratio) + _player.position.y;
    CGPoint targetPosition = ccp(targetX,targetY);
 
    // 3
    CCSprite *projectile = [CCSprite spriteWithImageNamed:@"projectile.png"];
    projectile.position = _player.position;
    [self addChild:projectile ];
 
    // 4
    CCActionMoveTo *actionMove   = [CCActionMoveTo actionWithDuration:1.5f position:targetPosition];
    CCActionRemove *actionRemove = [CCActionRemove action];
    [projectile runAction:[CCActionSequence actionWithArray:@[actionMove,actionRemove]]];
}

Let’s go over this step-by-step:

  1. You need to translate the screen touch into the scene’s coordinate system. There is a handy Cocos2D category on UITouch with locationInNode: that will do this.
  2. So as you can see, you have a small triangle created by the x and y offset from the origin point to the touch point. You just need to make a big triangle with the same ratio – and you know you want one of the endpoints to be off the screen.
  3. Create the projectile sprite and set it to start at the same position as the player. This is why you made _player a private instance variable so you could access it easily later.
  4. You should now be more familiar with the CCActionMoveTo now. You have the previously calculated target point and the duration is the time taken for the projectile to reach this point, so the lower it is, the faster your projectile will travel.

Build and run, and fire at will!

Attack the monsters.

Arggghhh these monsters are too strong, why wont they just die already!

Collision Detection and Physics

So you have a ninja, monsters and projectiles flying all over the screen. It looks good but it would be a lot more fun with a bit of impact, for that you need collision detection between your projectiles and your monsters.

One of the great new features of Cocos2D 3.0 is the deeply integrated physics engine, making this task a breeze. Physics engines are great for simulating realistic movement, however they are also very useful for handling collision detection.

You are now going to use the Cocos2D physics engine to determine when monsters and projectiles collide. There are four steps to do this:

  • Set up the physics world. A physics world is the simulation space for running physics calculations. You will set one up for the scene and modify a few properties like gravity. The physics world is then added to your scene. You will modify the existing game objects so they are added to the physics world so they will be part of the physics simulation.
  • Create physics bodies for each sprite. In Cocos2D you can associate a physics body to each sprite for collision detection purposes and set certain properties on it. Note that the physics body does not have to be the exact same shape as the sprite. Usually it’s a simpler shape such as a box or circle, rather than a pixel-perfect outline. Since that is generally good enough for most games and offers better physics performance.
  • Set CollisionType for each type of sprite. One of the handy features of Cocos2D physics is you don’t have to play around with integer bitmasks to define your collision types. You can set an easily readable string to define your collision types.
  • Set a collision handler delegate. By default when two physics bodies collide they will be handled by the physics simulation however you will want to do something when a projectile collides with a monster such as kill the monster. So you will be adding a collision handler delegate to handle the collision between your monster and projectile collision types.

Let’s get started. First, you will now need to add another private instance variable for you physics world.

Ensure you have HelloWorldScene.m open and add the following to the @implementation HelloWorldScene declaration after your CCSprite.

CCPhysicsNode *_physicsWorld;

Now you need to setup the physics simulation and add it to your scene, add the following code after the CCNodeColor in the init method.

_physicsWorld = [CCPhysicsNode node];
_physicsWorld.gravity = ccp(0,0);
_physicsWorld.debugDraw = YES;
_physicsWorld.collisionDelegate = self;
[self addChild:_physicsWorld];

Gravity is set to (0,0) as you are using the physics simulation primarily for collision detection. Cocos2D has some handy debug functionality, the debugDraw flag is really useful to help visualise your physics world. You will be able to see any physics bodies added to the simulation. You are also setting the collisionDelegate to self, this allows you to add collision Handlers to the scene and the physics simulation knows to look in HelloWorldScene for these handlers.

You will notice that Xcode will throw up a warning around the collisionDelegate line; this is easily resolved. Open HelloWorldScene.h and mark the interface as implenting the CCPhysicsCollisionDelegate.

@interface HelloWorldScene : CCScene <CCPhysicsCollisionDelegate>

Now you need to set up the player with a physics body and add the player to the _physicsWorld instead of directly to the scene.

Back in HelloWorldScene.m, find the following code in the init method:

[self addChild:_player];

Replace that code with the following:

_player.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, _player.contentSize} cornerRadius:0]; // 1
_player.physicsBody.collisionGroup = @"playerGroup"; // 2
[_physicsWorld addChild:_player];

Quick review of this code snippet:

  1. Creates a physics body, in this case a box using the player’s contentSize to create an bounding box rectangle around the player.
  2. Set physics body collisionGroup, by default everything will collide. If you set physics bodies to the same collisionGroup they will no longer collide with each other, this is handy when you have a player that is made up of multiple bodies but you don’t want these bodies to collide with each other, for example a player holding a weapon. You will use this to ensure the projectile does not collide with the player.

You have setup physics simulation and created a player physics body and added it to the simulation. Now see if you can do this yourself, by adding the monster to the physics simulation.

Solution Inside SelectShow>

This is nearly identical to creating the _player physics body, but I was being a little tricky as I introduced a new property. This time you are setting the collisionType property, this will be used in setting up a physics simulation collision delegate between the ‘monsterCollision’ and the ‘projectileCollsion’ collisionType.

You are nearly there now! This time, see if you can add the projectile to the simulation. I’ll give you a clue: it will use both the collisionType and the collisionGroup properties.

Solution Inside SelectShow>

Build and run, and you should see a lot of pretty pink boxes:

Physics!

The pink boxes around the sprites are created by the _physics property debugDraw. They are handy when you are first setting up your physics, so you can make sure that it’s all working as you expect. Notice that the box around the shuriken doesn’t look so great; it would much better if it used a circle.

There is of course a method you can use to create a circular body shape bodyWithCircleOfRadius: which is a much better fit for your projectile. Replace the projectile body code with the following:

projectile.physicsBody = [CCPhysicsBody bodyWithCircleOfRadius:projectile.contentSize.width/2.0f andCenter:projectile.anchorPointInPoints];

By default the center point will be at the bottom left of the sprite however you want the circle to be placed in the middle of your sprite.

Great, you have now modified your game objects to be part of the physics simulation. Now you really want to be able to execute some of your own code when the projectileCollision and monsterCollison collisionType make contact.

The Cocos2D physics engine has some really nice functionality to do this. Just add the following method into HelloWorldScene.m:

- (BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair monsterCollision:(CCNode *)monster projectileCollision:(CCNode *)projectile {
    [monster removeFromParent];
    [projectile removeFromParent];
    return YES;
}

That snippet of code is pretty powerful. When the physics simulation is set up, the physics engine will check for CCPhysicsCollisionDelegate methods and call them if they exist. The parameter names will be taken to be the collisionTypes you want to deal with yourself.

In this method you are completely removing both the ‘projectile’ and ‘monster’ nodes from the simulation and scene. You could of course add a score counter, apply a special effect or anything else you would like to do each time a projectile collides with monster.

Build and run and you finally should be able to destroy those monsters. Go go power ninja!

Finishing Touches

You’re pretty close now to having an extremely simple game now. But (Pew-Pew!), a bit of sound would be nice right about now.

Cocos2D uses the OpenAL sound library for sound support. No need to include any headers manually, it’s all good to go.

History Lesson: For the Cocos2D historians out there wondering what happened to SimpleAudioEngine, that sound library has now been superseded by Open AL.

Playing a SFX is really easy, time to add a SFX every time the ninja throws his shuriken. The sound may not be a 100% accurate representation of a ninja throwing a shuriken :-)

Add the following to the end of your projectile creating touchBegan: method.

[[OALSimpleAudio sharedInstance] playEffect:@"pew-pew-lei.caf"];

Time to add a little mood music, add this line to init, right after setting userInteractionEnabled to YES:

[[OALSimpleAudio sharedInstance] playBg:@"background-music-aac.caf" loop:YES];

As a final step, comment out this line in init to turn off debug drawing:

_physicsWorld.debugDraw = YES;

Build and run, pew-pew. You now have sound, how easy was that?

Finished Cocos2D 3.0 game

Where To Go From Here?

And that’s a wrap! Here’s the full code for the Cocos2D 3.0 game that you have developed thus far.

You have covered a lot of ground in this Cocos2D 3.0 tutorial, introducing you to the key areas of Cocos2D. From small beginnings come great things, so why not take things a bit further and improve upon the project?

  • Have a look at the code in the IntroScene you will see how to create a label. Why not try adding a counter to your game every time you destroy a monster?

  • What should happen when a monster collides with the ninja?

  • Want to try some more advanced animation, In Xcode Open Help\Documentation and API References and search for CCAction you will get a list of all the actions available that you can apply to any of your game character objects.

  • I’ve never seen a shuriken that didn’t spin, how about a little rotation (I’m sure we’ve seen an example of that earlier in this tutorial). Hint: You can use runAction more than once on a node. It doesn’t have to be in a sequence.

If you want to learn more about Cocos2D, the official Cocos2D forum is a friendly place to ask questions and learn from years of experience. My username is @cocojoe feel free to come in and say hi.

If you have any questions or comments about this tutorial, please join the discussion below!

How To Make A Simple iPhone Game with Cocos2D 3.0 Tutorial is a post from: Ray Wenderlich

The post How To Make A Simple iPhone Game with Cocos2D 3.0 Tutorial appeared first on Ray Wenderlich.

How to Create an Interactive Children’s Book for the iPad

$
0
0
Learn how to create an interactive children's book for the iPad!

Learn how to create an interactive children’s book for the iPad!

With the iPad, it’s never been a better time to be a kid!

The iPad allows developers to create beautiful interactive children’s books that simply cannot be replicated in any other medium. For some examples, check out The Monster at the End of This Book, Bobo Explores Light, and Wild Fables.

In the old days, developers had to use third party frameworks like Cocos2d to get the job done. But now, Apple has provided their own 2D framework called Sprite Kit, which is perfect for creating these types of books.

In this tutorial, you’ll create an interactive children’s book using the Sprite Kit framework, where you’ll learn how to add objects to scenes, create animation sequences, allow the reader to interact with the book and even how to add sound and music to your book!

This tutorial assumes you are familiar with at least the basics of Sprite Kit. If you are new to Sprite Kit, please check out our Sprite Kit Tutorial for Beginners first.

Getting Started

First download the starter project for this tutorial. Extract the project to a convenient location on your drive, then open it up in Xcode for a quick look at how it’s structured.

The project’s contents are collected in four main folders, as shown below:

The Seasons - Project Layout

  1. Other Resources: this contains all resources from third-party sources.
  2. Sounds: this contains most of the project’s sound files.
  3. Art: this contains all of the creative image assets.
  4. Scenes: as you may have guessed, this contains all the classes that control the various scenes of the project.
Note: The Seasons was written and illustrated by myself (Tammy L Coron). This tutorial uses the first few pages from that book. Please do not reproduce any of its contents in your own projects. The narration is provided by Amy Tominac, and again cannot be used in your own projects.

This tutorial also uses music from Kevin MacLeod and sound effects from FreeSound. These are both great resources for your project, but you must provide attribution. See attribution.txt for more details.

Remember, kids… only you can prevent copyright theft. Oh, and forest fires too, according to Smokey the Bear.

Close the Other Resources, Sounds, and Art groups; you won’t be making any changes in those areas. You’ll only be working with the “Scene*” files located in the Scenes group.

Like any good book, it’s best to start at the beginning. The next section will walk you through the “title” scene for your book.

Adding the Title Page

The starter project provides stub versions of the methods used in this scene — it’s your job to add the code. Your first steps will be to initialize the scene and add a background image.

Open the Scene00.m file and add the following block of code to initWithSize: just after the comment that reads /* add setup here */:

SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:@"bg_title_page"];
background.anchorPoint = CGPointZero;
background.position = CGPointZero;
 
[self addChild:background];

The code above creates an SKSpriteNode object and initializes it using the spriteNodeWithImageNamed: class method. It then sets the background‘s anchorPoint to CGPointZero which is the lower left corner of the scene.

Next, it sets the background‘s position to CGPointZero, which is the lower left corner of the screen. Finally, it uses the addChild: class method to add the newly created node as a child of the SKScene.

Note: SKSpriteNode subclasses SKNode. SKNodes are responsible for most of the content that you will add to your Sprite Kit apps.

Still working in the same file, add the following two lines of code to initWithSize: immediately after the call to addChild::

[self setUpBookTitle];
[self setUpSoundButton];

The two methods called here are only shells at the moment; you’ll flesh them out in the next section.

Build and run your project; you should see the title page as illustrated below:

tc_spritekit_build1

Adding the Book Title

As you saw in the code above, it’s a straightforward operation to add an SKSpriteNode to your scene. With just a few lines of code, you can add a textured sprite to any scene.

Now that the background image has been added, you’ll need to add the book title. You could use an SKLabelNode to add your title, or even a UIView, but instead I’ve opted to use a graphic.

Still working in the Scene00.m file, add the following block of code to setUpBookTitle:

SKSpriteNode *bookTitle = [SKSpriteNode spriteNodeWithImageNamed:@"title_text"];
bookTitle.name = @"bookTitle";
 
bookTitle.position = CGPointMake(421,595);
[self addChild:bookTitle];

In the above code, you first create an SKSpriteNode and then initialize it using spriteNodeWithImageNamed:. You then name the node “bookTitle” for ease of reference later, and set the node’s position to something other than CGPointZero.

Why? Since the background image takes up the entire view, you can get away with positioning it in the bottom-left corner of the screen. The title image, on the other hand, must be positioned precisely where you want it.

Build and run your project; you’ll see your background image with the graphic title superimposed over it, as shown below:

tc_spritekit_build2

That looks pretty neat. But wouldn’t it be great if you could add a little bit of action to your title screen?

Adding Animation to Objects

Sprite Kit makes it easy to add animation to the objects in your scene. Instead of simply having the title appear on the screen, you can make it slide on to the screen and bounce a little bit before it comes to rest.

To do this, you’ll use SKAction objects with predefined sequences.

Head back to Scene00.m and add the following code to the end of setUpBookTitle:

SKAction *actionMoveDown = [SKAction moveToY:600 duration:3.0];
SKAction *actionMoveUp = [SKAction moveToY:603 duration:0.25];
SKAction *actionMoveDownFast = [SKAction moveToY:600 duration:0.25];
 
[bookTitle runAction:[SKAction sequence:@[actionMoveDown, actionMoveUp, actionMoveDownFast]]];

The above code creates three actions using SKAction‘s class method moveToY:duration:; this specifies an action that moves a node vertically from its original position to the specified y position. Next, it creates a new sequence action using SKAction‘s sequence: class method; this instructs bookTitle to run the specified array of actions in order.

The net result of these actions is that the sprite will move the sprite down, then up, and then down again before it comes to rest.

Next, modify the line in setUpBookTitle that sets bookTitle‘s position property as shown below:

bookTitle.position = CGPointMake(425,900);

This simply modifies the start position of the title image so that it starts off-screen.

Build and run your project; the title now slides in slowly and bounces a little before it comes to rest at the position specified in actionMoveDownFast.

Actions are great, but sounds are an integral part of any interactive children’s book. Sprite Kit makes this tremendously easy as well!

Adding Sound to Your Story

There are several methods to add music and other sounds to your book, but in this tutorial you’ll just focus on two methods, one for adding the background music, and the other for adding the narration and sound effects.

Head back to Scene00.m and add the following import statement:

@import AVFoundation;

Add the following private instance variables to Scene00.m at the spot indicated by the comment /* set up your instance variables here */:

AVAudioPlayer *_backgroundMusicPlayer;
SKSpriteNode *_btnSound;
BOOL _soundOff;

Next, add the following two lines of code just before the line that creates background in locate initWithSize::

_soundOff = [[NSUserDefaults standardUserDefaults] boolForKey:@"pref_sound"];
[self playBackgroundMusic:@"title_bgMusic.mp3"];

This code retrieves the BOOL value that represents the user’s preference for having the sound on or off and assigns it to the _soundOff instance variable.

Now add the following block of code to playBackgroundMusic::

NSError *error;
NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
_backgroundMusicPlayer.numberOfLoops = -1;
_backgroundMusicPlayer.volume = 1.0;
[_backgroundMusicPlayer prepareToPlay];

playBackgroundMusic: plays the background music using a AVAudioPlayer object.

Note: AVAudioPlayer isn’t specific to Sprite Kit, so it won’t be covered in detail here. For more detail on AVAudioPlayer, check out the Audio Tutorial for iOS.

Okay — you have some code to handle the user preference for enabling or disabling sound in your book. You should probably add a control to let the user set this preference! :]

Still working in Scene00.m, add the code below to setUpSoundButton:

if (_soundOff)
{
  // NSLog(@"_soundOff");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_off"];
  _btnSound.position = CGPointMake(980, 38);
 
  [self addChild:_btnSound];
  [_backgroundMusicPlayer stop];
}
else
{
  // NSLog(@"_soundOn");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_on"];
  _btnSound.position = CGPointMake(980, 38);
 
  [self addChild:_btnSound];
  [_backgroundMusicPlayer play];
}

The above code sets the texture of the _btnSound SKSpriteNode to the appropriate image depending on the user’s stored preference. It then checks _soundOff and, regardless of its value, removes the sprite from the screen and initializes a new sprite with the appropriate on or off image. Then it sets the sprite’s position, adds it to the parent view, and finally plays or stops the music depending on the user’s preference.

There are a number of ways to handle changing a sprite’s image. The above method is only one way to accomplish this; the next section shows you another way to manage sprite images. The next section shows you another way to accomplish the same thing.

For now, build and run the app, and the background music should play. Tapping the button won’t do anything yet though.

Detecting Touch Events

The reason you can’t currently toggle the control is that the sprite doesn’t have any touch event handlers. So let’s add that next.

Add the following code to touchesBegan:withEvent: in Scene00.m:

/* Called when a touch begins */
for (UITouch *touch in touches)
{
  CGPoint location = [touch locationInNode:self];
  // NSLog(@"** TOUCH LOCATION ** \nx: %f / y: %f", location.x, location.y);
 
  if([_btnSound containsPoint:location])
  {
    // NSLog(@"xxxxxxxxxxxxxxxxxxx sound toggle");
 
    [self showSoundButtonForTogglePosition:_soundOff];
  }
}

touchesBegan:withEvent: is part of the UIResponder class which all SKNodes extend. It tells the receiver when one or more fingers begin touching the view. In this method you’re able to capture the location of the touch and respond to it accordingly.

Note: As every SKNode extends UIResponder, you can implement touch handling directly in your sprites. This tutorial performs all touch event handling in the scene nodes simply to keep the number of classes more manageable. However, in a production app it would likely make more sense to create an SKSpriteNode subclass, such as SoundToggleButton, and have that subclass handle its own touch events.

Look at the if statement above; you’ll see that if the touch location is on the sound button, you call showSoundButtonForTogglePosition:.

Now would be a great time to write that method! :]

Add the following code to showSoundButtonForTogglePosition: in Scene00.m:

// NSLog(@"togglePosition: %i", togglePosition);
 
if (togglePosition)
{
  _btnSound.texture = [SKTexture textureWithImageNamed:@"button_sound_on"];
 
  _soundOff = NO;
  [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"pref_sound"];
  [[NSUserDefaults standardUserDefaults] synchronize];
 
  [_backgroundMusicPlayer play];
}
else
{
  _btnSound.texture = [SKTexture textureWithImageNamed:@"button_sound_off"];
 
  _soundOff = YES;
  [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"pref_sound"];
  [[NSUserDefaults standardUserDefaults] synchronize];
 
  [_backgroundMusicPlayer stop];
}

The method above sets the appropriate texture for the SKSpriteNode that is held in _btnSound — much as you did in setUpSoundButton. Once the texture has been set, you can store the user’s selection and either play or stop the music accordingly.

Note: An SKTexture object stores a reference to the graphical data in memory that a sprite renders.

Build and run, and now you should be able to tap the sound button to turn the background music on and off.

Adding the Next Scene

Add the following import to the top of Scene00.m:

#import "Scene01.h"

You’ll need this to present the next scene; this is Page 1 of the book and is controlled by the Scene01 class.

Still working in Scene00.m, add the following code to setUpBookTitle just after the line that creates actionMoveDownFast, but before the line that calls runAction: on bookTitle:

SKAction *wait = [SKAction waitForDuration:0.75];
SKAction *showButton = [SKAction runBlock:^{
 
  SKSpriteNode *buttonStart = [SKSpriteNode spriteNodeWithImageNamed:@"button_read"];
  buttonStart.name = @"buttonStart";
 
  buttonStart.position = CGPointMake(425,460);
  [self addChild:buttonStart];
 
  [buttonStart runAction:[SKAction playSoundFileNamed:@"thompsonman_pop.wav" waitForCompletion:NO]];
}];

The code above creates a new action block which creates a sprite for the start button and plays a sound. By putting the code to create the sprite and play a sound inside an action, you can easily make this code run in a particular point in a sequence of actions.

As you may have figured out by now, there is usually more than one way to skin a cat — or code your children’s book!

Replace the line in setUpBookTitle that calls runAction: on bookTitle with the following code:

[bookTitle runAction:[SKAction sequence:@[actionMoveDown, actionMoveUp, actionMoveDownFast, wait, showButton]]];

This simply adds the new action to the sequence that bookTitle runs when called.

The next step is to modify the touch handling in touchesBegan:withEvent:.

Add the following line to the top of touchesBegan:withEvent:

SKNode *startButton = [self childNodeWithName:@"buttonStart"];

This assigns the button – added earlier in the showButton action – to the node.

Add the following else if clause after the if statement in touchesBegan:withEvent::

else if([startButton containsPoint:location])
{
  [_backgroundMusicPlayer stop];
 
  // NSLog(@"xxxxxxxxxxxxxxxxxxx touched read button");
 
  Scene01 *scene = [[Scene01 alloc] initWithSize:self.size];
  SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
  [self.view presentScene:scene transition:sceneTransition];
}

The above code adds a new scene and presents it with a new transition when it detects a user tap on the “Read Story” button.

Build and run your project. After the initial animation, you should see the new “Read Story” button, as shown below:

tc_spritekit_build3

Tap the “Read Story” button and the next scene presents itself on-screen. You won’t see a whole lot at this point — your next task is add some content to the first page.

Adding Content to the First Page

In this section, you’ll add the first page to the book along with some physics simulation and some touch response logic. Before you do that you’ll need to set up the scene.

Open Scene01.m and add the following import statements to the top of the file:

#import "Scene00.h"
#import "Scene02.h"
#import "SKTUtils.h"

The first two lines above give you access to other scenes so you can implement page forward and back controls. The third line allows you to use all of the goodies in SKTUtils, which is an external resource developed for iOS Games by Tutorials and won’t be covered in detail in this tutorial.

Add the following private variables to the implementation section of Scene01.m:

SKSpriteNode *_footer;
SKSpriteNode *_btnLeft;
SKSpriteNode *_btnRight;
Note: From this point forward, all code dealing with background sound has been included in the starter files for you. You can review the previous section to see how sound is implemented in this project.

Just as before, you’ll need to initialize the scene before you can do anything with it. There are several ways to accomplish this, but this tutorial takes the approach of creating separate methods for each scene instead of handling everything within a single method.

Add the following block of code to Scene01.m, just after the comment that reads /* additional setup */:

[self setUpText];
[self setUpFooter];
[self setUpMainScene];

Those are your basic setup methods — your job is to fill them out. The good news is that the implementation of setUpText setUpFooter is identical for each scene in your project, so you’ll only need to write it once for all scenes.

Add the following code to setUpText in Scene01.m:

SKSpriteNode *text = [SKSpriteNode spriteNodeWithImageNamed:@"pg01_text"];
text.position = CGPointMake(300 , 530);
 
[self addChild:text];
 
[self readText];

Here you create an SKSpriteNode, set its location, and add it to the scene. The sprite above holds an image for the text of the page; you’ll have one sprite for the text of each page in your book.

This method also calls readText which plays the narration for the book provided by the very talented Amy Tominac.

Add the following code to readText in Scene01.m:

if (![self actionForKey:@"readText"])
{
  SKAction *readPause = [SKAction waitForDuration:0.25];
  SKAction *readText = [SKAction playSoundFileNamed:@"pg01.wav" waitForCompletion:YES];
 
  SKAction *readSequence = [SKAction sequence:@[readPause, readText]];
 
  [self runAction:readSequence withKey:@"readText"];
}
else
{
  [self removeActionForKey:@"readText"];
}

Here you create a new SKAction, much like you did earlier for the bouncing title text. The difference this time — and it’s an important difference — is that you also assign a key name of readText to your action. You’ll discover why a bit later.

You’re also using SKAction’s playSoundFileNamed:waitForCompletion: class method, which is a really simple method for playing sounds. While this method works great for quick sound effects, it’s probably not the best choice for read-along text because you can’t interrupt the sound when it’s playing, among other reasons. You’re using it in this tutorial for ease-of-use only and to become familiar with the framework.

In a production-level app, you should consider using something with a little more functionality such as the ability to stop sounds while they are playing, such as one of Apple’s other audio technologies.

Build and run, and now you will see and hear the narrated text for page 1!

iOS Simulator Screen shot Jan 13, 2014, 2.42.55 PM

Adding the Navigation Controls

Next let’s add the navigation controls for your app, which will all be located along the bottom of the screen.

Add the following code to setUpFooter in Scene01.m:

/* add the footer */
 
_footer = [SKSpriteNode spriteNodeWithImageNamed:@"footer"];
_footer.position = CGPointMake(self.size.width/2, 38);
 
[self addChild:_footer];
 
/* add the right button */
 
_btnRight = [SKSpriteNode spriteNodeWithImageNamed:@"button_right"];
_btnRight.position = CGPointMake(self.size.width/2 + 470, 38);
 
[self addChild:_btnRight];
 
/* add the left button */
 
_btnLeft = [SKSpriteNode spriteNodeWithImageNamed:@"button_left"];
_btnLeft.position = CGPointMake(self.size.width/2 + 400, 38);
 
[self addChild:_btnLeft];
 
/* add the sound button */
 
if (_soundOff)
{
  // NSLog(@"_soundOff");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_off"];
  _btnSound.position = CGPointMake(self.size.width/2 + 330, 38);
 
  [self addChild:_btnSound];
  [_backgroundMusicPlayer stop];
}
else
{
  // NSLog(@"_soundOn");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_on"];
  _btnSound.position = CGPointMake(self.size.width/2 + 330, 38);
 
  [self addChild:_btnSound];
 
  [_backgroundMusicPlayer play];
}

The code above initializes _footer with the footer area’s background image, sets its position, and adds it to the scene. It also adds sprites for both the page back and page forward buttons. Finally, it adds the sound toggle button, which should look familiar since it’s essentially the same code you used to set up the button in the previous scene.

Build and run, and you will now see the footer along the bottom of the scene (although tapping it won’t do anything yet):

Book footer

Now that the basic scene setup is complete, it’s time to add the main character.

Adding The Main Character

Still working in the Scene01.m file, add the following private variable to the Scene01 implementation:

SKSpriteNode *_kid;

This variable holds a reference to your main character’s sprite.

Next, add the following two lines to setUpMainScene of Scene01.m:

[self setUpMainCharacter];
[self setUpHat];

These methods simply call other methods to keep your code nice and tidy. You’ll populate those next.

Add the following code to setUpMainCharacter in Scene01.m:

_kid = [SKSpriteNode spriteNodeWithImageNamed:@"pg01_kid"];
_kid.anchorPoint = CGPointZero;
_kid.position = CGPointMake(self.size.width/2 - 245, 45);
 
[self addChild:_kid];
[self setUpMainCharacterAnimation];

This should be familiar territory to you by now; you create an SKSpriteNode, set its anchorPoint and position, and add it to the scene. Then you call setUpMainCharacterAnimation to set up the main character animation.

setUpMainCharacterAnimation only exists as a shell — time to add some madness to your method! :]

Add the following code to setUpMainCharacterAnimation in in Scene01.m:

NSMutableArray *textures = [NSMutableArray arrayWithCapacity:2];
 
for (int i = 0; i <= 2; i++)
{
  NSString *textureName = [NSString stringWithFormat:@"pg01_kid0%d", i];
  SKTexture *texture = [SKTexture textureWithImageNamed:textureName];
  [textures addObject:texture];
}
 
CGFloat duration = RandomFloatRange(3, 6);
 
SKAction *blink = [SKAction animateWithTextures:textures timePerFrame:0.25];
SKAction *wait = [SKAction waitForDuration:duration];
 
SKAction *mainCharacterAnimation = [SKAction sequence:@[blink, wait, blink, blink, wait , blink, blink]];
[_kid runAction: [SKAction repeatActionForever:mainCharacterAnimation]];

Animations are performed using a series of images. In the code above, the main character blink animation uses two images to achieve this effect.

The first line creates an array to hold your images. Following that, you generate the image name, create an SKTexture object for each image in the animation, then add that object to your array.

Next you create the animation sequence using SKAction‘s animateWithTextures:timePerFrame: class method which expects an array of textures.

Finally, you instruct the _kid sprite to perform its action. By using the repeatActionForever method, you tell the node to run this action continuously.

Build and run your project; hit the “Read Story” button and you’ll see the main character appear on-screen and blink his eyes while the narration plays in the background, as so:

tc_spritekit_build4

An Introduction to Physics

You can really improve the appeal of your book by adding some interactivity. In this section, you’re going to create a hat for the main character which the reader can drag around the screen and place on the main character’s head.

Still working in Scene01.m, add the following instance variable to the Scene01 implementation:

SKSpriteNode *_hat;

Add the following code to setUpHat in Scene01.m:

SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Thonburi-Bold"];
label.text = @"Help Mikey put on his hat!";
label.fontSize = 20.0;
label.fontColor = [UIColor yellowColor];
label.position = CGPointMake(160, 180);
 
[self addChild:label];
 
_hat = [SKSpriteNode spriteNodeWithImageNamed:@"pg01_kid_hat"];
_hat.position = CGPointMake(150, 290);
_hat.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_hat.size];
_hat.physicsBody.restitution = 0.5;
 
[self addChild:_hat];

The first half of the code above creates an SKLabelNode object and adds it to the scene. An SKLabelNode is similar to a UILabel; in Sprite Kit, it’s a node used to draw a string.

The second half of the code adds the physics to the scene. You created the SKSpriteNode and assigned it to the _hat variable as well as an SKPhysicsBody which lets you apply a number of physical characteristics to your objects such as shape, size, mass, and gravity and friction effects.

Calling the SKPhysicsBody‘s bodyWithRegtangleOfSize: class method sets the shape of the body to match the node’s frame. You also set the restitution to 0.5 which means your physics body will bounce off objects with half of its initial force.

Build and run your project; tap the “Read Story” button and…huh? Where did the hat go?

If you were watching the screen closely, you may have noticed the hat falling off the screen. That’s because there was no opposing body to stop it from falling.

You can fix that by adding a physics body to act as the ground.

Add the following code to setUpFooter in Scene01.m, just after the line that calls addChild: to add _footer to the scene:

self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:_footer.frame];

Build and run your project again; this time, the hat has something to land on — the physics body you set up in the footer. As well, you can see the yellow text label that you created with SKLabelNode, as shown below:

tc_spritekit_build5

Okay, you’ve added some physics properties to the hat — but how do you go about adding interactivity?

Handling Touches and Moving the Hat

This section implements the touch handling for the hat so that you can move it around the screen, as well as touch handling for the Next, Previous and sound preference button.

touchesBegan:withEvent: will end up as one of the longest methods you’ll write; it already includes the processing you wrote for Scene00 to toggle the background music, and you’ll add supporting code for the next/previous page buttons and for moving the hat in the next few code blocks.

Add the following block of code immediately after the existing if statement of touchesBegan:withEvent: in Scene01.m:

else if ([_btnRight containsPoint:location])
{
  // NSLog(@">>>>>>>>>>>>>>>>> page forward");
 
  if (![self actionForKey:@"readText"]) // do not turn page if reading
  {
    [_backgroundMusicPlayer stop];
 
    SKScene *scene = [[Scene02 alloc] initWithSize:self.size];
    SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
    [self.view presentScene:scene transition:sceneTransition];
  }
}
else if ([_btnLeft containsPoint:location])
{
  // NSLog(@"<<<<<<<<<<<<<<<<<< page backward");
 
  if (![self actionForKey:@"readText"]) // do not turn page if reading
  {
    [_backgroundMusicPlayer stop];
 
    SKScene *scene = [[Scene00 alloc] initWithSize:self.size];
    SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
    [self.view presentScene:scene transition:sceneTransition];
  }
}

Here you check to see if the Next or Previous page buttons were the target of the touch event, much like you did for the sound toggle button. You then handle the touch event by stopping the background music and moving to the appropriate scene.

However, there is one small difference here. Recall the key that you set for the action that narrates the text?

[self runAction:readSequence withKey:@"readText"];

The block of code you added above uses that key to check if the readText action is currently playing. If it is, do nothing. If it’s not, turn the page.

But why check at all?

The reason is that when you start an SKAction that plays a sound, it’s impossible to interrupt that sound. This is why you can’t turn the page while the text is being read. As was mentioned earlier, you’ll probably want to use something more robust to narrate the text in your production-level app.

It would be nice to give the reader a way to jump back to the first scene, no matter where they are in the book.

Add the following code right below the code that you added previously, which will take you back to the first scene when the user touches the book title in the footer:

else if ( location.x >= 29 && location.x <= 285 && location.y >= 6 && location.y <= 68 )
{
  // NSLog(@">>>>>>>>>>>>>>>>> page title");
 
  if (![self actionForKey:@"readText"]) // do not turn page if reading
  {
    [_backgroundMusicPlayer stop];
 
    SKScene *scene = [[Scene00 alloc] initWithSize:self.size];
    SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
    [self.view presentScene:scene transition:sceneTransition];
  }
}

The above code tests the touch location a little differently than the other checks you’ve made. Because the book’s title is part of the footer image, this simply checks to see whether or not the touch location falls within the area where you know the book title appears. It’s usually not a good idea to have “magic numbers” like this strewn about your app, but it serves to keep things simple in this tutorial.

The rest of the above code is exactly like the code that handles the Previous Page button touch events.

Finally, you’ll need to handle touch events on the hat. To do so, you’ll need to store some data between events.

Add the following two instance variables to the Scene01 implementation of Scene01.m:

BOOL _touchingHat;
CGPoint _touchPoint;

In the above code, _touchingHat stores whether the user is currently touching the hat, while _touchPoint stores the most recent touch location.

To ensure the hat catches any touch events that occur both over it and another target area, check the hat first.

To do this, change the first if in touchesBegan:withEvent: to an else if so that it looks like the following:

else if([_btnSound containsPoint:location])

Next, add the following code immediately above the line you just changed in Scene01.m:

if([_hat containsPoint:location])
{
  // NSLog(@"xxxxxxxxxxxxxxxxxxx touched hat");
  _touchingHat = YES;
  _touchPoint = location;
 
  /* change the physics or the hat is too 'heavy' */
 
  _hat.physicsBody.velocity = CGVectorMake(0, 0);
  _hat.physicsBody.angularVelocity = 0;
  _hat.physicsBody.affectedByGravity = NO;
}

When the user first touches the hat, the code above sets the _touchingHat flag to YES and stores the touch location in _touchPoint. It also makes a few changes to the hat’s physics body. These changes are necessary because without them it’s virtually impossible to drag the hat around the screen as you’re constantly fighting with the physics engine.

You’ll need to track the touch as it moves across the screen, so add the following code to touchesMoved:withEvent: in Scene01.m:

_touchPoint = [[touches anyObject] locationInNode:self];

Here you update the most recent touch location stored in _touchPoint.

When the user stops touching the screen, you need to reset any hat-related data.

Add the following code to both touchesEnded:withEvent: and touchesCancelled:withEvent::

_touchingHat = NO;
_hat.physicsBody.affectedByGravity = YES;

The above code sets the _touchingHat flag to NO and re-enables gravity for the hat so that it will fall back to the floor when the user releases it.

There’s just one more thing to do to get the hat to track the user’s finger as it moves on the screen.

Add the following code to update::

if (_touchingHat)
{
  _touchPoint.x = Clamp(_touchPoint.x, _hat.size.width / 2, self.size.width - _hat.size.width / 2);
  _touchPoint.y = Clamp(_touchPoint.y,
                        _footer.size.height +  _hat.size.height / 2,
                        self.size.height - _hat.size.height / 2);
 
  _hat.position = _touchPoint;
}

update invokes before each frame of the animation renders. Here you check to see if the user is dragging the hat; if it is, change _hat‘s current location to the position stored in _touchPoint. You’re using the Clamp function from SKTUtils to ensure the hat doesn’t move off the screen or below the footer.

Build and run your project; hit the “Read Story” button, and play around with the hat on the screen a little.

Note: Try building the app both with and without the changes to the hat’s physics bodies to see the difference it makes. You should be able to move the hat around with your finger easily when the changes to the physics body are applied, but it’s a little more difficult to move it around when you comment out the changes.

Moving the hat around is cool, but there isn’t any feedback as to whether or not the hat is on top of the main character’s head. It’s time to add that feedback.

Modify touchesEnded:withEvent: so that it looks like the code below:

if (_touchingHat)
{
  CGPoint currentPoint = [[touches anyObject] locationInNode:self];
 
  if ( currentPoint.x >= 300 && currentPoint.x <= 550 && 
       currentPoint.y >= 250 && currentPoint.y <= 400 )
  {
    // NSLog(@"Close Enough! Let me do it for you");
 
    currentPoint.x = 420;
    currentPoint.y = 330;
 
    _hat.position = currentPoint;
 
    SKAction *popSound = [SKAction playSoundFileNamed:@"thompsonman_pop.wav" waitForCompletion:NO];
    [_hat runAction:popSound];
  }
  else
    _hat.physicsBody.affectedByGravity = YES;
 
  _touchingHat = NO;
}

With the above bit of code you can determine if the user is touching the hat and where they attempted to release it. This is why you want to use the end event and not the begin event.

If the user releases the hat close enough to the kid’s head, your code re-positions the hat to an exact location as defined by currentPoint.x and currentPoint.y.

Additionally, a sound plays using SKAction‘s playSoundFileNamed: class method to alert the user that the hat is now firmly placed on the main character’s head – which is important! Did you see all that snow outside the window? Brrrrr!

Build and run your project; grab the hat and plunk it down on your character’s head, like so:

tc_spritekit_build7

Adding Page Two

The code for page two is roughly the same as that for page one; to this end, it’s been pre-populated for you in Scene02.m. The code is heavily commented for your reading pleasure, and since it’s very similar to Scene01.m it won’t be covered in detail here.

One thing to note is that Scene02 has an instance variable — _catSound — that references an SKAction that plays a sound. This illustrates that you can store SKActions and reuse them, but it also improves the user experience in the case of actions that play sounds.

Your app loads the sound into memory when you create a sound action, so initializing the sound action before it’s needed eliminates any potential pauses your users might experience.

Build and run your project; tap “Read Story” and use the navigation buttons to move back and forth in the story (after the text is done reading), like so:

tc_spritekit_build8

As an added bonus, tap on the cat to hear him meow!

Ok, enough messing around with pre-written code and kitties. Back to work, lazy pants! :]

Adding Page Three

Hopefully you enjoyed your short break to play with the kitty, but don’t think that just because you didn’t write the code for Page Two that it’s not in your best interest to review it! There’s lots to be learned by reviewing code written by others.

In the interest of keeping this tutorial focused, Scene03.m also provides a lot of the code you’ll need, but there’s still a little to add.

Add the following code to setUpMainScene in Scene03.m:

/* add the kid and the cat */
 
SKSpriteNode *cat = [SKSpriteNode spriteNodeWithImageNamed:@"pg03_kid_cat"];
cat.anchorPoint = CGPointZero;
cat.position = CGPointMake(self.size.width/2 - 25, 84);
 
[self addChild:cat];
 
/* add Snowflakes */
 
float duration = 1.25f; // determines how often to create snowflakes
[self runAction:[SKAction repeatActionForever:[SKAction sequence:@[[SKAction performSelector:@selector(spawnSnowflake) onTarget:self],[SKAction waitForDuration:duration]]]]];

Just as before, you create a new SKSpriteNode, set its anchorPoint and position and add it to the scene. Nothing surprising there.

Next you create an SKAction sequence that runs forever. You can use SKAction’s performSelector:onTarget: to run any method of your choosing. In this case, the action runs spawnSnowflake; you’ll be writing that method in a moment.

The above sequence ends with a waitForDuration: action; since the sequence repeats forever, it calls spawnSnowflake, pauses for the specified number of seconds as stored in duration, then repeats those two actions forever.

Add the following code to spawnSnowflake in Scene03.m:

// here you can even add physics and a shake motion too
 
CGFloat randomNumber = RandomFloatRange(1, 4);
CGFloat duration = RandomFloatRange(5, 21);
 
NSString *snowflakeImageName = [NSString stringWithFormat:@"snowfall0%.0f",randomNumber];
SKSpriteNode *snowflake = [SKSpriteNode spriteNodeWithImageNamed:snowflakeImageName];
snowflake.name = @"snowflake";
 
snowflake.position = CGPointMake(RandomFloatRange(0, self.size.height), self.size.height + self.size.height/2);
[self addChild:snowflake];
 
SKAction *actionMove = [SKAction moveTo:CGPointMake(snowflake.position.x + 100, -snowflake.size.height/2) duration:duration];
 
SKAction *actionRemove = [SKAction removeFromParent];
[snowflake runAction:[SKAction sequence:@[actionMove, actionRemove]]];

The block of code above executes continuously because the SKAction calling this method was instructed to run forever. Talk about willing to make a commitment. Men, please take note. ;]

So what does that code actually do?

  1. It generates an SKSpriteNode using a random snowflake image.
  2. It creates and executes an SKAction sequence that includes an action to move the sprite down the screen using SKAction‘s moveTo: class method.
  3. Finally, it removes the sprite from the scene using SKAction‘s removeFromParent class method.
Note: This would be the perfect place to add some physics and a method that creates a snow globe effect when the user shakes the device. I’ll leave that job to you, dear reader!

Build and run your project; you should see snowflakes start to appear and move down the screen, as shown below:

tc_spritekit_build9

Note: Just for fun, adjust the duration variable to 0.15. The value is inversely proportional to the number of snowflakes on the screen. Build and run your project and watch a blizzard unfold right before your very own eyes! When you’re done creating the snowpocalypse, move along — that’s enough playing in the snow for now!

Eagle-eyed readers will have noticed that the snowflakes run right over your footer. Time to fix that!

Add the following code to checkCollisions in in Scene03.m:

[self enumerateChildNodesWithName:@"snowflake" usingBlock:^(SKNode *node, BOOL *stop)
{
  SKSpriteNode *snowflake = (SKSpriteNode *)node;
  CGRect footerFrameWithPadding = CGRectInset(_footer.frame, -25, -25); // set padding around foot frame
 
  if (CGRectIntersectsRect(snowflake.frame, footerFrameWithPadding))
  {
    [snowflake removeFromParent];
  }
}];

Using enumerateChildNodesWithName:, you’re able to detect nodes with the specified name. Since you gave each snowflake a node name of ‘snowflake’, you can do two things here: one, apply the collision detection on matching nodes only, and two, remove the node only if you detect a collision. You detect collisions using CGRectIntersectsRect.

The code above looks and works great, but how do you call it? How does the program know when to run it? That’s where the infamous “game loop” comes in handy. You may have noticed it already, but it’s explained in detail below if you didn’t follow through the code.

Sprite Kit uses a traditional rendering loop — often referred to as a game loop — to process the contents of each frame before its rendered and presented. Sprite Kit calls update: for each frame; it’s in this method that you will write the code to call the collision detection method you created above.

Add the following code to update: in Scene03.m:

[self checkCollisions];

Build and run your project; you now should see the snowflake sprites disappear as they approach the top edge of the footer, as shown below:

tc_spritekit_lastpage

Note: Collision detection is important in game development. For example, a routine like this would be useful to detect when your hero ship fired at an enemy ship in a space adventure game. If you detect a hit, then remove the enemy ship from the screen. In this tutorial, the collision detection is simplified, but the same basic principles apply.

Where To Go From Here?

At this point, the rest of the story is up to you!

Note that the Scene04.m file has been populated with a generic “to be continued” scene. I want you to create Page Four using everything you’ve learned in this tutorial. Here’s a mockup of what your page could look like:

tc_spritekit_comp

Everything you need is already included with the project. Have fun!

I hope you enjoyed working through this tutorial as much as I have enjoyed writing it. To compare notes, download the complete sample project herehere.

If you’re looking to learn more about Sprite Kit, be sure to check out our book, iOS Games by Tutorials.

If you have any questions or comments, feel free to join in the discussion below!

How to Create an Interactive Children’s Book for the iPad is a post from: Ray Wenderlich

The post How to Create an Interactive Children’s Book for the iPad appeared first on Ray Wenderlich.

iOS 5 by Tutorials Third Edition Now Available!

$
0
0
iOS 5 by Tutorials fully updated for iOS 7 and Xcode 5!

iOS 5 by Tutorials fully updated for iOS 7 and Xcode 5!

This is a quick announcement to let you know some good news – the third edition of iOS 5 by Tutorials is now available!

In this new third edition, all of the tutorials in the book have been fully updated to iOS 7 and Xcode 5.

In addition to making sure everything still works in iOS 7, we added notes and information about relevant iOS 7 changes where appropriate.

And the best news of all – this second edition is completely free of charge to existing iOS 5 by Tutorials customers, to thank you for supporting this site. You can download the new second edition on your My Loot page.

Note that this is the last time we will be updating iOS 5 by Tutorials. After all, it wouldn’t make sense for us to continue updating this all the way to iOS 20! It’s been a great ride and we hope this book has been useful for you during your iOS journey.

At this point, all three of the books in our iOS by Tutorials Bundle are fully up-to-date for iOS 7 and Xcode 5, w00t!

If you don’t have iOS 5 by Tutorials and want to learn about some cool APIs like Storyboards, iCloud, or ARC – now’s a good time to grab a copy!

We hope you enjoy the new third edition!

iOS 5 by Tutorials Third Edition Now Available! is a post from: Ray Wenderlich

The post iOS 5 by Tutorials Third Edition Now Available! appeared first on Ray Wenderlich.

Viewing all 4374 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>