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

Creating a Cross-Platform Multi-Player Game in Unity — Part 2

$
0
0

TK2MeshNetworkWelcome back to the second part of this series on creating a cross-platform multi-player game in Unity. You’ll need to tackle the first part of this tutorial before you take on this one — otherwise this tutorial won’t make a whole lot of sense! :]

Up to this point, you’ve spent a lot of time adding frameworks and filling out forms to get to the very first step of any multiplayer game — signing in and out. Now you can move on to the real work of making the game happen.

In this tutorial part, you are going to learn the following:

  • Some general game networking theory
  • How to to connect Google Play Services
  • How to create a game app using Google Development console.
  • The code necessary to have devices talk with each other.
  • Mad driving skills! :]

You can download the completed version for part one over here. You can read the first part over here.

Note: If you are downloading the starter project for this part, you will need to configure the ClientID, or the project will not work. To learn how to obtain a ClientID, check out the first part of this tutorial series.

An Introduction to Matchmaking

Matchmaking is one of the main functions of Google Play game services and other multiplayer frameworks. When your app tells Google Play’s servers that the player is interested in a multiplayer game, Google Play then looks for other people in the world who are also interested in playing that same game at that same time.

Forever looking for others

Matchmaking is a somewhat complex operation. the Google Play servers don’t just pick the next person they find; if one player is in Helsinki and the other player is in Sydney, the network connection between them would be terribly laggy. Unfortunately, even the best network programmers haven’t yet found a way to increase the speed of light. :]

So Google Play may choose to wait for better options to come up for our Helsinki player and only match them against the player in Sydney if no better option comes along. Fortunately, you don’t have to worry about any of that logic; you just need to know that the underlying service does a pretty good job at connecting people within a reasonable amount of time.

Ping time

Many multiplayer games, such as MMOs, require that all clients connect to a server in a traditional client-server model like the one illustrated below:

A traditional client-server setup

However, many other games, like yours, will pair up players then connect their devices to each other through a peer-to-peer mesh network, where all players’ devices talk directly to each other as shown below:

Peer to peer mesh network

Invites, or Auto-Match?

Traditionally, there are two ways that players are matched with opponents:

  1. Players can request to be matched up with specific friends.
  2. Players can ask to be auto-matched, which tells Google Play to find other people looking to play this game.

I find that vast majority of games use auto-matching; players tend to be impatient and just want to start a game instead of waiting around for their friend to see the invitation notification and decide whether or not to accept it.

Tired of waiting

Invitations typically happen when two players are in the same physical location and spontaneously decide to play a game, or they’ve scheduled a gameplay session ahead of time. In most other cases, players tend to stick with auto-matching.

Note: Turn-based games are an entirely different story, as they aren’t as time-sensitive and players happily invite their friends — since they don’t have to wait around for them! :]

For these reasons, you’ll implement auto-matching in your game.

Adding Auto Matching

Open your Circuit Racer project in Unity, then open up your MultiplayerController script from the Scripts folder.

Create the following private instance variables in your class:

private uint minimumOpponents = 1;
private uint maximumOpponents = 1;
private uint gameVariation = 0;
  1. minimumOpponents is the minimum number of opponents to match your player against. You’ll create a two-player game for now, so set this to 1.
  2. maximumOpponents is the maximum number of opponents to match your player against. Since it’s a two-player game, set this to 1 as well.
  3. gameVariation specifies which particular multiplayer variation of your game that you wish to play. If Circuit Racer had a racing mode and a destruction derby mode, you wouldn’t want the racing players to end up auto-matched with people playing in the destruction derby!

The variables are all unsigned integers, meaning that they cannot hold a negative value. After all, you wouldn’t to play a game with negative players. Would that mean that you would cease to exist? :]

I just want to race

You could specify a variation of 1 for racing players, and a variation of 2 for destruction derby players to keep them separated. Using too many variants of your game segments the players and means there’s fewer players in each pool. In this tutorial, you only have the one variant, so use 0 as the default.

Next, add the following method to your MultiplayerController class: (MonoDevelop will complain that your argument is invalid because you’ve declared that MultiplayerController will be your RealTimeMultiplayerListener, and you haven’t set it up that way — but that’s an easy fix.)

private void StartMatchMaking() {
    PlayGamesPlatform.Instance.RealTime.CreateQuickGame (minimumOpponents, maximumOpponents, gameVariation, this);
}

You will notice that you’ve passed in all your private instance values. The final argument is the RealTimeMultiplayerListener instance that receives any messages from the Google Play games service about the status of your multiplayer game. To keep all multiplayer logic contained in the same class, you use this to indicate that the MultiplayerController class is your listener.

Now fix the compile error by changing the following line at the top of your class:

public class MultiplayerController  {

…to the following

public class MultiplayerController : RealTimeMultiplayerListener {

Here you declare that MultiplayerController conforms to the RealTimeMultiplayerListener interface, which is a list of methods a class promises to implement; this is very similar to a protocol in Objective-C.

Look at the SignInAndStartMPGame(); you’ll see there are two places in the code where the comments say you can start a multiplayer game like so:

// We could start our game now

Replace those comments with the following method call:

StartMatchMaking();

Head back to Unity, and you’ll see…a bunch of new errors!

So Many Unity Errors

Unity is complaining that you aren’t conforming to the interface as you promised. The RealTimeMultiplayerListener interface says any conforming class must implement the OnRoomConnected(bool success) method, among others.

To get rid of the errors, you can create a few mostly stubbed-out methods for the time being.

Note: Here’s a handy shortcut to generate stub methods in MonoDevelop: right-click on a blank line in your file and select Show Code Generation Window. Select Implement Interface Methods from the resulting dialog, check all the methods that appear, hit Return, and MonoDevelop automatically adds all the selected stub methods for you!

Generating Code

You’ll replace MonoDevelop’s boilerplate exception handling code with something that just prints out simple debug messages for now.

First, add the following utility method in MultiplayerController that prints out status messages from your matchmaking service:

private void ShowMPStatus(string message) {
    Debug.Log(message);
}

Now you can tackle each interface method in turn.

Add the following method, or alternately replace the contents of your stub method if you auto-generated it in the previous step:

public void OnRoomSetupProgress (float percent)
{
    ShowMPStatus ("We are " + percent + "% done with setup");
}

OnRoomSetupProgress indicates the progress of setting up your room. Admittedly, it’s pretty crude; on iOS in particular, I think it jumps from 20% to 100%. But hey, it’s better than nothing! :]

Some of you may be wondering what is a “room”? In Google Games terminology, a room is a virtual place that players gather to play real time games.

Add the following method, or replace the contents of your stub method:

public void OnRoomConnected (bool success)
{
    if (success) {
        ShowMPStatus ("We are connected to the room! I would probably start our game now.");
    } else {
        ShowMPStatus ("Uh-oh. Encountered some error connecting to the room.");
    }
}

OnRoomConnected executes with success set to true when you’ve successfully connected to the room. This would normally be the point where you’d switch to a multiplayer game.

Add or replace the following method:

public void OnLeftRoom ()
{
    ShowMPStatus ("We have left the room. We should probably perform some clean-up tasks.");
}

OnLeftRoom tells you that your player has successfully exited a multiplayer room.

Add or replace the following method:

public void OnPeersConnected (string[] participantIds)
{
    foreach (string participantID in participantIds) {
        ShowMPStatus ("Player " + participantID + " has joined.");
    }
}

You will receive an OnPeersConnected message whenever one or more players joins the room to which your local player is currently connected. You’ll learn more about participantIds later, but for now all you need to know is that they’re unique IDs for a specific player in this gameplay session.

Add or replace the following method:

public void OnPeersDisconnected (string[] participantIds)
{
    foreach (string participantID in participantIds) {
        ShowMPStatus ("Player " + participantID + " has left.");
    }
}

OnPeersDisconnected is similar to OnPeersConnected but it signals that one or more players have left the room.

Now for the last interface method! Add or replace the following method:

public void OnRealTimeMessageReceived (bool isReliable, string senderId, byte[] data)
{
    ShowMPStatus ("We have received some gameplay messages from participant ID:" + senderId);
}

You call OnRealTimeMessageReceived whenever your game client receives gameplay data from any player in the room; this handles all of your multiplayer traffic.

Once you’ve added all of the above methods, head back to Unity and check that all your compiler errors have resolved. If so, hit Command-B to export and run your project in Xcode. When the game starts, click the Multiplayer button and check the console log, where you should see something similar to the following:

DEBUG: Entering internal callback for RealtimeManager#InternalRealTimeRoomCallback
DEBUG: Entering state: ConnectingState
We are 20% done with setup

That means you’re in a two-player multiplayer lobby, waiting for another player to join. Success! :]

However, you’re the only person on earth who can play this game, so you might be waiting a looong time for someone else to join. Time to fix that.

Running on a Second Device

It’s quite difficult to test a Unity multiplayer game on a real device and the iOS simulator at the same time, so to make your life easy you’ll use two physical devices to test your multiplayer functions.

It’s safe to assume that since you’re reading this on RayWenderlich.com (where the iOS tutorials outnumber the Android ones by about 60 to 1), your second device also runs iOS. In that case, you can skip the section below. For those of you who want (or need) to run this on an Android device, read on!

Running on Android

If you’ve never run an Android app before, I recommend you read through the excellent “Make Your First Android App” series by Matt Luedke, as this section only presents a brief summary of the steps required.

Download and install Android Studio from http://developer.android.com/sdk/installing/index.html?pkg=studio. Click the Check for updates now text at the bottom of the welcome screen to ensure you have the latest version installed.

Next, get the latest version of the SDK: at the welcome dialog, click on Configure\SDK manager. Make sure you have the latest version of:

  • Android SDK Tools
  • Android SDK Platform-tools
  • Android SDK Build-tools
  • The Android API which corresponds to the device you have. Not sure what device you have? Go to Settings\About phone and look for the Android Version number.
  • Android Support Library
  • Google Play Services

If the status of any of these items is Not installed or Update available, simply check the box next to the item and click Install xx packages… as shown below:

Updating Android Things

Accept all the licenses as you’re prompted, and you’re good to go! Now that you’ve installed the requisite SDKs, you can quit Android Studio.

Note: If you used Eclipse in the past and were left a little underwhelmed, give Android Studio a try; it’s turning out to be quite a nice product.

Make sure USB debugging is turned on for your device, as noted in Matt’s tutorial:


If you have a device, you don’t need any silly provisioning profiles. You just need to turn on USB debugging for your device. Sometimes the checkbox option is available just by going to Settings > Developer Options on your device. Check these instructions for more details.

Other times, you have to do some weird shenanigans. I can’t make this up! A direct quote from Android: “On Android 4.2 and newer, Developer options is hidden by default. To make it available, go to Settings > About phone and tap Build number seven times. Return to the previous screen to find Developer options.”

Head back to Unity and hook up your device to your computer. Select Unity\Preferences then select External Tools from the dialog that appears. Ensure the Android SDK location is pointing to the right place: on my machine, it’s in /Applications/AndroidStudio/sdk/ but your setup might differ.

Select Google Play Games\Android Setup…; in the dialog box that appears, you may already see the Application ID for your app. If it’s not there, re-enter it:

Unity Android Setup Dialog

If your Application ID isn’t in the dialog box, and you don’t remember what is, here’s how to find it again:

  1. Go back to the Play Developer console at https://play.google.com/apps/publish/
  2. Click on Game Services (the little controller icon) on the left
  3. Click on your game
  4. Look at the name of your game at the top of the screen. Next to it should be an 11-or-12 digit number:
  5. Application ID location

  6. Copy-and-paste this value and then continue with the tutorial!

Click Setup and after a moment or two you should see a confirmation dialog that everything has been set up correctly. Next, select File\Build Settings\Android\Switch Platform to make this the default platform.

Click Build and Run, and Unity will prompt you for a filename for your Android apk file; choose whatever you’d like, but CircuitRacerAndroid is a good suggestion. Unity then compiles the app, transfers it to your device, and launches it!

If you followed the above steps, you should see Circuit Racer running on your Android device. Try out the single player version of the game!

It's Alive!!!

Open up a terminal window and type:

adb logcat | grep Unity

This reports back any debug output from your device. You don’t even need to restart your app to view the logs!

I/Unity   ( 5914):  [Play Games Plugin DLL] 11/21/14 11:04:20 -08:00 DEBUG: Starting Auth Transition. Op: SIGN_IN status: ERROR_NOT_AUTHORIZED
I/Unity   ( 5914):
I/Unity   ( 5914): (Filename: ./artifacts/AndroidManagedGenerated/UnityEngineDebug.cpp Line: 49)
I/Unity   ( 5914):
I/Unity   ( 5914):  [Play Games Plugin DLL] 11/21/14 11:04:20 -08:00 DEBUG: Invoking user callback on game thread
I/Unity   ( 5914):

Return to the main menu of the game and tap on the multiplayer option. You’ll likely be prompted to sign in, but the attempt will probably fail in a few moments:

Don’t panic — you simply haven’t set up your Client ID for Android as you did for iOS in Part 1 of this tutorial.

Go to the Play Developer console (http://play.google.com/apps/publish/) and select the Game Services icon. Select your app, then click on Linked Apps. Click Link another app then select Android:
Link Another App

Give this a name such as Circuit Racer Debug. Most developers create two Client IDs: one for debug releases, and one for production. Under Package Name, use the sample BundleID you’re using for the iOS version. Developers in the real world often like to add “.debug” to the end of this, but you’ll just keep things simple for the sake of this tutorial.

Turn on Real-time multiplayer, just as you did for the iOS version. Your screen should look like this:

Finished "Link Another App" screen

Click Save, then Continue, and finally click Authorize your app now.

Next you’re prompted for your package name, which should be filled out for you, and a signing certificate fingerprint. Hmm, that’s new; how do you get that?

Execute the following command in Terminal:

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v

When prompted for your password, enter android, which admittedly is a terrible password, but good enough for the purposes of this tutorial. :]

You’ll see the following output:

Certificate fingerprints:
     MD5:  (Lots of hex numbers)
     SHA1: (Even more hex numbers)
     SHA256: (Yet more hex numbers)
     Signature algorithm name: SHA1withRSA
     Version: 3

Copy the hex string next to the SHA1 entry and paste it into the Signing certificate fingerprint field in the dialog box as shown:

Fingerprint added

The above steps tell Google Play’s servers that “I am the owner of com.<mycompany>.CircuitRacer, and here is a giant random number associated with me that basically guarantees it.” At this point, nobody else can claim to own a Client ID for com.<mycompany>.CircuitRacer.

Finalyl, click Create Client and you’re done!

Build and run your game again; note that in the Android world, you can select Replace when saving your apk, since there are no additional post-process steps as there are the Xcode world.

This time around, you should be able to sign in to your multiplayer game; make sure you’re using a different Google account than the one you’re signed into on your iOS device, and that has also been listed as a Tester account. You’ll soon join the multiplayer lobby for your game.

Running on iOS

Here’s how to get your game running on two iOS devices:

  1. Stop the app in XCode.
  2. Plug your second device in to your computer.
  3. Run the app again in Xcode on your second device. You don’t need to re-export from Unity; just hit Run in Xcode.
  4. Once your game is running, sign in to your second iOS device with a different Google account than the one you are using on your first device, and one that has been listed as a Tester account under the Game Services section of the Play Developer console.
  5. Once you’ve signed in, start a multiplayer game; the debug messages will tell you that you’ve joined the multiplayer lobby for your game.
  6. Go back to your first device, start up your Circuit Racer app and request to join a multiplayer game.

After a few moments, you’ll see that your two accounts are connected with some resultant console output like the following:

DEBUG: New participants connected: p_CKq_go_Vq4CMchAB,p_CJXhuOmO0se-BRAB
DEBUG: Fully connected! Transitioning to active state.
DEBUG: Entering state: ActiveState
DEBUG: Entering internal callback for RealTimeEventListenerHelper#InternalOnRoomStatusChangedCallback
 
We are connected to the room! I would probably start our game now.

As far as the Google Play library is concerned, you’re in a real multiplayer game — you just can’t see your opponent’s car or actually interact with the game in any way. Boo. In fact, if you click on the Multiplayer button again, it won’t work because the service thinks you’re still in a game. You’ll need to kill both apps and restart them again to join another multiplayer game.

The important thing is that the Google Play games service has matched up the two accounts on your devices and, in theory, you could start sending messages between the devices.

Since there aren’t any magazines lying around the multiplayer waiting room of your game to read while you wait for another player to join, It might be nice to add a little UI to your game that shows the progress of connecting to the multiplayer game.

Adding a Simple Waiting Room UI

The Google Play Games library you’ve incorporated into your Xcode build has some built-in support for a creating multiplayer waiting room UI. For some reason though, the Unity plug-in doesn’t take advantage of it. I suspect that most real game developers would prefer to build their own waiting room interface with their own themed graphics, which is the approach you’ll follow in this tutorial.

Open MainMenuScript.cs and add the following public variable.

public GUISkin guiSkin;

This variable holds the image that will be the background of your dialog box. You will set this image using the Unity inspector.

Now add the following two instance variables:

private bool _showLobbyDialog;
private string _lobbyMessage;

These variables track whether you should display the lobby dialog box and what message it should contain.

Now, add the following setter method to update _lobbyMessage:

public void SetLobbyStatusMessage(string message) {
    _lobbyMessage = message;
}

Next, add the method below:

public void HideLobby() {
    _lobbyMessage = "";
    _showLobbyDialog = false;
}

The above method simply instructs the Main Menu to stop showing the lobby interface.

Next, open MultiplayerController.cs and add the following public variable:

public MainMenuScript mainMenuScript;

The mainMenuScript variable holds a reference to your mainMenuScript. By doing so, you can call the public methods that you just added.

Replace ShowMPStatus() with the following::

private void ShowMPStatus(string message) {
    Debug.Log(message);
    if (mainMenuScript != null) {
        mainMenuScript.SetLobbyStatusMessage(message);
    }
}

With the changes above, instead of just printing debug messages to the console, you’re telling your mainMenuScript that it should be showing message as a lobby status message.

Now you need to display these messages. Go back to MainMenuScript.cs and replace the following lines in OnGUI():

} else if (i == 1) {
    RetainedUserPicksScript.Instance.multiplayerGame = true;
    MultiplayerController.Instance.SignInAndStartMPGame();
}

…with the following code:

} else if (i == 1) {
    RetainedUserPicksScript.Instance.multiplayerGame = true;
    _lobbyMessage = "Starting a multi-player game...";
    _showLobbyDialog = true;
    MultiplayerController.Instance.mainMenuScript = this;
    MultiplayerController.Instance.SignInAndStartMPGame();
}

Here you set _showLobbyDialog to true, which indicates you’re ready to show a dialog box; you also tell MultiplayerController that this is the class to which it should send lobby status messages.

Next, at the top of OnGUI() wrap that entire for loop inside another if block as follows:

if (!_showLobbyDialog) {
    for (int i = 0; i < 2; i++) {
 
        // Lots of GUI code goes here,,,
 
    }
}

This hides the menu buttons [when the lobby dialog box appears. Even though the dialog box will cover the buttons, they’ll still remain clickable — which could lead to some unexpected behavior from your game! :]

Add the following code to the end of OnGUI():

if (_showLobbyDialog) {
    GUI.skin = guiSkin;
    GUI.Box(new Rect(Screen.width * 0.25f, Screen.height * 0.4f, Screen.width * 0.5f, Screen.height * 0.5f), _lobbyMessage);
}

This displays the dialog box on the screen.

Head back to Unity and go to the MainMenu scene. Select MainMenuGameObject, click the little target next to Gui Skin, and then select GameGuiSkin from the Assets dialog:

Setting your gui skin

This sets the skin of your game using the public guiSkin variable you declared at the start of this section.

Build and run your game; this time you should see your online connection status as a nice series of dialog boxes:

Lobby Dialog

That’s a little more comforting to a player who can’t view the Xcode console log! :]

However, you’re still stuck with this dialog box that never goes away. As nice as the dialog is, playing an actual game would be a lot more fun.

Adhering to the Delegate Pattern

Some of you may have noticed that the code above violates the principle of encapsulation. MultiplayerController directly communicates with MainMenuScript, which means the two classes are highly coupled. If you wrote some really awesome code in MultiplayerController and wanted to reuse it in your new exciting boat racing game “Circuit Sailor”, you’d be in a hard spot.

In much the same way that you use the delegate pattern in Objective-C to logically separate one class from another, you can create an interface in C# to do the same thing.

Head back to Unity and open the Scripts folder in your assets panel. Right-click the panel and select Create\C# Script. Name the script MPInterfaces, then open up the file for editing.

Replace the entire file (yep, the whole thing) with the following code:

public interface MPLobbyListener {
    void SetLobbyStatusMessage(string message);
    void HideLobby();
}

Here you declare an interface, which as you’ll recall is sort of like a contract. Any class that implements this interface promises that it will add any requisite methods.

Go back to MultiplayerController and modify the following variable:

public MainMenuScript mainMenuScript;

…to the following:

public MPLobbyListener lobbyListener;

You’re replacing mainMenuScript, which was tied directly to a MainMenuScript class, with an instance of lobbyListener instead. You’re saying you don’t care what kind of class this is, so long as it implements the MPLobbyListener interface.

Modify ShowMPStatus so that mainMenuScript variable is replaced by a lobbyListener variable:

private void ShowMPStatus(string message) {
    Debug.Log(message);
    if (lobbyListener != null) {
        lobbyListener.SetLobbyStatusMessage(message);
    }   
}

The following code decouples the current object from the MainMenuScript since the code now works upon an interface as opposed to an instance.

Now open MainMenuScript.cs and modify the class declaration at the beginning of the file as follows:

public class MainMenuScript : MonoBehaviour, MPLobbyListener {

This change indicates that the class implements the MPLobbyListener interface.

Now find the following line in OnGUI():

MultiplayerController.Instance.mainMenuScript = this;

..and modify it as follows:

MultiplayerController.Instance.lobbyListener = this;

This last bit of code removes the last reference of the MainMenuScript and uses the new interface instead.

Since MainMenuScript already implements the required methods, it’s fulfilled its contract with the MPLobbyListener interface.

If you were to build and run your app at this point, it would still look and run the same, But you could feel confident developing it further knowing that you have some nicely encapsulated code under the hood! :]

RefactoringLikeABoss

Starting the Multiplayer Game

Now that you’ve cleaned up your code, you can add some multiplayer game logic to your project.

Add the following code to the if (success) block of OnRoomConnected in MultiplayerController.cs:

lobbyListener.HideLobby();
lobbyListener = null;
Application.LoadLevel("MainGame");

This hides the lobby dialog, clears out your mainMenuScript and then loads up your MainGame scene. If you were to run your app at this point, you’d see that you’re taken into the game as soon as you’re connected.

Having only one car on the screen somewhat limits the multiplayer experience. Your next task is to add the opponent’s car.

Browse out your assets folder; you’ll see that there’s an existing Prefab for an OpponentCar object. Click on it, then click Add Component and add a new Script Component. Name it OpponentCarController, then click Create and Add.Opponent Car

Finally, double-click on the newly created script to edit it. OpponentCarController will represent the other players in the game; it doesn’t need to be very smart, since your human opponents will do all the driving.

At the top of your class definition, add the following variable:

public Sprite[] carSprites;

This variable is going to hold the sprites of various car images.

Next, add the following method into the script:

public void SetCarNumber (int carNum) {
    GetComponent<SpriteRenderer>().sprite = carSprites[carNum-1];
}

This lets you store your collection of car sprites in an array (yellow, blue, and red); you can then set the car sprite for a specific player simply by calling SetCarNumber on it.

Go back to Unity, then click on the OpponentCar Prefab. Find the Car Sprites entry in the script component, and change its size to 3. Then select car_1 for element 0, car_2 for element 1, and car_3 for element 2 as shown below:

Adding car sprites

Assigning Player Colors

At this point, you’re left with an interesting problem: how do you determine the color to assign to each player? If it was simply the player’s choice, all players could choose the yellow car. Or one player could think her car is red and her opponent’s car is blue, while her opponent thinks the exact opposite. How can you get all game clients to agree on the state of the world?

At first, you might think “Who cares?” But it quickly becomes evident that this sort of thing does matter in many cases. In a poker game, all clients need to decide who should deal first. In a real-time strategy game, you need to decide who should start at which base.

In your racing game, you need to decide which lane each client will start in; therefore this problem applies to you. Other game tutorials on this site usually solve this problem by generating random numbers; the highest number goes first, deals first, gets the yellow car and so on.

Shouting Random Numbers

Instead of this approach, you can take advantage of the participant ID entity in Google Play Games.

Each player is assigned a participant ID by the game service when they join a game. This participant ID is different than a traditional player ID as it is randomly generated and only exists for the duration of that gameplay session. It’s a nice way for strangers to refer to each other in a game without having to use any data that could be traced back to the actual player.

Participant IDs

You can easily sort out the game configuration by sorting the list of participant IDs and assigning qualities to players based on their order in this list; the yellow car goes to the first listed participant, the blue car to the second, etc. In a poker game, the player listed first could be the dealer, and so on.

Note: Google Play engineers have asked me to point out that these participant IDs are only mostly random; it’s possible for one player to get a lower-ordered participant ID more often than other players.

If your game design confers a major gameplay advantage to the player listed first, you might want to sort the list of participant IDs, then randomly choose the player who gets to be dealer, gets the sniper rifle, or gets the inside lane. In your case, the yellow car isn’t all that special, so you don’t need to do anything beyond sorting the list.

Open MultiplayerController.cs and add the following line to the top of the file:

using System.Collections.Generic;

This ensures that List is defined.

Next, add the following method:

public List<Participant> GetAllPlayers() {
    return PlayGamesPlatform.Instance.RealTime.GetConnectedParticipants ();
}

This simply gets a list of all participants in the room. It’s worth noting here that the library already sorts this list for you by participantId, so you don’t need to sort it yourself.

Add the following method to get the ParticipantID of the local player:

public string GetMyParticipantId() {
    return PlayGamesPlatform.Instance.RealTime.GetSelf().ParticipantId;
}

Open GameController.cs and add the following imports:

using System.Collections.Generic;
using GooglePlayGames.BasicApi.Multiplayer;

Now add the following variables near the top of the class definition:

public GameObject opponentPrefab;
 
private bool _multiplayerReady;
private string _myParticipantId;
private Vector2 _startingPoint = new Vector2(0.09675431f, -1.752321f);
private float _startingPointYOffset = 0.2f;
private Dictionary<string, OpponentCarController> _opponentScripts;

You’ll be using all these variables in the next method. Add the following code to the empty implementation of SetupMultiplayerGame():

// 1
_myParticipantId = MultiplayerController.Instance.GetMyParticipantId();
// 2
List<Participant> allPlayers = MultiplayerController.Instance.GetAllPlayers();
_opponentScripts = new Dictionary<string, OpponentCarController>(allPlayers.Count - 1); 
for (int i =0; i < allPlayers.Count; i++) {
    string nextParticipantId = allPlayers[i].ParticipantId;
    Debug.Log("Setting up car for " + nextParticipantId);
    // 3
    Vector3 carStartPoint = new Vector3(_startingPoint.x, _startingPoint.y + (i * _startingPointYOffset), 0);
    if (nextParticipantId == _myParticipantId) {
        // 4
        myCar.GetComponent<CarController> ().SetCarChoice(i + 1, true);
        myCar.transform.position = carStartPoint;
    } else {
        // 5
        GameObject opponentCar = (Instantiate(opponentPrefab, carStartPoint, Quaternion.identity) as GameObject);
        OpponentCarController opponentScript = opponentCar.GetComponent<OpponentCarController>();
        opponentScript.SetCarNumber(i+1);
        // 6
        _opponentScripts[nextParticipantId] = opponentScript;
    }
}
// 7
_lapsRemaining = 3;
_timePlayed = 0; 
guiObject.SetLaps(_lapsRemaining);
guiObject.SetTime(_timePlayed);
 _multiplayerReady = true;

Taking each numbered comment in turn:

  1. This gets the local player’s participant ID and stores it as a local variable.
  2. This grabs the list of sorted participants from the multiplayer controller so you can iterate through them.
  3. This calculates the start point for each car based on its order in the list.
  4. If the participantID of the current player matches the local player, then instruct the CarController script to set the car number, which determines its color.
  5. Otherwise, instantiate a new opponent car from the Prefab object and set its car number and color as well.
  6. You’re storing this carController in a dictionary with the participant ID as the key. Since you’ll be getting a lot of messages in the form “Participant ID xxx said so-and-so”, storing your opponents in a dictionary is an easy way to refer to them later by their participant ID.
  7. Finally, you initialize a few other game constants and set _multiplayerReady to true, signalling that you’re ready to receive multiplayer message. Since there’s no guarantee all clients will start at precisely the same time, weird things may happen if you begin to receive game messages while you’re still setting up. The _multiplayerReady flag will protect against that, as you’ll see later.

You might be thinking “Hey, isn’t it overkill to create a dictionary to store all of my OpponentCarControllers, when there’s really only one? Why not just have a simple opponentCar variable and be done with it?” Although you’re only testing the game with two players at the moment, you’ll want the model to be flexible enough to handle more than two players in the future, and the dictionary is a good way to prepare for that scenario.

Go back to Unity and open the MainGame scene. Select GameManager and set the OpponentPrefab variable in the inspector to be your OpponentCar prefab object, then save the scene as shown below:

Setting the Opponent Car

Build and run your game again; start a multiplayer game and you’ll see that one player has been assigned the yellow car and the other has been assigned the blue — and each player can drive themselves around the track.

Screen Shot 2015-01-22 at 6.30.10 PM

Drive each of the cars around the track, and you’ll notice some strange gameplay: your car’s progress isn’t reported on your opponent’s device and vice versa. Time to get these devices talking to each other!

Making the Cars Move

You’ll move the opponent’s cars by reporting the location of each car to all players in the room at frequent intervals. When your game receives an update from another client, you can move the appropriate OppponentCar to its current location.

Here’s the big question: how often are these “frequent intervals”? A typical PC game sends updates about 10-30 times per second. That’s a lot! However, mobile device games need to worry about battery life and data usage limits, so you will definitely want to send updates less often than that.

You’ll start by sending a network call every Update() — which is definitely too often, but works as a good starting point — and you can work on optimizing your network calls in Part 3 of this tutorial series.

Sending Message Data

Add the following code to the end of DoMultiplayerUpdate() in GameController.cs:

MultiplayerController.Instance.SendMyUpdate(myCar.transform.position.x, 
                                            myCar.transform.position.y,
                                            myCar.rigidbody2D.velocity, 
                                            myCar.transform.rotation.eulerAngles.z);

Here you send all information the other players need to display the local player’s car appropriately: their x and y coordinates, z-axis rotaion, and the car’s current velocity.

At this point, Unity is probably complaining because you haven’t defined SendMyUpdate. You’ll do that in just a moment, once you add a bit of supporting code.

Add the following private variables near the top of your MultiplayerController class definition:

private byte _protocolVersion = 1;
// Byte + Byte + 2 floats for position + 2 floats for velcocity + 1 float for rotZ
private int _updateMessageLength = 22;
private List<byte> _updateMessage;

You’ll see how you use each of these variables shortly.

Add the following code to the beginning of private MultiplayerController() to create your _updateMessage list:

_updateMessage = new List<byte>(_updateMessageLength);

Then add SendMyUpdate to your MultiplayerController class:

public void SendMyUpdate(float posX, float posY, Vector2 velocity, float rotZ) {
    _updateMessage.Clear ();
    _updateMessage.Add (_protocolVersion);
    _updateMessage.Add ((byte)'U');
    _updateMessage.AddRange (System.BitConverter.GetBytes (posX));  
    _updateMessage.AddRange (System.BitConverter.GetBytes (posY));  
    _updateMessage.AddRange (System.BitConverter.GetBytes (velocity.x));
    _updateMessage.AddRange (System.BitConverter.GetBytes (velocity.y));
    _updateMessage.AddRange (System.BitConverter.GetBytes (rotZ));
    byte[] messageToSend = _updateMessage.ToArray(); 
    Debug.Log ("Sending my update message  " + messageToSend + " to all players in the room");
    PlayGamesPlatform.Instance.RealTime.SendMessageToAll (false, messageToSend);
}

First, you put your message in a byteArray. The easiest, if not terribly efficient, way to do this is to build the message using a List of bytes. You’ll re-use the _updateMessage variable you created in the previous step, which is set at 22 bytes — just enough for everything you need to send. In Unity, floats are represented as 4 bytes.

The very first byte you add is a single byte to represent the protocol version. You can think of this as the version number of the message itself. You’ll see why this is important later in this tutorial.

The next byte is the instruction to send. It’s possible for a client to send all sorts of messages to the other players, so it’s helpful to first include a character as one of your first bytes to describe what’s going to be in the rest of the message. In your case, you’re using U to declare that you are sending an Update.

After that, you add the passed-in values of the position, velocity, and rotation of the car. AddRange is one way to add a number of bytes to a list, and the System.BitConverter.GetBytes() method reinterprets each of these float values as a series of bytes.

Next, you convert this list into a byteArray using your list’s toArray() method. Finally, you send this message to all the other players using the Google Play library’s SendMessageToAll method, which takes two arguments:

  1. Whether or not this message should be sent reliably
  2. The actual message to send, as a byteArray

The second argument is pretty self-explanatory, but what does it mean to send a message “reliably”?

Reliable or Unreliable?

Typically there are two ways a client can send messages to other clients in a multi-player game.

The unreliable way is via the UDP network protocol. When you send messages over UDP, a small percentage of them don’t always make it to the target device. And if they are received, there’s no guarantee they’ll be received in the order they were sent — especially in your game, since you’re sending updates so frequently.

The reliable method uses the TCP network protocol. It guarantees that a message will always be received — eventually — by the target device and, even better, all messages will be received in the order they were sent.

So…why wouldn’t you choose the reliable method, you ask? The answer is that all this convenience with reliable network messaging comes at a considerable cost in speed. Reliable messages are significantly slower than unreliable ones, and the amount of slowdown can vary a lot.

Think about it this way: since reliable messages are guaranteed to be received in order, what happens if one message doesn’t make it to its target? That’s right, all of your other messages will be delayed and not received by the other player until the first one is re-sent and received, which can take a considerable amount of time:

The problem with reliable messages

The general rule for most game developers is to use reliable messages only when speed isn’t important, such as in the theoretical poker game discussed earlier. In fact, many developers of action games try to not use reliable messages at all, and instead implement some lightweight logic as needed on top of the UDP protocol to add little bits of reliability as needed.

Build and run your game now; join a multiplayer game and you’ll see from your debug logs that you’re sending dozens of messages per second:

Sending my update message  System.Byte[] to all players in the room
Sending my update message  System.Byte[] to all players in the room
Sending my update message  System.Byte[] to all players in the room
Sending my update message  System.Byte[] to all players in the room

There’s no need to clutter up the debug log with these messages; remove the call to Debug.Log message from SendMyUpdate() to streamline the debuglog.

You know that your client is sending out update messages, but now you need to do something when your game receives those messages from an opponent, like, say, move their car around the track! :] You’ll tackle that next.

Receiving Message Data

In MultiplayerController.cs, replace the code of OnRealTimeMessageReceived with the following:

// We'll be doing more with this later...
byte messageVersion = (byte)data[0];
// Let's figure out what type of message this is.
char messageType = (char)data[1];
if (messageType == 'U' && data.Length == _updateMessageLength) { 
    float posX = System.BitConverter.ToSingle(data, 2);
    float posY = System.BitConverter.ToSingle(data, 6);
    float velX = System.BitConverter.ToSingle(data, 10);
    float velY = System.BitConverter.ToSingle(data, 14);
    float rotZ = System.BitConverter.ToSingle(data, 18);
    Debug.Log ("Player " + senderId + " is at (" + posX + ", " + posY + ") traveling (" + velX + ", " + velY + ") rotation " + rotZ);
    // We'd better tell our GameController about this.
}

You’re basically doing the opposite of what SendMyUpdate does; instead of putting data into a ByteArray, you’re extracting it into useful values that you can use in your game. System.BitConverter.ToSingle() takes a ByteArray and an offset, and converts those bytes into a native data type.

Now that you have the information you need, you can do something useful with it. Add the following interface definition to MPInterfaces.cs:

public interface MPUpdateListener {
    void UpdateReceived(string participantId, float posX, float posY, float velX, float velY, float rotZ);
}

Next, add the following public variable to the top of your MultiplayerController class:

public MPUpdateListener updateListener;

Then, add the following to the end of the if block in OnRealTimeMessageReceived, right after the We'd better tell our GameController about this comment:

if (updateListener != null) {
    updateListener.UpdateReceived(senderId, posX, posY, velX, velY, rotZ);
}

Go back to GameController.cs and declare that it satisfies the MPUpdateListener interface as follows:

public class GameController : MonoBehaviour, MPUpdateListener {

Then add the following method somewhere inside that same class:

public void UpdateReceived(string senderId, float posX, float posY, float velX, float velY, float rotZ) {
    if (_multiplayerReady) {
        OpponentCarController opponent = _opponentScripts[senderId];
        if (opponent != null) {
            opponent.SetCarInformation (posX, posY, velX, velY, rotZ);
        }
    }
}

Here you’re checking that _multiplayerReady is true, as there’s always a chance you could receive a message before you’re done your setup; if you don’t protect against this you could end up with some fun and exciting null pointer exceptions.

If everything checks out, you then call SetCarInformation() on the OpponentCarController corresponding to the participantID of the sender.

Now open OpponentCarController.cs and add the following method:

public void SetCarInformation(float posX, float posY, float velX, float velY, float rotZ) {
    transform.position = new Vector3 (posX, posY, 0);
    transform.rotation = Quaternion.Euler (0, 0, rotZ);
    // We're going to do nothing with velocity.... for now
}

There’s nothing too surprising in SetCarInformation; you simply set this car’s position and rotation to the values reported by its game client.

Finally, go back to GameController and add the following line to the top of SetupMultiplayerGame():

MultiplayerController.Instance.updateListener = this;

In the code above, GameController tells MultiplayerController that “I am the class you should talk to when you get an update from another player in a multiplayer game.” So MultiplayerController will call UpdateReceived() in your GameController, which will then call SetCarInformation() on the appropriate OpponentCarController object.

Build and run your app on both devices; you should now be able to move your car on one screen and see its movement reflected on the other screen. Move both cars on both devices at the same time, and hey — you’ve got yourself a racing game! :]

Look, ma! We're racing!

Victory!

If everything hasn’t gone well, I’d suggest you try the following two things:

  • Make sure you’re running the latest version of the game on both devices. Sometimes I’ll update my game on only one device and forget that I need to load it on the other as well.
  • Kill the app on both devices and try again. Sadly, the “try it a second time and maybe it will magically work this time” solution seems to work for multiplayer games more times than I’d care to admit.

Where to Go From Here?

You can download the completed project over here.

Your multiplayer racing game is off to a really good start, but there still are a few problems that you’ll need to address:

  • A biggie: the game doesn’t end! You’ll need a way to declare a winner and leave the room.
  • You’re making network calls more frequently than you need to. It’d be nice if your game was a little more considerate of your user’s battery and data plan.
  • Depending on your network connection, the cars’ movements might look a little jittery. You need a way to smooth them out and make the animation look fluid.

Luckily, these are all relatively easy issues to fix — and you’ll address them all in Part 3 of this tutorial series. As always, if you have any questions or comments, please feel free to join in the discussion below!

Creating a Cross-Platform Multi-Player Game in Unity — Part 2 is a post from: Ray Wenderlich

The post Creating a Cross-Platform Multi-Player Game in Unity — Part 2 appeared first on Ray Wenderlich.


Video Tutorial: Collection Views Part 3: Multiple Sections

Creating a Cross-Platform Multi-Player Game in Unity — Part 3

$
0
0

Get ready to start engines and hope the network is actually working!

Get ready to start engines and hope the network is actually working!

Welcome back to Part 3 of our tutorial series on creating a multiplayer game in Unity!

In part one, you developed your game to the point where your game clients could connect to each other.

In part two, you allowed clients to join a game room, send their current position to each other, and update the game play based on the positions received. It’s starting to look like a real game

However, you uncovered a few things in that need to be addressed: you’re making network calls more frequently than you need to; the cars’ movements look a little jittery and there’s no way to end the game! You’ll take care of these things in this part of the tutorial series — and learn some game design tips along the way.

In this tutorial, you will do the following:

  • Learn some of the various techniques with handling network latency.
  • Implement a game over state.
  • Provide various ways for clients to disconnect from a game.

And when you finish, you’ll have an honest-to-goodness multiplayer game.

Reducing Network Traffic

You’re currently sending updates once every frame, which is way more than you need. Frequent updates can ensure smooth gameplay, but that comes at the cost of data usage, battery drain and potentially clogging the network by sending more updates than it can handle.

So how frequently should you send updates? The annoyingly vague answer is: “as often as you need to keep the gameplay good, but no more than that.” Frankly, update frequency is always a delicate balancing act with no “perfect” solution. For this tutorial, you’ll reduce your network update calls to six per second, which is an arbitrary amount, but it’s a good place to start.

An Aside on Compression Strategies

Cutting down your network calls from 30 times a second to 6 is a pretty good way to reduce your network traffic, but you could do even more if you wanted. For instance, you’re using a luxurious 4 bytes to represent the player’s rotation – which is probably overkill. You could reduce this down to a single byte if you took the player’s rotation (between 0 and 360), multiplied it by 256/360, and then rounded it to the nearest whole number. To translate it back, you would multiply that number again by 360/256.

There would be some loss of accuracy, of course; for instance a car originally with a z rotation of 11 degrees would end up as 11.25. But, really, is that something you would notice when these cars are going 300 pixels per second around a track?

// Converting degrees to 1 byte...
(int)(11.0 * 256 / 360) = 8
 
// Converting back again...
8 * 360.0 / 256.0 = 11.25

Similarly, you’re using 4 bytes each to represent your car’s position and velocity in the x and y axis. You could represent the velocity with a single byte in each axis, and you might even be able to do the same with its position. And suddenly, your update message goes from 22 bytes to 7 bytes each. What a savings!

This might seem like you’re going a little nutty with compression, particularly if you’re coming from the web world where you’re probably used to dealing with messages represented as strings and numbers in a JSON blob. But real-time multiplayer game developers tend to worry about this stuff a little more than other, saner, folks.

Decreasing Update Frequency

Open your project. If you want to start with this part, feel free to download the starter project.

Note: You will have to configure the starter project to have your own ClientID as described in “>Part One

.

Open GameController.cs found in the scripts folder, and add the following variable near the top:

private float _nextBroadcastTime = 0;

This variable will hold the time to determine when the next time the script should broadcast client data.

Find the following line in DoMultiplayerUpdate:

MultiplayerController.Instance.SendMyUpdate(myCar.transform.position.x, 
                                            myCar.transform.position.y,
                                            myCar.rigidbody2D.velocity, 
                                            myCar.transform.rotation.eulerAngles.z);

…and modify it as shown below:

if (Time.time > _nextBroadcastTime) {
    MultiplayerController.Instance.SendMyUpdate(myCar.transform.position.x, 
                                                myCar.transform.position.y,
                                                myCar.rigidbody2D.velocity, 
                                                myCar.transform.rotation.eulerAngles.z);
    _nextBroadcastTime = Time.time + .16f;
}

This sends updates once every 0.16 seconds, or about six times per second.

Build and run your project; update the game on one device only, so you can compare the two versions side-by-side. What do you notice about the gameplay?

My Eyes! The Goggles Do Nothing!

The gameplay looks terrible — it’s like you’ve replaced your awesome game with a slideshow! :[

So you need to bump up the rate of your network updates, right? Not so fast; there are a few tricks of the trade that can improve your cruddy slideshow experience — without bumping up the frequency of your update calls.

Trick #1: Interpolation

The first trick to improve your gameplay is to interpolate your opponent’s position; instead of teleporting your opponent’s car to its new position, you can smooth out the action by calculating the path the car should take instead.

Add the following variables to the top of OpponentCarController:

private Vector3 _startPos;
private Vector3 _destinationPos;
private Quaternion _startRot;
private Quaternion _destinationRot;
private float _lastUpdateTime;
private float _timePerUpdate = 0.16f;

Now add the following code to Start():

_startPos = transform.position;
_startRot = transform.rotation;

This initially sets the start position of the car at the start of the game.

Next, replace all of SetCarInformation() with the following:

public void SetCarInformation(float posX, float posY, float velX, float velY, float rotZ) {
    // 1
    _startPos = transform.position;
    _startRot = transform.rotation;
    // 2
    _destinationPos = new Vector3 (posX, posY, 0);
    _destinationRot = Quaternion.Euler (0, 0, rotZ);
    //3
    _lastUpdateTime = Time.time;
}

Taking each numbered comment in turn:

  1. Each time you receive a position update, keep track of the current position and rotation of the car.
  2. Next, record the new position and location.
  3. Finally, record the current time — you’ll see in a moment why this is necessary.

Now, add the following code to Update():

void Update () {
    // 1
    float pctDone = (Time.time - _lastUpdateTime) / _timePerUpdate;
 
    if (pctDone <= 1.0) {
        // 2
        transform.position = Vector3.Lerp (_startPos, _destinationPos, pctDone);
        transform.rotation = Quaternion.Slerp (_startRot, _destinationRot, pctDone);
    }   
}

This is not a lot of code, but it is a lot of math. Here’s what’s going on:

  1. pctDone stores how far along you should be on the interpolated path, based on the assumption that it takes approximately 0.16 seconds to process an update.
  2. Then you use the value in pctDone information to update your transform’s position and rotation accordingly.

Build and run your game in Unity and update it on both devices; you should notice that the gameplay runs a little more smoothly.

Things still don’t look perfect, though, as the cars still lurch around the track a bit — and it only gets worse if your network conditions deteriorate. Why? Your cars travel for 0.16 seconds until they reach their new position — and then they stop dead in their tracks until you receive the next update from the network. If that update takes more than 0.16 seconds to arrive, say 0.30 seconds, then the car will sit there for 0.14 seconds until it moves again.

So…is it time to bump up the rate of your network updates? No! :] You can fix the lurchiness with a second trick from the game developer’s toolbox.

Trick #2: Extrapolation

Rather than halting the car until the next update comes, you can take advantage of the velocity information you’ve received and keep the car moving until you get the next update. This is a cheap and easy trick that smoothes over network lag issues.

Open OpponentCarController and add the following variable:

private Vector3 _lastKnownVel;

This variable will hold the Vector location of the last known velocity of the car.

Add the following line near the end of SetCarInformation, right before you set _lastUpdateTime:

_lastKnownVel = new Vector3 (velX, velY, 0);

This sets the last known velocity based off the velocity that was used to set the car’s information.

Finally, modify the if block in Update() as follows:

if (pctDone <= 1.0) {
    // Previous code
    // ...
} else {
    // Guess where we might be
    transform.position = transform.position + (_lastKnownVel * Time.deltaTime);
}

This sets the position based off the velocity value.

Build and run your game again; only update one of your devices to compare the motion between the two versions of the game. You should see a lot less lurching around than before.

Note: While your game certainly looks better, it’s actually less accurate than before. Why? When you receive an update from another player, you’re seeing their position at some point in the past.

Adding this extrapolation means it takes you longer to bring the player to the point where they were a few milliseconds ago! And if your opponent’s updates take too long to arrive, their car will calmly drive off the edge of the playing field.

This loss of accuracy probably doesn’t matter much in this game; if you needed to be more accurate, you could try to guess the position of your opponent’s car — and you might end up with some slightly more accurate car positions.

This would probably involve calculating some accurate ping times for your clients, as well as adding a bit of AI. This is beyond the scope of this tutorial, but it would make for some great experience with AI and game physics if you wanted to tackle it on your own!

So you’ve got some smooth looking car action — but you still don’t have a way to end the game. Time to fix that!

Finishing a Game

In this game, the race is won once a competitor completes three laps. At that point, you need to do three things:

  1. Stop the local player’s car from moving.
  2. Send a “game over” message to all the other players once you’ve finished and note your final time.
  3. Show the game over screen once you’ve received a “game over” message from all other players.

Why do you need to send your final time in step 2 — why not just mark the order that you receive a “game over” message? Like everything in multiplayer games, it comes down to network lag. Imagine a situation where Player 1 finishes first, but by the time she sends a “game over” message to Player 2, Player 2 has already finished his game, so he thinks he’s finished first.

And since all players in your networked game don’t necessarily start at the exact same time, a faster player could receive a “game over” message by the slower player before they finish!

Crazy Time!

You can’t rely on the order of messages to carry meaning in a networked game. You can only guarantee a fair resolution to the match by letting each client report their finishing times to all other clients.

Sending a Game Over Call

Open GameController find the following code in Update():

    if (_multiplayerGame) {
        // TODO: Probably need to let the game know we're done.
    } else {

…and replace it with the following code:

  if (_multiplayerGame) {
    // 1
    myCar.GetComponent<CarController>().Stop();
    // 2
    MultiplayerController.Instance.SendMyUpdate(myCar.transform.position.x,
                                                myCar.transform.position.y,
                                                new Vector2(0,0),
                                        myCar.transform.rotation.eulerAngles.z);
    // 3
    MultiplayerController.Instance.SendFinishMessage(_timePlayed);
 
  } else {

Looking in depth at the logic you added above:

  1. First, you tell the car controller to stop moving, at which point it will ignore all further updates from the interface.
  2. Next, you send an update to all other players with your current position, and a velocity of 0,0. This ensures your car stops at the finish line on all game clients.
  3. Finally, you call SendFinishMessage, which is a yet-to-be-written method in MultiplayerController which tells all of your opponents you’ve finished.

Open MultiplayerController and add the following private variable near the beginning of your class to track the length of the message:

// Byte + Byte + 1 float for finish time
private int _finishMessageLength = 6;

Now create the following method just after SendMyUpdate():

public void SendFinishMessage(float totalTime) {
    List<byte> bytes = new List<byte>(_finishMessageLength); 
    bytes.Add (_protocolVersion);
    bytes.Add ((byte)'F');
    bytes.AddRange(System.BitConverter.GetBytes(totalTime));  
    byte[] messageToSend = bytes.ToArray ();
    PlayGamesPlatform.Instance.RealTime.SendMessageToAll (true, messageToSend);
}

This is quite similar to SendMyUpdate(); the ‘F’ character lets other players know this is an “I’m done and here’s my final time” call, followed by a float value containing the total elapsed time.

A big difference is the true argument in the SendMessageToAll call, which sends this message reliably, instead of via the unreliable mechanism you’ve used all along. Why? This time it’s extra-super-important that all players know your car has finished. If this message were lost, your game would never end.

Even though it’s important that your message is delivered, the timing of your message doesn’t matter all that much. Even if this call was delayed by two or three seconds, it just means that the final screen wouldn’t appear for a few seconds.

Receiving the Game Over Call

Now you’ll need some logic to handle this “game over” message.

Still working in MultiplayerController, add the following code to the end of the if (messageType == 'U' ... block in OnRealTimeMessageReceived:

} else if (messageType == 'F' && data.Length == _finishMessageLength) {
    // We received a final time!
    float finalTime = System.BitConverter.ToSingle(data, 2);
    Debug.Log ("Player " + senderId + " has finished with a time of " + finalTime);    
}

This code checks to see if the message is a game-over message, at which point, it parses the final time and prints out a log message.

Build and run your game; race your cars around the track and once a player completes three laps, they’ll come to a stop and a message similar to Player p_CPT-6O-nlpXvVBAB has finished with a time of 15.67341 message should appear in your console log.

Note: To speed up testing, set _lapsRemaining to 1 in SetupMultiplayerGame of GameController.cs. But don’t forget to set it back when you want to play a real game! :]

Although the cars have stopped, you still need to handle the end-game logic and figure out who won.

Add the following line to MPUpdateListener in MPInterfaces:

void PlayerFinished(string senderId, float finalTime);

This declares that PlayerFinished is part of the MPUpdateListener interface.

Now you need to handle this information in GameController! Add the followiong variable to GameController.cs:

private Dictionary<string, float> _finishTimes;

This sets up a dictionary to map the finish times to participantIDs.

Inside of SetupMultiplayerGame(), add the following line before the start of the for-loop:

_finishTimes = new Dictionary<string, float>(allPlayers.Count);

Just inside the for-loop, add the following the line of code just underneath the first line like so:

for (int i =0; i < allPlayers.Count; i++) {
    string nextParticipantId = allPlayers[i].ParticipantId;             
     _finishTimes[nextParticipantId] = -1;   // <-- New line here! 
    ...

You initialize each entry with a negative number, which is an easy way to indicate that this player hasn’t finished yet.

Next, add the following method to GameController.cs:

public void PlayerFinished(string senderId, float finalTime) {
    Debug.Log ("Participant " + senderId + " has finished with a time of " + finalTime);
    if (_finishTimes[senderId] < 0) {
        _finishTimes[senderId] = finalTime;
    }
    CheckForMPGameOver();
}

This simply records the finishing time of this player in the dictionary.

Next, add the following method underneath the previous one:

void CheckForMPGameOver() {
    float myTime = _finishTimes [_myParticipantId];
    int fasterThanMe = 0;
    foreach (float nextTime in _finishTimes.Values) {
        if (nextTime < 0) { // Somebody's not done yet
            return; 
        }
        if (nextTime < myTime) {
            fasterThanMe++;
        }
    }
    string[] places = new string[]{"1st", "2nd", "3rd", "4th"};
    gameOvertext = "Game over! You are in " + places[fasterThanMe] + " place!";
    PauseGame(); // Should be redundant at this point
    _showingGameOver = true;
    // TODO: Leave the room and go back to the main menu
}

In the code above you’re iterating through the finish times of all of the players in your dictionary. If any of them are negative, it means they haven’t finished yet and you can jump out early. Otherwise, you keep track of how many finish times are faster than the local player’s so you can display the appropriate game over text. Then you set _showingGameOver to true so that your OnGUI() method knows to display the game over dialog box.

Next, add the following line to OnRealTimeMessageReceived() in MultiplayerController.cs, just after the Debug.Log line in the else-if block:

updateListener.PlayerFinished(senderId, finalTime);

Finally, you need to tell the local device that the game is done, as calling SendMessageToAll() doesn’t send a message to the local player.

Open GameController.cs and in Update(), add the following code directly underneath the MultiplayerController.Instance.SendFinishMessage(_timePlayed); line:

PlayerFinished(_myParticipantId, _timePlayed);

Build and run your game on both devices; race both cars around the track and you should now have a lovely game over dialog! …and once again, your game is stuck.

Game Over Dialog

It’s high time that this game has a proper exit strategy! Fortunately, that’s your very next task. :]

Leaving a Game (Normally)

Leaving a multiplayer game is quite straightforward; you simply call the Google Play platform’s LeaveRoom() method.

Add the following line to the end of CheckForMPGameOver in GameController.cs, where the TODO line is:

Invoke ("LeaveMPGame", 3.0f);

This calls the unwritten LeaveMPGame after a three-second pause.

Add that method next:

void LeaveMPGame() {
    MultiplayerController.Instance.LeaveGame();
}

Now you can add the missing LeaveGame() call to MultiplayerController.cs as follows:

public void LeaveGame() {
    PlayGamesPlatform.Instance.RealTime.LeaveRoom ();
}

So that disconnects you from the Google Play game room — but you haven’t yet instructed your game client to do anything different, so your game will stay stuck on the game over screen.

There are a few ways to fix this; you could simply load the MainMenu scene as soon as you call LeaveMPGame. That would work, but a better method is to wait until OnLeftRoom() in MultiplayerController is called by the platform. At that point, you know you’ve left the room for good and your Game Controller can perform any necessary cleanup tasks.

Another advantage of this approach is that if you added other ways to leave the multiplayer room, they’d all be caught and handled by this same code path, which prevents against future redundant code.

Here’s a quick diagram explaining all this:

This is the full series of calls that are made when you leave a room. (The ones in gray are handled by the library.)

This is the full series of calls that are made when you leave a room. (The ones in gray are handled by the library.)

Add the following line to MPUpdateListener in MPInterfaces:

void LeftRoomConfirmed();

This defines the interface to let any listeners know that the player has left the room.

Then, modify OnLeftRoom() in MultiplayerController.cs as follows:

public void OnLeftRoom ()
{
    ShowMPStatus("We have left the room.");
    if (updateListener != null) {
        updateListener.LeftRoomConfirmed();
    }
}

Once this method is called, it is safe to call LeftRoomConfirmed() on the delegate.

Finally, add the following method to GameController.cs:

public void LeftRoomConfirmed() {
    MultiplayerController.Instance.updateListener = null;
    Application.LoadLevel ("MainMenu");
}

Once this method is called, it is safe to move the player from the current scene, to the MainMenu scene.

Build and run your game; race around the track and when you finish, you should see the game over dialog box. After about three seconds, you’ll find yourself at the main menu ready to play another game!

So, are we done?

Before you call it a day, you should test some networking edge cases, such as losing a connection altogether. Start up another game on both devices and kill the app using the task manager on one device partway through the race.

Finish the race on the other device — but as soon as your car finishes, it will wait there forever, waiting for its opponent to finish.

Leaving the Game (Less Normally)

It’s a fact of life that all players won’t see a game through to completion. This can happen intentionally, where a player rage-quits, has to go to an appointment, or just gets fed up with an opponent who’s not playing fair; or unintentionally, where your game crashes, the player loses their network connection, or their battery dies. Your job as a game designer is to handle these unexpected quits in a clean manner.

Take the case where a player consciously leaves the game. Do you really want to encourage players to rage-quit? No, but there are lots of valid reasons someone might leave a game; many events in the real world often require you to put your device away for a moment, such as saying your wedding vows. :] To that end, you’ll add a “Leave Game” button in your UI.

Open GameController.cs and add the following code to the beginning of OnGUI(), just outside of the if block:

if (_multiplayerGame) {
    if (GUI.Button (new Rect (0.0f, 0.0f, Screen.width * 0.1f, Screen.height * 0.1f), "Quit")) {
 
        // Tell the multiplayer controller to leave the game
        MultiplayerController.Instance.LeaveGame();
    } 
}

This calls LeaveGame() in MultiplayerController, which in turn calls LeaveRoom() in the Google Play platform. Once your player has left the room, the platform reports this back to your game in the OnLeftRoom() callback, which will then call LeaveGameConfirmed() in your Game Controller, just as in the diagram shown earlier.

Note: This is actually a pretty terrible way for a player to quit the game. At the very least, you’d want to put up a confirmation dialog so that a player can’t accidentally leave the game with a single tap on the screen.

But what about the players remaining in the game? They need to know that this player has left, so they’re not sitting around waiting for him or her to finish. Well, if you’ve been carefully studying your console log (which is what I like to do for fun on a Saturday night), you likely saw a line like this when your opponent left the game:

Player p_CL3Ay7mbjLn16QEQAQ has left.

This means this event is being captured in your OnPeersDisconnected() listener; therefore you just need to pass that information back to the game.

Note: At the time of this writing, there is an “undocumented feature” (okay, fine, a bug) in the library where the room is considered destroyed if your opponent leaves the room and you’re the only one left. You’ll also receive an OnLeftRoom callback instead of an OnPeersDisconnected call. Hopefully that “feature” has been dealt with by the time this is published! :]

First, add the following line to your MPUpdateListener interface in MPInterfaces.cs file:

void PlayerLeftRoom(string participantId);

MultiplayerController will call this method when it receives a notice that somebody left the room.

Next, modify OnPeersDisconnected in MultiplayerController.cs, as follows:

public void OnPeersDisconnected (string[] participantIds)
{
    foreach (string participantID in participantIds) {
        ShowMPStatus("Player " + participantID + " has left.");
        if (updateListener != null) {
            updateListener.PlayerLeftRoom(participantID);
        }
    }
}

This loops through each player and calls your new interface method on your listener. Open GameController.cs and add that method now:

public void PlayerLeftRoom(string participantId) {
    if (_finishTimes[participantId] < 0) {
        _finishTimes[participantId] = 999999.0f;
        CheckForMPGameOver();
    }
}

When a player leaves a room, you record their finish time as 999999.0; this ensures CheckForMPGameOver counts this player as “finished” since the finish time is positive. Using such a large value means a player can’t cheat their way to first place by quitting a game early.

The call to CheckForMPGameOver() is necessary since the disconnected player won’t ever send a “game over” message; if they were the only player remaining in the game you’d want the game to end at this point.

Note: Depending on your game design, you might want to end the game early if there is only one player remaining. In Circuit Racer, it’s still fun to drive around a track by yourself, so you let the local player finish off the race. On the other hand, a first person shooter would be much less fun if there were nobody left to shoot, so leaving the game early would be a better choice.

Build and run your game; start a game and end one client early by tapping the Quit button. The other player will be able to finish their game and start a new one instead of waiting around forever.

It looks a little odd to see your disconnected opponent’s dead car sitting in the middle of the track, but it’s easy enough to add some code to remove it from the game.

Open OpponentController.cs and add the following method:

public void HideCar() {
    gameObject.renderer.enabled = false;
}

In PlayerLeftRoom, add the following code just before CheckForMPGameOver():

if (_opponentScripts[participantId] != null) {
    _opponentScripts[participantId].HideCar();
}

Build and run your game; start a two-player game and quit a game prematurely. The opponent’s car will eventually vanish from the screen, so you know your opponent is truly gone and not just being difficult by sulking in the middle of the road. :]

So are we done now?

Close — but not yet. You’ve handled the situation where a player intentionally leaves the room and the Google Play game services library calls LeaveRoom on behalf of that player. Sometimes, though, the service doesn’t have the chance to make a clean exit — and that’s the next scenario you’ll need to handle.

Leaving the Game (Abnormally)

If your player’s battery dies, their game crashes, lose their cell service in the subway, or drop their phone in the toilet (it’s been known to happen), your game won’t have an opportunity to leave the room properly.

Try this yourself — no, don’t go and drop your phone in the toilet. :] You can turn on airplane mode on one of your devices while in the middle of the game, which will kill your network connectivity, not give the Google Play games services library a chance to call LeaveRoom(), and leave the other player waiting forever for the other player to either send a “game over” message or leave the room — neither of which will happen.

The most common way to detect these scenarios is through timeouts. You’re receiving approximately six updates per second for your opponent; if those calls stopped for long enough, you could probably assume something terrible has happened to the other player. Or that their game has crashed. One of the two.

Our time-out strategy

Your strategy for detecting timeouts will be pretty straightforward. Each CarOpponent will keep track of the last time they received a game update. You’ll check at intervals to see if this update is longer than your timeout threshold. If it is, treat the opponent as you would if they left the game voluntarily.

Aha! OpponentCarController is already storing the last time it received an update from the network in _lastUpdateTime. You just need a way to access it.

Add the following line to OpponentCar, right beneath the spot where you declare _lastUpdateTime:

public float lastUpdateTime { get { return _lastUpdateTime; } }

That code might look a little odd to you, but it’s not all that different from creating a readonly property in Objective-C. You simply create a lastUpdateTime property with only a getter (hence making it read-only), and define the getter to return the value of the private variable _lastUpdateTime.

This lets you retrieve the value of _lastUpdateTime from GameController, while only OpponentCar can set the value.

You need to make sure this threshold is realistic; you don’t want players timing out right away just because _lastUpdateTime was initialized to 0!

Add the following code to Start():

_lastUpdateTime = Time.time;

This sets the time when an opponent is created.

Now you can add the logic to check for timeouts. Add the following variables to GameController:

public float timeOutThreshold = 5.0f;
private float _timeOutCheckInterval = 1.0f;
private float _nextTimeoutCheck = 0.0f;

The timeOutThreshold is the how many seconds before the player is considered gone. _timeOutCheckInterval is how often the system should check for a timeout. _nextTimeoutCheck holds the time plus the interval so it can be easily checked (versus calculating the total every iteration).

Next, add the following method to GameController:

void CheckForTimeOuts() {
    foreach (string participantId in _opponentScripts.Keys) {
        // We can skip anybody who's finished.
        if (_finishTimes[participantId] < 0) {
            if (_opponentScripts[participantId].lastUpdateTime < Time.time - timeOutThreshold) { 
                // Haven't heard from them in a while!
                Debug.Log("Haven't heard from " + participantId + " in " + timeOutThreshold + 
                          " seconds! They're outta here!");
                PlayerLeftRoom(participantId);
            }
        }
    }
}

First, you iterate through all opponent participantIDs by looking at the keys of the _opponentScripts dictionary. You then check the last time you heard from each player that hasn’t finished; if you haven’t heard from them in more than 5.0 seconds treat them as if they had left the game.

Note: A five-second value is great for testing, but in real life you’d want to use a value around 10 or 15 seconds, particularly if you’re planning on releasing your game internationally to markets where flaky 2G networks are still common.

Finally, you need to call this method from within DoMultiplayerUpdate(). It’s probably overkill to call it in every frame, so you’ll call it once per second instead.

Add the following code to the beginning of DoMultiplayerUpdate().

if (Time.time > _nextTimeoutCheck) {
    CheckForTimeOuts();
    _nextTimeoutCheck = Time.time + _timeOutCheckInterval;
}

Build and run your game one last time — and ensure you’ve turned off airplane mode to save pulling of hair and much gnashing of teeth. :] Start a game, then enable airplane mode on one of your devices. After a five-second pause, you should see a note in the console log that this player is gone, and their car should disappear from the track.

Are we done now?!?

Well, you’re done with all the ways a player can exit the game, so you’re done for this part of the tutorial at least! :]

Where to Go from Here?

So far, you’ve made some major improvements to your game: you’ve reduced your game updates to a reasonable level; retained the smooth animation thanks to the magic of interpolation and extrapolation; and dealt with the normal and abnormal ways players can leave a game.

Here’s the completed project for this part of the tutorial.

There are still a number of improvements left to do before you can call your game finished, though:

  • Handling messages arriving out-of-order
  • Running your cross-platform game on Android
  • Running your game with more than two players
  • Discovering a BIG DARK SECRET about multiplayer tutorials :]

So stay tuned! You’ll cover these issues (and more!) in the Part 4 of this multiplayer tutorial. As always, if you have comments or questions, feel free to join the discussion below!

Creating a Cross-Platform Multi-Player Game in Unity — Part 3 is a post from: Ray Wenderlich

The post Creating a Cross-Platform Multi-Player Game in Unity — Part 3 appeared first on Ray Wenderlich.

Creating a Cross-Platform Multiplayer Game in Unity — Part 4

$
0
0

Prepare for the final lap where you will learn how to handle drivers sending mixed signals!

Prepare for the final lap where you will learn how to handle drivers sending mixed signals!

Welcome back to the final part of this series on implementing multiplayer in your games. The previous three sections of this tutorial series walked you through creating a functional multiplayer game, including matching with random players across the internet, dealing with infrequent updates, properly ending the game, and handling players who leave mid-game.

You’re not quite done; to put some polish on your app you’ll have to get your cross-platform app running properly on Android and let more than two people play at once among other things! However, they aren’t huge issues to fix — and you’ll take care of all of them in this part of the tutorial.

The most pressing problem to fix, however, is dealing with your unreliable messaging protocol and handling the inevitable out-of order messages.

In this tutorial, you will be learning the following:

  • How to respond to messages that are received out of order
  • How to cancel auto matchmaking
  • A whole lot of game theory regarding network connectivity

You can download the starter project over here.

If you are starting this project with the starter project, you will need to configure based on the first part of the tutorial series.

Handling Out of Order Messages

Since you’re likely testing with your devices on a fast internal network, there’s a good chance most of your messages arrive in order; throttling back on your updates as you did in the previous chapter also means that statistically, you’ll experience fewer out-of-order messages.

However, real users play games while on public transportation that meanders in and out of good cell coverage, and some players just have cruddy mobile connections all the time. You can easily imagine what might happen if you receive an update message out of order. Think about the following car traveling around the track with messages #1 through #6 received in order:

Cars in order

But what if message #3 wasn’t received until sometime after message #5? You’d end up with a zig-zagging car as the following image illustrates:

Zig Zagging Car!

The car will appear to teleport all over the place — what a mess! Here’s a very simple strategy you can use to manage this issue:

  • Anytime you send out an update message, start the message with a number that you automatically increment.
  • Anytime you receive an update message from another player, look at the auto-increment number; if it’s smaller than the number from the previous message you received, then it’s an out-of-order message, and you simply discard it.

In the example above, you would simply discard car location #3, since you received it after message #5. Sure, you’d be missing a location update, but the car would continue on with the same velocity from message #2 and recover when it received location #4:

Skipping in out of order message

Re-open the Circuit Racer project, then open MultiplayerController.cs in the scripts folder. Add the following variable to track your own auto-increment message number:

private int _myMessageNum;

Next, update _updateMessageLength, so that you have space for this new 4-byte integer.

// Byte + Byte + 1 int for message num + 2 floats for position + 2 floats for velcocity + 1 float for rotZ
private int _updateMessageLength = 26;

Add the following code to the OnRoomConnected callback, right before your Application.LoadLevel call:

_myMessageNum = 0;

This simply initializes _myMessageNum to a starting value.

Now, modify SendMyUpdate as follows to include the new message number:

public void SendMyUpdate(float posX, float posY, Vector2 velocity, float rotZ) {
    _updateMessage.Clear ();
    _updateMessage.Add (_protocolVersion);
    _updateMessage.Add ((byte)'U');
    _updateMessage.AddRange(System.BitConverter.GetBytes(++_myMessageNum)); // THIS IS THE NEW LINE
    ...

You won’t need to add any kind of message number to SendFinishMessage; that’s because this message is sent reliably and is guaranteed to be received in order. Also, you’re only sending one of these messages, which means sending it out of order is mathematically impossible. :]

Now that you’re sending this message number, you need to handle it on the receiving end.

if (messageType == 'U' && data.Length == _updateMessageLength) { 
        int messageNum = System.BitConverter.ToInt32(data, 2);
	float posX = System.BitConverter.ToSingle(data, 6);
	float posY = System.BitConverter.ToSingle(data, 10);
	float velX = System.BitConverter.ToSingle(data, 14);
	float velY = System.BitConverter.ToSingle(data, 18);
	float rotZ = System.BitConverter.ToSingle(data, 22);
 
    if (updateListener != null) {
        updateListener.UpdateReceived(senderId, messageNum, posX, posY, velX, velY, rotZ);
    }
} else if ...

This change means you’ll have to adjust your method’s signature in MPUpdateListener.

Open your MPUpdateListener interface in MPInterfaces.cs and change UpdateReceived as follows:

void UpdateReceived(string participantId, int messageNum, float posX, float posY, float velX, float velY, float rotZ);

Next, open your GameController class and change UpdateReceived as follows:

public void UpdateReceived(string senderId, int messageNum, float posX, float posY, float velX, float velY, float rotZ) {
    if (_multiplayerReady) {
        OpponentCarController opponent = _opponentScripts[senderId];
        if (opponent != null) {
            opponent.SetCarInformation(messageNum, posX, posY, velX, velY, rotZ);
        }
    }
}

You’re now passing messageNum to SetCarInformation() of OpponentCarController.

Open OpponentCarController.cs, and add the following variable:

private int _lastMessageNum;

This variable contains that last message number that it received. That way, you can determine if the next message is an older or newer one.

Now initialize that variable in Start():

_lastMessageNum = 0;

Now change as follows:

public void SetCarInformation(int messageNum, float posX, float posY, float velX, float velY, float rotZ) {
    if (messageNum <= _lastMessageNum) {
        // Discard any out of order messages
        return;
    }
    _lastMessageNum = messageNum;
 
    // The rest of the method is the same as before.
}

Whenever you receive a message number from an opponent, you compare it to the lastMessageNum you have stored for that player. If it’s a smaller number than what you’ve received in the past, you know this message has been received out of order and you toss it.

That was a wide-ranging change; build and run your game to make sure everything is still working. But just for kicks, update your game on only one device and see what happens…

Your clients are no longer talking to each other! In fact, pretty soon your players will time out on each other’s devices because no messages are getting through. Can you guess why?

Solution Inside: Why are our clients giving each other the silent treatment? SelectShow>

In a way, you got lucky in this scenario. Ignoring each other’s messages is probably the best outcome. What would have happened if you didn’t bother checking the message length, or if you removed a few bytes elsewhere in the message so that it ended up the same length as before?

You could have created a situation where one client thinks it’s sending an integer representing the message number, but the other client is interpreting those 4 bytes as a float representing the car’s x position.

It’s not a theoretical problem — this could totally happen in real life. When you release a new version of your game, not all players will update right away. Your client has no way of knowing it’s using an older message protocol…unless you had a way of sending a version number across with every message.

Wait — you do have a way to do that. Remember that _protocolVersion variable you set way back in the last lesson? The one Unity keeps complaining that we’re not using? It’s time to put it to work.

We could not secure the rights to Dr. Zoidberg, but that isn't going to stop you from reading this in his voice.

We could not secure the rights to Dr. Zoidberg, but that isn’t going to stop you from reading this in his voice.

First, you should change your version number, since your message format has changed.

Change the value of _protocolVersion near the top of MultiplayerController as follows:

private byte _protocolVersion = 2;

Next, you will modify OnRealTimeMessageReceived so you check the message version before you do anything else.

Add the following code to the begining of OnRealTimeMessageReceived, right underneath the line byte messageVersion = (byte)data[0];

if (messageVersion < _protocolVersion) {
    // Our opponent seems to be out of date.
    Debug.Log("Our opponent is using an older client.");
    return;
} else if (messageVersion > _protocolVersion) {
    // Our opponents seem to be using a newer client version than our own! 
    // In a real game, we might want to prompt the user to update their game.
    Debug.Log("Our opponent has a newer client!");
    return;
}

Build your game and update it on the one device you updated earlier; launch a multiplayer game and you’ll see that your two clients still won’t talk to each other — but at least now you know why! :]

Your newer client recognizes that its protocol version is out of sync with the other clients and it’s rejecting the message for that reason instead of having to rely on just the message length. You’re only displaying this in your console log for now, but in a real game you could display this message to the player and give them a better understanding of why their two games aren’t talking to each other.

This is a rather simple strategy for dealing with out of sync clients. If you wanted to be more flexible, you could add some degree of backwards compatibility into your clients. For instance, you could still accept messages based on protocol version 1; you simply wouldn’t check for an auto-incrementing message number.

With backwards compatibility built into your game, your clients could declare at the start of a match what protocol version they’re using and how backwards compatible they are.

Agreeing on Protocol, part 1

If there’s a “lowest common protocol” version that all of your clients support, they could agree to use that one, and your different-versioned games could still play with one another!

Agreeing on Protocol, Part 2

Supporting multiple protocols can be a lot of work; sometimes the benefits of a breaking change outweigh the need for backward compatibility. It’s up to you and your game’s design to decide if this approach makes sense to you.

Another alternative is to set the game’s auto-match variant equal to your protocol version, which would ensure that only players of the same client version could be auto-matched to play with each other. This would certainly minimize this problem — but it would also decrease the number of possible competitors in each pool. No one ever said this multiplayer thing was easy! :]

Load the newest version of your game onto both devices; everything should be working just about the same as before, although the game will now smartly discard any message received out of order — and you’re also prepared for a world where people might have different client versions.

Cancelling Auto-Matching

What if your player gets tired of waiting for people to play with and wants to play a single-player game instead? To solve that, you will add a cancel button to the auto-match dialog.

Leaving a room you’re waiting in is as simple as calling Google Play Games platform’s LeaveRoom(), which you’ve already implemented as LeaveGame() in MultiplayerController.

Open MainMenuScript.cs; find the following code in OnGUI():

if (_showLobbyDialog) {
	GUI.skin = guiSkin;
	GUI.Box(new Rect(Screen.width * 0.25f, Screen.height * 0.4f, Screen.width * 0.5f, Screen.height * 0.5f), _lobbyMessage);
}

…and replace it with the following:

if (_showLobbyDialog) {
	GUI.skin = guiSkin;
	GUI.Box(new Rect(Screen.width * 0.25f, Screen.height * 0.4f, Screen.width * 0.5f, Screen.height * 0.5f), _lobbyMessage);
	if (GUI.Button(new Rect(Screen.width * 0.6f, 
                                Screen.height * 0.76f, 
                                Screen.width * 0.1f, 
                                Screen.height * 0.07f), "Cancel")) {
		MultiplayerController.Instance.LeaveGame();
		HideLobby();
	}
}

You call HideLobby() right away instead of calling it from a listener method in your MultiplayerController. In many cases, when you leave a room in the middle of setting it up you won’t get a callback in the form of OnLeftRoom(). It’s more likely to surface as an OnRoomConnected(false) error.

Build and run your game; start a multiplayer game, then cancel it and watch that dialog disappear!

Note: Don’t be tempted to start and cancel games in rapid succession, or you may find yourself in a weird state where you can’t join any games for about ten minutes or so. Google engineers tell me this is because quickly joining and canceling a room too many times could get you into a weird state where the service thinks your client is broken and starts to rate-limit you.

Personally, I suspect it’s because the service is a little ticked off that you were messing with it and it’s giving you the silent treatment in an insolent kind of way. It’s sensitive like that. :]

Adding More Players

Not everybody has three devices they can develop with, but if you do, then you can easily update your game to permit more than two players!

To start, replace StartMatchMaking in MultiplayerController with the following:

private void StartMatchMaking() {
    PlayGamesPlatform.Instance.RealTime.CreateQuickGame(2, 2, 0, this);
}

The next step is…oh, wait, that’s all you have to do! :] Now you understand why you used those dictionary lists to track opponents.

Build and run your project on three different devices and play to your heart’s content! In fact, there’s nothing to stop you from making this a four-player game…except for the fact that I ran out of car artwork. :]

3 Player Awesomeness

The first two arguments to CreateQuickGame means the game will accept a minimum of two and a maximum of two other opponents. If you changed that call to CreateQuickGame(1, 2, 0, this), your game would try to find a three-player game, but it would start a two-player game if it didn’t find enough opponents in time.

If you’re having a problem getting all three devices into a game, check the two following things — which both happened to me as I wrote this tutorial:

  1. Make sure you’re not signed in with the same account on two (or all) devices.
  2. Make sure you’re signed in with an account that has been added as a tester.
At this point, the actual tutorial part of the ends and the following sections is a fascinating look at various aspects of game networking

Synchronizing Game Start

Depending on your devices and how quickly they receive all the setup information from Play Game services, you may have noticed that not all of your games start at exactly the same time.

This doesn’t end up affecting the fairness of the game, because each individual client reports back the total time it took them to go around the track, regardless of when they actually started the race. But it does mean that it might look like you finished your game earlier than your opponent — only to find out your opponent had a better race time than you.

It’s a bit tricky to get your players to start at the same time, but one potential solution could look like this:

  1. Choose your “host”; the easiest way is to pick the player listed first in your GetAllPlayers() call, which all clients should agree upon since this call returns a sorted list of participant IDs.
  2. Have your host send out ping messages to all the other players. These messages would likely include the local time in milliseconds and, perhaps, a numeric ID.
  3. Have all your other players reply back to these pings. These replies would include the original send time.
  4. When your host receives these replies, it can compare its current time to when it sent the original message, divide by 2 to get the one-way trip time, and it will know approximately how long it takes for a message to make the round-trip from the host to the other player.
  5. Repeat this until your player receives about seven or eight replies from all other players to calculate a decent average ping time.
  6. Once that’s done, you can create a synchronized start by looking at the longest ping time, adding a little buffer, and then sending out a message to each player telling them to start.

For example, suppose you’re the host and you find it takes 200 ms to send a message to Player 1 and 25 ms to Player 2. You could send a message to Player 1 saying “start the game 50 ms from now”, a message to player 2 saying “start the game 225 ms from now” and a message to yourself saying, “Start the game 250 ms from now”. That would make all three players start at approximately the same time.

Here’s a diagram showing this process:

Synchronized Start

One complication is that this kind of timing test only works if you’re using unreliable messaging; the timing of reliable messaging varies too much to be of use. So your clients need to send a “Yeah, I got the game start message; stop bothering me about it” reply to your host, and your host needs to keep telling the other players to start (adjusting the start time offset along the way) until the host has confirmed that everybody received the start message.

That’s a lot of work just to ensure all players start at the same time. However, if there’s enough interest in seeing a fully coded solution, let us know and perhaps we can add it as a follow-up tutorial.

Synchronizing the State of the Game World

(or, the Big Dark Secret About Multiplayer Tutorials)

You’ve probably noticed that those crates on the track don’t match up on each device — which means you can see your opponents drive straight through a crate:

The car drove right through the crate! It must be a g-g-g-g-ghost car!

The car drove right through the crate! It must be a g-g-g-g-ghost car!

You might also notice that an opponent car seems to get stuck on nothing at all — perhaps a particularly dense patch of air? :] And come to think of it, why aren’t the cars colliding with each other? The more you think about it, the weirder this game gets…

Congratulations — you’ve hit upon the big deep dark dirty secret of multiplayer tutorials. Have you noticed that nearly all multiplayer tutorials you see involve racing games? And not just that, but racing games where nothing you do really affects the other player?

That’s because trying to write a game where all clients agree on the shared state of the world is, to use a technical term, REALLY HARD. Imagine you’re playing a game where both your car and your opponent’s car are heading towards the same crate.

Due to network latency, you see your car run into the crate a few milliseconds before your opponent, but your opponent sees that she runs into the crate first. Just a few milliseconds difference means the two player worlds end up in drastically different states:

One minor difference, two drastically different game worlds

One minor difference, two drastically different game worlds

Generally, games like this fake it by making all players participate in what ends up being a collection of single player games, and then simply report the end result of those single player games to each other. Puzzle fighter games, in particular, work this way. Of course, any real-time games that rely on slow turns — like the theoretical poker example — don’t need to deal with this problem at all.

But you’ve surely played fast-paced, online multiplayer games where players do interact with each other in a shared world. How do they do it, and why aren’t we covering this here?

There are a lot of different strategies to solve this REALLY HARD problem, but here are two of the most common:

Strategy 1: Using Discrete Turns

In real-time strategy games, the game itself is defined as a series of very fast, discrete turns. You assume that when a player makes a move, it won’t get carried out until a few turns later. For instance, if you decide to move your tank on turn 1056, it won’t start moving until turn 1059, which should be enough time for your “Hey, Todd wants his tank to start moving on turn 1059″ message to make it out to all of the other opponents.

On your device, some user interface sleight-of-hand along with some audio feedback (“Rolling out, commander!”) would hide the fact that your tank doesn’t move right away. This way, each client is able to perfectly recreate the state of the world locally by having everybody perform the exact same actions on the exact same turns, as illustrated below:

RTS Strategy

Strategy 2: Using a Server to Sync

In first-person shooter games, a separate server determines the state of the world. Sometimes — particularly in mobile games — this “server” is just a player’s device. When a player decides to take an action, such as jumping, your client sends a “Start jumping” message to the server. The server then responds back with a message of what actually happened when you started jumping, and your game then updates to reflect the reality as reported by the server.

This solves your multiplayer divergent worlds problem, because the server is the one true source of what’s going on in the world. But it would be a terribly laggy experience to wait for a response from the server before you can start jumping. Many games solve this by taking a really good guess as to what they think should happen when you start jumping, but then alter your game state as required when they gets the definitive result from the server.

Here’s a quick diagram showing this strategy in action:

Client / "Server"  strategy

Keeping the world in sync in a multiplayer game is a fascinating topic, and there’s a lot more detail than can be included here. If you want to delve deeper into this topic, I highly recommend reading 1500 Archers on a 28.8 Network by the developers of Age of Empires, and What Every Programmer Needs to Know About Game Networking by Glenn Fiedler.

These are really interesting problems to solve, but as you can see, they’re also quite complicated with lots of thorny issues. And just like the “synchronized start” problem, they are best saved for a future tutorial.

Where to Go From Here?

Congratulations! You now have an awesome multiplayer game that, in spite of its simplicity, is a lot of fun to play against one or more opponents. There are still a few outstanding issues to solve in your game, such as the synchronous world issue described above, but if there’s enough interest, we could look into discussing some of these in a future series.

In the meantime, now that you’ve got the basics down for a multiplayer game, try making your own! Maybe there are some gameplay changes you can add to Circuit Racer to make it more fun. How about giving your player a power-up that messes with their opponent if you can make it through an entire lap without hitting a crate?

You can also take what you’ve learned here and apply it to your own game. Even a simple game like a single-player puzzle game can have a fun multiplayer component if you turn it into a simple contest of “who can reach the target score first?” If you do make something using what you’ve learned here, let us know — we’d love to hear about it in the discussion below!

Creating a Cross-Platform Multiplayer Game in Unity — Part 4 is a post from: Ray Wenderlich

The post Creating a Cross-Platform Multiplayer Game in Unity — Part 4 appeared first on Ray Wenderlich.

Scene Kit with David Rönnqvist – Podcast S03 E02

$
0
0
Learn about Scene Kit with David Rönnqvist!

Learn about Scene Kit with David Rönnqvist!

Welcome back to season 3 of the raywenderlich.com podcast!

In this episode, we talk with David Rönnqvist to take a deep dive into Scene Kit, Apple’s high-level general-purpose 3D framework.

[Subscribe in iTunes] [RSS Feed]

Our Sponsor

  • PaintCode is a unique drawing app that automatically turns your drawings into Objective-C or Swift code. With this, you can make your apps truly resolution-independent!

Interested in sponsoring a podcast episode? We sell ads via Syndicate Ads, check it out!

Links and References

Contact Us

Where To Go From Here?

We hope you enjoyed this episode of our podcast. Stay tuned for a new episode next week! :]

Be sure to subscribe in iTunes to get access as soon as it comes out!

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!

Scene Kit with David Rönnqvist – Podcast S03 E02 is a post from: Ray Wenderlich

The post Scene Kit with David Rönnqvist – Podcast S03 E02 appeared first on Ray Wenderlich.

Video Tutorial: Collection Views Part 4: Inserting Cells

CALayer in iOS with Swift: 10 Examples

$
0
0

LayersScreenshot

As you probably know, everything you see in an iOS app is a view. There’s button views, table views, slider views, and even parent views that contain other views.

But what you might not know is that each view in iOS is backed by another class called a layer – a CALayer to be specific.

In this article, you’ll learn what a CALayer is, and how it works. You’ll also see 10 examples of using CALayers for cool effects, like shapes, gradients, and even particle systems.

This article assumes you’re familiar with the basics of iOS app development and Swift, including constructing your UI with storyboards.

Note: If you’re not quite there, no worries. You’ll be happy to know we have quite a few tutorials and books on the subject, such as Learn to Code iOS Apps with Swift and The iOS Apprentice.

Getting Started

The easiest way to understand what layers are is to see them in action. So let’s start by creating a simple project from scratch to play around with layers.

Ready to write some code? Good! Fire up Xcode and:

  1. Choose File\New\Project… from the menu.
  2. Select iOS\Application\Single View Application from the dialog.
  3. Click Next, enter CALayerPlayground for the Product Name and enter an organization name and identifier of your choosing.
  4. Select Swift for Language and Universal for Devices.
  5. Uncheck Core Data is unchecked, then click Next.
  6. Find a nice home for your project (I keep projects in a folder within my user directory called Source), and click Create.
  7. Okay, now you have the files set up and the next order of business is to create a view:

  8. In the Project navigator, select Main.storyboard.
  9. Select View\Assistant Editor\Show Assistant Editor from the menu, and View\Utilities\Show Object Library if it’s not already displayed.
  10. Also, select Editor\Canvas\Show Bounds Rectangles, so that you can see the bounds outline of the view you’re about to add to your scene.
  11. From the Object library, drag a View onto your View Controller Scene. With it selected, go to the Size inspector (View\Utilities\Show Size Inspector) and set x and y to 150 and width and height to 300.
  12. With that view still selected, click the Align button in the auto layout toolbar (bottom-right of the storyboard) and check Horizontal Center in Container and Vertical Center in Container, leave both their values set to 0, and click Add 2 Constraints.
  13. Click the Pin button, check Width and Height, make sure both values are set to 300, and click Add 2 Constraints.

Finally, control-drag from the view you just created to the scene’s ViewController.swift file, right above the viewDidLoad() method. In the popup that appears, give your outlet the name viewForLayer. Your Xcode should now look similar to this:

Xcode-CreateOutlet

Click Connect to create the outlet.

Replace the contents of ViewController.swift with the following:

import UIKit
 
class ViewController: UIViewController {
 
  @IBOutlet weak var viewForLayer: UIView!
 
  var l: CALayer {
    return viewForLayer.layer
  }
 
  override func viewDidLoad() {
    super.viewDidLoad()
    setUpLayer()
  }
 
  func setUpLayer() {
    l.backgroundColor = UIColor.blueColor().CGColor
    l.borderWidth = 100.0
    l.borderColor = UIColor.redColor().CGColor
    l.shadowOpacity = 0.7
    l.shadowRadius = 10.0
  }
 
}

As mentioned earlier, every view in iOS has a layer associated with it, and you can retrieve that layer with yourView.layer. The first thing this code does is create a computed property called “l” (that’s a lower case L) to access the viewForLayer‘s layer, which saves some keystrokes as you write the subsequent code.

The code also calls setUpLayer to set a few properties on the layer – a shadow, a blue background color, and a huge red border. You’ll learn more about setUpLayer() in a moment, but first, build and run to the iOS Simulator (I chose iPhone 6) and check out your customized layer:

CALayerPlayground-1

Pretty cool effect with just a few lines of code, eh? And again – since every view is backed by a layer, you can do this kind of thing for any view in your app. Let’s take a closer look.

Basic CALayer Properties

CALayer has several properties that let you customize its appearance. Think back to what you’ve already done:

  • Changed the layer’s background color from its default of no background color to blue
  • Gave it a border by changing border width from the default 0 to 100
  • Changed its color from the default black to red.
  • Lastly, you gave it a shadow by changing its shadow opacity from default zero (transparent) to 0.7; this alone would cause a shadow to display, and you took it a step further by increasing its shadow radius from its default value of 3 to 10.

These are just a few of the properties you can set on CALayer. Let’s try two more. Add these lines to the bottom of setUpLayer():

l.contents = UIImage(named: "star")?.CGImage
l.contentsGravity = kCAGravityCenter

The contents property on a CALayer allows you to set the layer’s content to an image, so you set it to an image named “star” here. For this to work, you’ll need to add that image to your project, so download this star image I made and add it to your project.

Build and run and take a moment to appreciate this stunning piece of art:

CALayerPlayground-2

Notice how the star is centered – this is because you set the contentsGravity property to kCAGravityCenter. As you might expect, you can also change the gravity to top, top-right, right, bottom-right, bottom, bottom-left, left and top-left.

Changing the Layer’s Appearance

Just for fun, let’s add some gesture recognizers to manipulate the appearance of this layer. In Xcode, drag a tap gesture recognizer onto the viewForLayer object. For reference, here’s what adding the tap gesture recognizer should look like:

Xcode-AddTapGestureRecognizer

Note: If you’re not familiar with gesture recognizers, check out Using UIGestureRecognizer with Swift.

Repeat this to add a pinch gesture recognizer onto the viewForLayer as well.

Then control-drag from each gesture recognizer item on your storyboard scene dock into ViewController.swift, one after the other, and place them between setUpLayer() and the closing curly brace for the class itself.

In the popup, change the connection to Action and name the tap recognizer action tapGestureRecognized and the pinch recognizer pinchGestureRecognized. For example:

Xcode-CreateAction

Change tapGestureRecognized(_:) to look like this:

@IBAction func tapGestureRecognized(sender: UITapGestureRecognizer) {
  l.shadowOpacity = l.shadowOpacity == 0.7 ? 0.0 : 0.7
}

This tells the viewForLayer layer to toggle its layer’s shadow opacity between 0.7 and 0 when the view recognizes a tap.

The view, you say? Well, yes. You could override CALayer’s hitTest(_:) to do the same thing, and actually you’ll see that approach later in this article. But here’s the logic behind the above approach: hit testing is all a layer can do because it cannot react to recognized gestures. That’s why you set up the tap gesture recognizer on the view.

Now change pinchGestureRecognized(_:) to look like this:

@IBAction func pinchGestureRecognized(sender: UIPinchGestureRecognizer) {
  let offset: CGFloat = sender.scale < 1 ? 5.0 : -5.0
  let oldFrame = l.frame
  let oldOrigin = oldFrame.origin
  let newOrigin = CGPoint(x: oldOrigin.x + offset, y: oldOrigin.y + offset)
  let newSize = CGSize(width: oldFrame.width + (offset * -2.0), height: oldFrame.height + (offset * -2.0))
  let newFrame = CGRect(origin: newOrigin, size: newSize)
  if newFrame.width >= 100.0 && newFrame.width <= 300.0 {
    l.borderWidth -= offset
    l.cornerRadius += (offset / 2.0)
    l.frame = newFrame
  }
}

Here you’re creating a positive or negative offset based on the user’s pinch, and then adjusting the size of the layer’s frame, width of its border and the border’s corner radius.

A layer’s corner radius is 0 by default, meaning it’s a standard rectangle with 90-degree corners. Increasing the radius creates rounded corners. Want to turn a square layer into a circle? Set its corner radius to half of its width.

Note that adjusting the corner radius doesn’t clip the layer’s contents (the star image) unless the layer’s masksToBounds property is set to true.

Build and run, and try tapping on and pinching your view in and out:

CALayerPlayground-3

Hey, with a little more polish you could have yourself a pretty nifty avatar maker! :]

The Great CALayer Tour

CaLayerTour

CALayer has more than just a few properties and methods to tinker with, as well as several subclasses that have unique properties and methods.

What better way to get an overview of all this great API than by taking a guided tour, raywenderlich.com-style?

For the rest of this article, you will need the following:

This is a handy app that includes examples of 10 different types of CALayers, which you’ll learn about in this article. Here’s a preview of the 10 examples:

CALayerTourScenes

As we go through each example below, I recommend you play around with it in the CALayer app, and optionally look at the source code provided. You don’t need to actually code anything for the rest of this article, so just sit back, read and relax :]

These should be some great examples for you as you add different types of CALayers to your own projects. We hope you enjoy!

Example #1: CALayer

You’ve already seen an example of using CALayer, and setting a few of the properties.

There are a few things I didn’t mention about CALayers yet:

  • Layers can have sublayers. Just like views can have subviews, layers can have sublayers. You can use this for some cool effects!
  • Layer properties are animated. When you change the property of a layer, it is animated over time by default. You can also customize this animation behavior to your own timing.
  • Layers are lightweight. Layers are lighter-weight than views, and therefore they help you achieve better performance.
  • Layers have tons of useful properties. You’ve seen a few already, but let’s take a look at a few more!

As you’ve seen earlier, layers have tons of useful properties. Let’s take a tour of the full list of CALayer properties – some you haven’t seen yet, and are quite handy!

// 1
let layer = CALayer()
layer.frame = someView.bounds
 
// 2
layer.contents = UIImage(named: "star")?.CGImage
layer.contentsGravity = kCAGravityCenter
 
// 3
layer.magnificationFilter = kCAFilterLinear
layer.geometryFlipped = false
 
// 4
layer.backgroundColor = UIColor(red: 11/255.0, green: 86/255.0, blue: 14/255.0, alpha: 1.0).CGColor
layer.opacity = 1.0
layer.hidden = false
layer.masksToBounds = false
 
// 5
layer.cornerRadius = 100.0
layer.borderWidth = 12.0
layer.borderColor = UIColor.whiteColor().CGColor
 
// 6
layer.shadowOpacity = 0.75
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3.0
someView.layer.addSublayer(layer)

In the above code:

  1. Creates a CALayer instance and sets it to the bounds of someView.
  2. Sets an image as the layer’s contents and centers it within the layer. Notice that the underlying Quartz image data (CGImage) is assigned.
  3. Use this filter when enlarging the image via contentsGravity, which can be used to change both size (resize, resize aspect, and resize aspect fill) and position (center, top, top-right, right, etc.).
  4. The previous changes are not animated, and if geometryFlipped is not set to true, the positional geometry and shadow will be upside-down. Continuing on:

  5. You set the background color to Ray’s favorite shade of green :] and the made the layer opaque and visible. At the same time, you tell the layer not mask its contents, which means that if its size is smaller than its contents (the star image), the image will not be clipped.
  6. The layer’s corner radius is set to half the width of the layer to create visuals of a circle a border; notice that layer colors are assigned as the Quartz color references (CGColor).
  7. Creates a shadow and sets shouldRasterize to true (discussed below), and then adds the layer to the view hierarchy.

Here’s the result:

CALayer

CALayer has two additional properties that can improve performance: shouldRasterize and drawsAsynchronously.

shouldRasterize is false by default, and when set to true it can improve performance because a layer’s contents only need to be rendered once. It’s perfect for objects that are animated around the screen but don’t change in appearance.

drawsAsynchronously is sort of the opposite of shouldRasterize. It’s also false by default. Set it to true to improve performance when a layer’s contents must be repeatedly redrawn, such as when you work with an emitter layer that continuously renders animated particles. (See the CAEmitterLayer example later.)

A Word of Caution: Consider the implications before setting either shouldRasterize or drawsAsynchronously to true for a given layer. Compare the performance between true and false so you know if activating these features actually improves performance. When misused, performance is likely to take a nosedive.

Now shift your attention briefly to Layer Player. It includes controls to manipulate many of CALayer’s properties:

CALayer_5.5

Play around with the various controls – it’s a great way to get a feel of what you can do with CALayer!

Note: Layers are not part of the responder chain so they won’t directly react to touches or gestures like views can, as you saw in the CALayerPlayground example.

However, you can hit test them, as you’ll see in the example code for CATransformLayer. You can also add custom animations to layers, which you’ll see when we get to CAReplicatorLayer.

Example #2: CAScrollLayer

CAScrollLayer displays a portion of a scrollable layer. It’s fairly basic and cannot directly respond to user touches or even check the bounds of the scrollable layer, so it does cool things like preventing scrolling beyond the bounds ad infinitum! :]

UIScrollView doesn’t use a CAScrollLayer to do its work, instead it directly changes its layer’s bounds.

What you can do with a CAScrollLayer is set its scrolling mode to horizontal and/or vertical, and you can programmatically tell it to scroll to a specific point or rect:

// In ScrollingView.swift
import UIKit
 
class ScrollingView: UIView {
  // 1
  override class func layerClass() -> AnyClass {
    return CAScrollLayer.self
  }
}
 
// In CAScrollLayerViewController.swift
import UIKit
 
class CAScrollLayerViewController: UIViewController {
  @IBOutlet weak var scrollingView: ScrollingView!
 
  // 2
  var scrollingViewLayer: CAScrollLayer {
    return scrollingView.layer as CAScrollLayer
  }
 
  override func viewDidLoad() {
    super.viewDidLoad()
    // 3
    scrollingViewLayer.scrollMode = kCAScrollBoth
  }
 
  @IBAction func tapRecognized(sender: UITapGestureRecognizer) {
    // 4
    var newPoint = CGPoint(x: 250, y: 250)
    UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: {
      [unowned self] in
      self.scrollingViewLayer.scrollToPoint(newPoint)
      }, completion: nil)
  }
 
}

In the above code:

  1. A custom UIView subclass is used to override layerClass() to return CAScrollLayer; this is an alternative to creating a new layer and adding it as a sublayer, such as what was done in the CALayer example.
  2. A computed property is used to streamline working with the scrolling view layer of the custom UIView.
  3. Scrolling is set to both horizontal and vertical.
  4. When a tap is recognized, a new point is created and the scrolling layer scrolls to that point inside a UIView animation. Note: scrollToPoint(_:) and scrollToRect(_:) do not animate automatically.

Case study: An instance of ScrollingView that houses an image view with an image that’s larger than the scrolling view’s bounds. When you run the above code and tap on the view, this would be the result:

CAScrollLayer

Layer Player includes multiple controls to lock scrolling horizontally and vertically.

Here are some rules of thumb for when to use (or not to use) CAScrollLayer:

  • If you want something lightweight and only need to programmatically scroll: Consider using CAScrollLayer.
  • If you want the user to be able to scroll: You’re probably better off with UIScrollView. To learn more, check out our 18-part video tutorial series on this.
  • If you are scrolling a very large image: Consider using CATiledLayer (more below).

Example #3: CATextLayer

CATextLayer provides simple but fast rendering of plain text or attributed strings. Unlike UILabel, a CATextLayer cannot have an assigned UIFont, only a CTFontRef or CGFontRef.

With a block of code like this, it’s possible to manipulate font, font size, color, alignment, wrapping and truncation, as well as animate changes:

// 1
let textLayer = CATextLayer()
textLayer.frame = someView.bounds
 
// 2
var string = ""
for _ in 1...20 {
  string += "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce auctor arcu quis velit congue dictum. "
}
 
textLayer.string = string
 
// 3
let fontName: CFStringRef = "Noteworthy-Light"
textLayer.font = CTFontCreateWithName(fontName, fontSize, nil)
 
// 4
textLayer.foregroundColor = UIColor.darkGrayColor().CGColor
textLayer.wrapped = true
textLayer.alignmentMode = kCAAlignmentLeft
textLayer.contentsScale = UIScreen.mainScreen().scale
someView.layer.addSublayer(textLayer)

Explanation of the above code:

  1. Creates a CATextLayer instance and sets its to someView‘s bounds.
  2. Creates a string of repeated text and assigns it to the text layer.
  3. Creates a font and assigns it to the text layer.
  4. Sets the text layer to wrap and left-align, (You have the option of setting it to natural, right, center and justified.), matches its contentsScale to the screen, and then adds the layer to the view hierarchy.

All layer classes, not just CATextLayer, render at a scale factor of 1 by default. When attached to views, layers automatically have their contentsScale set to the appropriate scale factor for the current screen. You need to set the contentsScale explicitly for layers you create manually, or else their scale factor will be 1 and you’ll have pixilation on retina displays.

If added to a square-shaped someView, the created text layer would look like this:

CATextLayer

Truncation is a setting you can play with, and it’s nice when you’d like to represent clipped text with an ellipsis. Truncation defaults to none and can be set to start, end and middle:

CATextLayer-MiddleTruncation.png

CATextLayer-StartTruncation.png

CATextLayer-EndTruncation

Layer Player has controls to change many of CATextLayer’s properties:

CATextLayer_5.5

Example #4: AVPlayerLayer

AVPlayerLayer adds a sweet layer goodness to AVFoundation. It holds an AVPlayer to play AV media files (AVPlayerItems). Here’s an example of creating an AVPlayerLayer:

override func viewDidLoad() {
  super.viewDidLoad()
  // 1
  let playerLayer = AVPlayerLayer()
  playerLayer.frame = someView.bounds
 
  // 2
  let url = NSBundle.mainBundle().URLForResource("someVideo", withExtension: "m4v")
  let player = AVPlayer(URL: url)
 
  // 3
  player.actionAtItemEnd = .None
  playerLayer.player = player
  someView.layer.addSublayer(playerLayer)
 
  // 4
  NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidReachEndNotificationHandler:", name: "AVPlayerItemDidPlayToEndTimeNotification", object: player.currentItem)
}
 
deinit {
  NSNotificationCenter.defaultCenter().removeObserver(self)
}
 
// 5
@IBAction func playButtonTapped(sender: UIButton) {
  if playButton.titleLabel?.text == "Play" {
    player.play()
    playButton.setTitle("Pause", forState: .Normal)
  } else {
    player.pause()
    playButton.setTitle("Play", forState: .Normal)
  }
 
  updatePlayButtonTitle()
  updateRateSegmentedControl()
}
 
// 6
func playerDidReachEndNotificationHandler(notification: NSNotification) {
  let playerItem = notification.object as AVPlayerItem
  playerItem.seekToTime(kCMTimeZero)
}

A breakdown of the above code:

  1. Creates a new player layer and sets its frame.
  2. Creates a player with an AV asset.
  3. Tells the player to do nothing when it finishes playing; additional options include pausing or advancing to the next asset, if applicable.
  4. Registers for AVPlayer’s notification when it finishes playing an asset (and remove the controller as an observer in deinit).
  5. When the play button is tapped, it toggles controls to play the AV asset and set the button’s title.

Note this is just a simple example just to get you started. In a real project, it would generally not be advisable to pivot on a button’s title text.

The AVPlayerLayer and its AVPlayer created above would be visually represented by the first frame of the AVPlayerItem instance, like this:

AVPlayerItem

AVPlayerLayer has a couple additional properties:

  • videoGravity sets the resizing behavior of the video display.
  • readyForDisplay checks if the video is ready for display.

AVPlayer, on the other hand, has quite a few additional properties and methods. One to note is rate, which is the playback rate from 0 to 1. Zero means to pause, and 1 means the video plays at regular speed (1x).

However, setting rate also instructs playback to commence at that rate. In other words, calling pause() and setting rate to 0 does the same thing, as calling play() and setting rate to 1.

So what about fast forward, slow motion or playing in reverse? AVPlayerLayer has you covered. Setting rate to anything higher than 1 is equivalent to asking the player to commence playback at that number times regular speed, for instance, setting rate to 2 means double-speed.

As you might assume, setting rate to a negative number instructs playback to commence at that number times regular speed in reverse.

Before playback occurs at any rate other than regular speed (forward), however, the appropriate method is called on the AVPlayerItem instance to verify that it can be played back at that rate:

  • canPlayFastForward() for higher than 1
  • canPlaySlowForward() for between 0 and 1
  • canPlayReverse() for -1
  • canPlaySlowReverse() for between -1 and 0
  • canPlayFastReverse() for lower than -1

Most videos can typically play at various forward speeds, but it’s less typical that they can play in reverse. Layer Player also includes playback controls:

AVLayerPlayer_5.5

Example #5: CAGradientLayer

CAGradientLayer makes it easy to blend two or more colors together, making it especially well suited to backgrounds. To configure it, you assign an array of CGColors, as well as a startPoint and an endPoint to specify where the gradient layer should begin and end.

Bear in mind, startPoint and endPoint are not explicit points. Rather, they are defined in the unit coordinate space and then mapped to the layer’s bounds when drawn. In other words, an x value of 1 means the point is at the right edge of the layer, and a y value of 1 means the point is at the bottom edge of the layer.

CAGradientLayer has a type property, although kCAGradientLayerAxial is the only option, and it transitions through each color in the array linearly.

This means that if you draw a line (A) between startPoint and endPoint, the gradations would occur along an imaginary line (B) that is perpendicular to A, and all points along B would be the same color:

AxialGradientLayerType

Alternatively, you can control the locations property with an array of values between 0 and 1 that specify relative stops where the gradient layer should use the next color in the colors array.

If left unspecified the stop locations default to evenly spaced. If locations is set, though, its count must match colors count, or else bad things will happen. :[

Here’s an example of how to create a gradient layer:

let gradientLayer = CAGradientLayer()
gradientLayer.frame = someView.bounds
gradientLayer.colors = [cgColorForRed(209.0, green: 0.0, blue: 0.0),
  cgColorForRed(255.0, green: 102.0, blue: 34.0),
  cgColorForRed(255.0, green: 218.0, blue: 33.0),
  cgColorForRed(51.0, green: 221.0, blue: 0.0),
  cgColorForRed(17.0, green: 51.0, blue: 204.0),
  cgColorForRed(34.0, green: 0.0, blue: 102.0),
  cgColorForRed(51.0, green: 0.0, blue: 68.0)]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 0, y: 1)
someView.layer.addSublayer(gradientLayer)
 
func cgColorForRed(red: CGFloat, green: CGFloat, blue: CGFloat) -> AnyObject {
  return UIColor(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: 1.0).CGColor as AnyObject
}

In the above code, you create a gradient layer, match its frame to the bounds of someView, assign an array of colors, set start and end points, and add the gradient layer to the view hierarchy. Here’s what it would look like:

CAGradientLayer

So colorful! Next, you’ll program a butterfly that comes fluttering out of the app to tickle your nose. :]

Layer Player provides you controls to change start and end points, colors and locations:

CAGradientLayer_5.5

Example #6: CAReplicatorLayer

CAReplicatorLayer duplicates a layer a specified number of times, which can allow you to create some cool effects.

Each layer copy can have it’s own color and positioning changes, and its drawing can be delayed to give an animation effect to the overall replicator layer. Depth can also be preserved to give the replicator layer a 3D effect. Here’s an example:

// 1
let replicatorLayer = CAReplicatorLayer()
replicatorLayer.frame = someView.bounds
 
// 2
replicatorLayer.instanceCount = 30
replicatorLayer.instanceDelay = CFTimeInterval(1 / 30.0)
replicatorLayer.preservesDepth = false
replicatorLayer.instanceColor = UIColor.whiteColor().CGColor
 
// 3
replicatorLayer.instanceRedOffset = 0.0
replicatorLayer.instanceGreenOffset = -0.5
replicatorLayer.instanceBlueOffset = -0.5
replicatorLayer.instanceAlphaOffset = 0.0
 
// 4
let angle = Float(M_PI * 2.0) / 30
replicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)
someView.layer.addSublayer(replicatorLayer)
 
// 5
let instanceLayer = CALayer()
let layerWidth: CGFloat = 10.0
let midX = CGRectGetMidX(someView.bounds) - layerWidth / 2.0
instanceLayer.frame = CGRect(x: midX, y: 0.0, width: layerWidth, height: layerWidth * 3.0)
instanceLayer.backgroundColor = UIColor.whiteColor().CGColor
replicatorLayer.addSublayer(instanceLayer)
 
// 6
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.fromValue = 1.0
fadeAnimation.toValue = 0.0
fadeAnimation.duration = 1
fadeAnimation.repeatCount = Float(Int.max)
 
// 7
instanceLayer.opacity = 0.0
instanceLayer.addAnimation(fadeAnimation, forKey: "FadeAnimation")

What the above code does:

  1. Creates an instance of CAReplicatorLayer and sets its frame set to someView‘s bounds.
  2. Sets the replicator layer’s number of copies (instanceCount) and drawing delay. Also sets the replicator layer to be 2D (preservesDepth = false) and its instance color to white.
  3. Adds red/green/blue offsets to the color values of each successive replicated instance. Each defaults to 0, and that effectively preserves color value across all instances. However, in this case, the instance color was originally set to white, meaning red, green and blue are 1.0 already. Hence, setting red to 0 and the green and blue offset values to a negative number allows red to be the prominent color. Similarly, the alpha offset is added to the alpha of each successive replicated instance.
  4. Creates a transform to rotate each successive instance around a circle.
  5. Creates an instance layer for the replicator layer to use and sets its frame so the first instance will be drawn at center x and at the top of someView‘s bounds. This block also sets the instance’s color and adds the instance layer to the replicator layer.
  6. Makes a fade animation to animate opacity from 1 (opaque) to 0 (transparent).
  7. Sets the instance layer’s opacity to 0 so that it is transparent until each instance is drawn and its color and alpha values are set.

And here’s what that code would get you:

CAReplicatorLayer

Layer Player includes controls to manipulate most of these properties:

CAReplicatorLayer_5.5

Example #7: CATiledLayer

CATiledLayer asynchronously draws layer content in tiles. This is great for very large images or other sets of content where you are only looking at small bits at a time, because you can start seeing your content without having to load it all into memory at once.

There are a couple of ways to handle the drawing. One is to override UIView and use a CATiledLayer to repeatedly draw tiles to fill up view’s background, like this:

// In ViewController.swift
import UIKit
 
class ViewController: UIViewController {
 
  // 1
  @IBOutlet weak var tiledBackgroundView: TiledBackgroundView!
 
}
 
// In TiledBackgroundView.swift
import UIKit
 
class TiledBackgroundView: UIView {
 
  let sideLength = CGFloat(50.0)
 
  // 2
  override class func layerClass() -> AnyClass {
    return CATiledLayer.self
  }
 
  // 3
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    srand48(Int(NSDate().timeIntervalSince1970))
    let layer = self.layer as CATiledLayer
    let scale = UIScreen.mainScreen().scale
    layer.contentsScale = scale
    layer.tileSize = CGSize(width: sideLength * scale, height: sideLength * scale)
  }
 
  // 4
  override func drawRect(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    var red = CGFloat(drand48())
    var green = CGFloat(drand48())
    var blue = CGFloat(drand48())
    CGContextSetRGBFillColor(context, red, green, blue, 1.0)
    CGContextFillRect(context, rect)
  }
 
}

Here’s what’s happening in the above code:

  1. tiledBackgroundView is positioned at (150, 150) with width and height of 300.
  2. layerClass() is overridden so the layer for this view is created as an instance of CATiledLayer.
  3. Seeds the rand48() function that will be used to generate random colors in drawRect(). Then scales the contents of the layer (cast as a CATiledLayer) to match the screen’s scale and its tile size set.
  4. Overrides drawRect() to fill the view with tiled layers with random colors.

Ultimately, the above code draws a 6×6 grid of randomly colored square tiles, like this:

CATiledLayer

Layer Player expands upon this usage by also drawing a path on top of the tiled layer background:

CATiledLayer_5.5

The star in the above screenshot becomes blurry as you zoom in on the view:

CATiledLayerZoomedBlurry

This blurriness is the result of levels of detail maintained by the layer. CATiledLayer has two properties, levelsOfDetail and levelsOfDetailBias.

levelsOfDetail, as its name aptly applies, is the number of levels of detail maintained by the layer. It defaults to 1, and each incremental level caches at half the resolution of the previous level. The maximum levelsOfDetail value for a layer is that which its bottom-most level of detail has at least a single pixel.

levelsOfDetailBias, on the other hand, is the number of magnified levels of detail cached by this layer. It defaults to 0, meaning no additional magnified levels will be cached, and each incremental level will be cached at double the preceding level’s resolution.

For example, increasing the levelsOfDetailBias to 5 for the blurry tiled layer above would result in caching levels magnified at 2x, 4x, 8x, 16x and 32x, and the zoomed in layer would look like this:

CATiledLayerZoomed

Pretty cool, eh? But wait, there’s more!

CATiledLayer slices and dices and…sorry, had a flashback to the Ginsu knife infomercial for a second there. :]

But seriously, CATiledLayer has another, dare I say more useful purpose than a Ginsu knife: asynchronously drawing tiles of a very large image, for example, within a scroll view.

You have to provide the tiles and logic to tell the tiled layer which tiles to grab as the user scrolls around, but the performance gain here is remarkable.

Layer Player includes a UIImage extension in a file named UIImage+TileCutter.swift. Fellow tutorial team member Nick Lockwood adapted this code for his Terminal app, which he provided in his excellent book, iOS Core Animation: Advanced Techniques.

Its job is to slice and dice the source image into square tiles of the specified size, named according to the column and row location of each tile; for example, windingRoad_6_2.png for the tile at column 7, row 3 (zero-indexed):

windingRoad

With those tiles in place, a custom UIView subclass can be created to draw those tile layers:

import UIKit
 
class TilingViewForImage: UIView {
 
  // 1
  let sideLength = CGFloat(640.0)
  let fileName = "windingRoad"
  let cachesPath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as String
 
  // 2
  override class func layerClass() -> AnyClass {
    return CATiledLayer.self
  }
 
  // 3
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let layer = self.layer as CATiledLayer
    layer.tileSize = CGSize(width: sideLength, height: sideLength)
  }
 
  // 4
  override func drawRect(rect: CGRect) {
    let firstColumn = Int(CGRectGetMinX(rect) / sideLength)
    let lastColumn = Int(CGRectGetMaxX(rect) / sideLength)
    let firstRow = Int(CGRectGetMinY(rect) / sideLength)
    let lastRow = Int(CGRectGetMaxY(rect) / sideLength)
 
    for row in firstRow...lastRow {
      for column in firstColumn...lastColumn {
        if let tile = imageForTileAtColumn(column, row: row) {
          let x = sideLength * CGFloat(column)
          let y = sideLength * CGFloat(row)
          let point = CGPoint(x: x, y: y)
          let size = CGSize(width: sideLength, height: sideLength)
          var tileRect = CGRect(origin: point, size: size)
          tileRect = CGRectIntersection(bounds, tileRect)
          tile.drawInRect(tileRect)
        }
      }
    }
  }
 
  func imageForTileAtColumn(column: Int, row: Int) -> UIImage? {
    let filePath = "\(cachesPath)/\(fileName)_\(column)_\(row)"
    return UIImage(contentsOfFile: filePath)
  }
 
}

The above code:

  1. Creates properties for length of the tile side, base image filename, and the path to the caches directory where the TileCutter extension saves tiles.
  2. Overrides layerClass() to return CATiledLayer.
  3. Implements init(_:), in the view’s layer, casts it as a tiled layer and sets its tile size. Note that it is not necessary to match contentsScale to the screen scale, because you’re working with the backing layer of the view directly vs. creating a new layer and adding it as a sublayer.
  4. Overrides drawRect() to draw each tile according to its column and row position.

Then a view of that subclass type, sized to the original image’s dimensions can be added to a scroll view:

XcodeTilingViewForImageStoryboard

And voilà, you have buttery smooth scrolling of a large image (5120 x 3200 in this case), thanks to CATiledLayer:

CATiledImageLayer

As you can see in the above animation, though, there is noticeable blockiness when fast-scrolling as individual tiles are drawn. Minimize this behavior by using smaller tiles (the tiles used in the above example were cut to 640 x 640) and by creating a custom CATiledLayer subclass and overriding fadeDuration() to return 0:

class TiledLayer: CATiledLayer {
 
  override class func fadeDuration() -> CFTimeInterval {
    return 0.0
  }
 
}

Example #8: CAShapeLayer

CAShapeLayer makes use of scalable vector paths to draw, and it’s much faster than using images. Another part of the win here is that you’ll no longer need to provide images at regular, @2x and @3x sizes. w00t!

Additionally, you have a variety of properties at your disposal to customize line thickness, color, dashing, how lines join other lines, if the line intersects itself to form a closed area, and if that area should be filled and with what color. Here’s an example:

import UIKit
 
class ViewController: UIViewController {
 
  @IBOutlet weak var someView: UIView!
 
  // 1
  let rwColor = UIColor(red: 11/255.0, green: 86/255.0, blue: 14/255.0, alpha: 1.0)
  let rwPath = UIBezierPath()
  let rwLayer = CAShapeLayer()
 
  // 2
  func setUpRWPath() {
    rwPath.moveToPoint(CGPointMake(0.22, 124.79))
    rwPath.addLineToPoint(CGPointMake(0.22, 249.57))
    rwPath.addLineToPoint(CGPointMake(124.89, 249.57))
    rwPath.addLineToPoint(CGPointMake(249.57, 249.57))
    rwPath.addLineToPoint(CGPointMake(249.57, 143.79))
    rwPath.addCurveToPoint(CGPointMake(249.37, 38.25), controlPoint1: CGPointMake(249.57, 85.64), controlPoint2: CGPointMake(249.47, 38.15))
    rwPath.addCurveToPoint(CGPointMake(206.47, 112.47), controlPoint1: CGPointMake(249.27, 38.35), controlPoint2: CGPointMake(229.94, 71.76))
    rwPath.addCurveToPoint(CGPointMake(163.46, 186.84), controlPoint1: CGPointMake(182.99, 153.19), controlPoint2: CGPointMake(163.61, 186.65))
    rwPath.addCurveToPoint(CGPointMake(146.17, 156.99), controlPoint1: CGPointMake(163.27, 187.03), controlPoint2: CGPointMake(155.48, 173.59))
    rwPath.addCurveToPoint(CGPointMake(128.79, 127.08), controlPoint1: CGPointMake(136.82, 140.43), controlPoint2: CGPointMake(129.03, 126.94))
    rwPath.addCurveToPoint(CGPointMake(109.31, 157.77), controlPoint1: CGPointMake(128.59, 127.18), controlPoint2: CGPointMake(119.83, 141.01))
    rwPath.addCurveToPoint(CGPointMake(89.83, 187.86), controlPoint1: CGPointMake(98.79, 174.52), controlPoint2: CGPointMake(90.02, 188.06))
    rwPath.addCurveToPoint(CGPointMake(56.52, 108.28), controlPoint1: CGPointMake(89.24, 187.23), controlPoint2: CGPointMake(56.56, 109.11))
    rwPath.addCurveToPoint(CGPointMake(64.02, 102.25), controlPoint1: CGPointMake(56.47, 107.75), controlPoint2: CGPointMake(59.24, 105.56))
    rwPath.addCurveToPoint(CGPointMake(101.42, 67.57), controlPoint1: CGPointMake(81.99, 89.78), controlPoint2: CGPointMake(93.92, 78.72))
    rwPath.addCurveToPoint(CGPointMake(108.38, 30.65), controlPoint1: CGPointMake(110.28, 54.47), controlPoint2: CGPointMake(113.01, 39.96))
    rwPath.addCurveToPoint(CGPointMake(10.35, 0.41), controlPoint1: CGPointMake(99.66, 13.17), controlPoint2: CGPointMake(64.11, 2.16))
    rwPath.addLineToPoint(CGPointMake(0.22, 0.07))
    rwPath.addLineToPoint(CGPointMake(0.22, 124.79))
    rwPath.closePath()
  }
 
  // 3
  func setUpRWLayer() {
    rwLayer.path = rwPath.CGPath
    rwLayer.fillColor = rwColor.CGColor
    rwLayer.fillRule = kCAFillRuleNonZero
    rwLayer.lineCap = kCALineCapButt
    rwLayer.lineDashPattern = nil
    rwLayer.lineDashPhase = 0.0
    rwLayer.lineJoin = kCALineJoinMiter
    rwLayer.lineWidth = 1.0
    rwLayer.miterLimit = 10.0
    rwLayer.strokeColor = rwColor.CGColor
  }
 
  override func viewDidLoad() {
    super.viewDidLoad()
 
    // 4
    setUpRWPath()
    setUpRWLayer()
    someView.layer.addSublayer(rwLayer)
  }
 
}

Here’s the lowdown on the above code:

  1. Creates color, path, and shape layer objects.
  2. Draws the shape layer’s path. If writing this sort of boilerplate drawing code is not your cup of tea, check out PaintCode; it generates the code for you by letting you draw using intuitive visual controls or import existing vector (SVG) or Photoshop (PSD) files.
  3. Sets up the shape layer. Its path is set to the CGPath of the path drawn in step 2, its fill color to the CGColor of the color created in step 1, and the fill rule is explicitly set to the default value of non-zero.
    • The only other option is even-odd, and for this shape that has no intersecting paths the fill rule makes little difference.
    • The non-zero rule counts left-to-right paths as +1 and right-to-left paths as -1; it adds up all values for paths and if the total is greater than 0, it fills the shape(s) formed by the paths.
    • Essentially, non-zero fills all points inside the shape.
    • The even-odd rule counts the total number of path crossings that form a shape and if the count is odd, that shape is filled. This is definitely a case when a picture is worth a thousand words.
    • The number of path crossings in the even-odd diagram that form the pentagon shape is even, so the pentagon is not filled, whereas the number path crossings that form each triangle is odd, so the triangles are filled.
      CAShapeLayerFillRules

  4. Calls the path drawing and layer set up code, and then it adds the layer to the view hierarchy.

This code draws the raywenderlich.com logo:

RayWenderlichLogo

And in case you’re curious to know what this drawing looks like in PaintCode:

PaintCodeRayWenderlichLogo

Layer Player includes controls to manipulate many of CAShapeLayer’s properties:

CAShapeLayer_5.5

Note: You may notice that we’re skipping over the next demo in the Layer Player app. This is because CAEAGLLayer is effectively obsoleted by CAMetalLayer, which debuted with iOS 8 alongside the Metal framework. You can find a great tutorial covering CAMetalLayer here.

Example #9: CATransformLayer

CATransformLayer does not flatten its sublayer hierarchy like other layer classes, so it’s handy for drawing 3D structures. It’s actually a container for its sublayers, and each sublayer can have its own transforms and opacity changes, however, it ignores changes to other rendered layer properties such as border width and color.

You cannot directly hit test a transform layer because it doesn’t have a 2D coordinate space to map a touch point to, however, it’s possible to hit test individual sublayers. Here’s an example:

import UIKit
 
class ViewController: UIViewController {
 
  @IBOutlet weak var someView: UIView!
 
  // 1
  let sideLength = CGFloat(160.0)
  var redColor = UIColor.redColor()
  var orangeColor = UIColor.orangeColor()
  var yellowColor = UIColor.yellowColor()
  var greenColor = UIColor.greenColor()
  var blueColor = UIColor.blueColor()
  var purpleColor = UIColor.purpleColor()
  var transformLayer = CATransformLayer()
 
  // 2
  func setUpTransformLayer() {
    var layer = sideLayerWithColor(redColor)
    transformLayer.addSublayer(layer)
 
    layer = sideLayerWithColor(orangeColor)
    var transform = CATransform3DMakeTranslation(sideLength / 2.0, 0.0, sideLength / -2.0)
    transform = CATransform3DRotate(transform, degreesToRadians(90.0), 0.0, 1.0, 0.0)
    layer.transform = transform
    transformLayer.addSublayer(layer)
 
    layer = sideLayerWithColor(yellowColor)
    layer.transform = CATransform3DMakeTranslation(0.0, 0.0, -sideLength)
    transformLayer.addSublayer(layer)
 
    layer = sideLayerWithColor(greenColor)
    transform = CATransform3DMakeTranslation(sideLength / -2.0, 0.0, sideLength / -2.0)
    transform = CATransform3DRotate(transform, degreesToRadians(90.0), 0.0, 1.0, 0.0)
    layer.transform = transform
    transformLayer.addSublayer(layer)
 
    layer = sideLayerWithColor(blueColor)
    transform = CATransform3DMakeTranslation(0.0, sideLength / -2.0, sideLength / -2.0)
    transform = CATransform3DRotate(transform, degreesToRadians(90.0), 1.0, 0.0, 0.0)
    layer.transform = transform
    transformLayer.addSublayer(layer)
 
    layer = sideLayerWithColor(purpleColor)
    transform = CATransform3DMakeTranslation(0.0, sideLength / 2.0, sideLength / -2.0)
    transform = CATransform3DRotate(transform, degreesToRadians(90.0), 1.0, 0.0, 0.0)
    layer.transform = transform
    transformLayer.addSublayer(layer)
 
    transformLayer.anchorPointZ = sideLength / -2.0
    applyRotationForXOffset(16.0, yOffset: 16.0)
  }
 
  // 3
  func sideLayerWithColor(color: UIColor) -> CALayer {
    let layer = CALayer()
    layer.frame = CGRect(origin: CGPointZero, size: CGSize(width: sideLength, height: sideLength))
    layer.position = CGPoint(x: CGRectGetMidX(someView.bounds), y: CGRectGetMidY(someView.bounds))
    layer.backgroundColor = color.CGColor
    return layer
  }
 
  func degreesToRadians(degrees: Double) -> CGFloat {
    return CGFloat(degrees * M_PI / 180.0)
  }
 
  // 4
  func applyRotationForXOffset(xOffset: Double, yOffset: Double) {
    let totalOffset = sqrt(xOffset * xOffset + yOffset * yOffset)
    let totalRotation = CGFloat(totalOffset * M_PI / 180.0)
    let xRotationalFactor = CGFloat(totalOffset) / totalRotation
    let yRotationalFactor = CGFloat(totalOffset) / totalRotation
    let currentTransform = CATransform3DTranslate(transformLayer.sublayerTransform, 0.0, 0.0, 0.0)
    let rotationTransform = CATransform3DRotate(transformLayer.sublayerTransform, totalRotation,
      xRotationalFactor * currentTransform.m12 - yRotationalFactor * currentTransform.m11,
      xRotationalFactor * currentTransform.m22 - yRotationalFactor * currentTransform.m21,
      xRotationalFactor * currentTransform.m32 - yRotationalFactor * currentTransform.m31)
    transformLayer.sublayerTransform = rotationTransform
  }
 
  // 5
  override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    if let location = touches.anyObject()?.locationInView(someView) {
      for layer in transformLayer.sublayers {
        if let hitLayer = layer.hitTest(location) {
          println("Transform layer tapped!")
          break
        }
      }
    }
  }
 
  override func viewDidLoad() {
    super.viewDidLoad()
 
    // 6
    setUpTransformLayer()
    someView.layer.addSublayer(transformLayer)
  }
 
}

The above code does all of this:

  1. Creates properties for side length, colors for each side of the cube, and a transform layer.
  2. Builds the cube by creating, rotating and then adding each side to the transform layer. Then it sets the transform layer’s z axis anchor point, rotates the cube and adds the cube to the view hierarchy.
  3. Creates helper code to create each cube side layer with the specified color and to convert degrees to radians. Why radians? Simply because I find it more intuitive to work with degrees than radians. :]
  4. Applies a rotation based on specified x and y offsets. Notice that the code sets the transform to sublayerTransform, and that applies to the sublayers of the transform layer.
  5. Observes touches and cycles through the sublayers of the transform layer. This section hit tests each one and breaks out as soon as a hit is detected, since there are no benefits to hit testing remaining layers.
  6. Sets up the transform layer and adds it to the view hierarchy.

Note: So what’s with all those currentTransform.m##s? I’m glad you asked, sort of :]. These are CATransform3D properties that represent elements of a matrix that comprises a rectangular array of rows and columns.

To learn more about matrix transformations like those used in this example, check out 3DTransformFun project by fellow tutorial team member Rich Turton and Enter The Matrix project by Mark Pospesel.

Running the above code with someView being a 250 x 250 view results in this:

CATransformLayer

Now, try something: tap anywhere on the cube and “Transform layer tapped!” will print to the console.

Layer Player includes switches to toggle the opacity of each sublayer, and the TrackBall utility from Bill Dudney, ported to Swift, which makes it easy to apply 3D transforms based on user gestures:

CATransformLayer_5.5

Example #10: CAEmitterLayer

CAEmitterLayer renders animated particles that are instances of CAEmitterCell. Both CAEmitterLayer and CAEmitterCell have properties to change rendering rate, size, shape, color, velocity, lifetime and more. Here’s an example:

import UIKit
 
class ViewController: UIViewController {
 
  // 1
  let emitterLayer = CAEmitterLayer()
  let emitterCell = CAEmitterCell()
 
  // 2
  func setUpEmitterLayer() {
    emitterLayer.frame = view.bounds
    emitterLayer.seed = UInt32(NSDate().timeIntervalSince1970)
    emitterLayer.renderMode = kCAEmitterLayerAdditive
    emitterLayer.drawsAsynchronously = true
    setEmitterPosition()
  }
 
  // 3
  func setUpEmitterCell() {
    emitterCell.contents = UIImage(named: "smallStar")?.CGImage
 
    emitterCell.velocity = 50.0
    emitterCell.velocityRange = 500.0
 
    emitterCell.color = UIColor.blackColor().CGColor
    emitterCell.redRange = 1.0
    emitterCell.greenRange = 1.0
    emitterCell.blueRange = 1.0
    emitterCell.alphaRange = 0.0
    emitterCell.redSpeed = 0.0
    emitterCell.greenSpeed = 0.0
    emitterCell.blueSpeed = 0.0
    emitterCell.alphaSpeed = -0.5
 
    let zeroDegreesInRadians = degreesToRadians(0.0)
    emitterCell.spin = degreesToRadians(130.0)
    emitterCell.spinRange = zeroDegreesInRadians
    emitterCell.emissionRange = degreesToRadians(360.0)
 
    emitterCell.lifetime = 1.0
    emitterCell.birthRate = 250.0
    emitterCell.xAcceleration = -800.0
    emitterCell.yAcceleration = 1000.0
  }
 
  // 4
  func setEmitterPosition() {
    emitterLayer.emitterPosition = CGPoint(x: CGRectGetMidX(view.bounds), y: CGRectGetMidY(view.bounds))
  }
 
  func degreesToRadians(degrees: Double) -> CGFloat {
    return CGFloat(degrees * M_PI / 180.0)
  }
 
  override func viewDidLoad() {
    super.viewDidLoad()
 
    // 5
    setUpEmitterLayer()
    setUpEmitterCell()
    emitterLayer.emitterCells = [emitterCell]
    view.layer.addSublayer(emitterLayer)
  }
 
  // 6
  override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
    setEmitterPosition()
  }
 
}

The above code:

  1. Creates an emitter layer and cell.
  2. Sets up the emitter layer by doing the following:
    • Provides a seed for the layer’s random number generator that in turn randomizes certain properties of the layer’s emitter cells, such as velocity. This is further explained in the next comment.
    • Renders emitter cells above the layer’s background color and border in an order specified by renderMode.

      Note: Apple’s documentation currently incorrectly states that values for this property are defined under Emitter Modes. In fact, renderMode’s values are defined under Emitter Render Order. The default value is unordered, and additional options include oldest first, oldest last, back to front and additive.

    • Sets drawsAsynchronously to true, which may improve performance because the emitter layer must continuously redraw its emitter cells.
    • Next, the emitter position is set via a helper method — learn more on comment 4. This is a good case study for how setting drawsAsynchronously to true has a positive effect on performance and smoothness of animation.
  3. There’s a lot of action in this block!
    • It sets up the emitter cell by setting its contents to an image (this image is available in the Layer Player project).
    • Then it specifies an initial velocity and max variance (velocityRange); the emitter layer uses the aforementioned seed to create a random number generator that randomizes values within the range (initial value +/- the range value). This randomization happens for any properties ending in Range.
    • The color is set to black to allow the variance (discussed below) to vary from the default of white, because white results in overly bright particles.
    • A series of color ranges are set next, using the same randomization as for velocityRange, this time to specify the range of variance to each color. Speed values dictate how quickly each color can change over the lifetime of the cell.
    • Next, block three specifies how to distribute the cells around a full circular cone. More detail: It sets the emitter cell’s spinning velocity and emission range. Furthermore, emission range determines how emitter cells are distributed around a cone that is defined by the emissionRange specified in radians.
    • Sets the cell’s lifetime to 1 second. This property’s default value is 0, so if you don’t explicitly set this, your cells never appear! Same goes for birthRate (per second); the default is 0, so this must be set to some positive number in order for cells to appear.
    • Lastly, cell x and y acceleration are set; these values affect the visual angle to which the particles emit.
  4. Makes helper methods to convert degrees to radians and to set the emitter cell position to the midpoint of the view.
  5. Sets up the emitter layer and cell, and then adds that cell to the layer, and the layer to the view hierarchy.
  6. This method, new in iOS 8, provides a way to handle changes to the current trait collection, such as when the device is rotated. Not familiar with trait collections? Check out Section 1 of iOS 8 by Tutorials and you’ll become a master of them :]

Huzzah! I know that’s a lot of information, but you’re tough and smart.

The outcome of running the above code reminds me of those The More You Know commercials:

CAEmitterLayer2

Layer Player includes controls to adjust all of the above-mentioned properties, and several more:

CAEmitterLayer_5.5

victorious

Where To Go From Here?

Congratulations! You have completed the great CALayer Tour, and have seen 10 examples of how to use CALayer and its many subclasses.

But don’t stop here! Open up a new project or work with one of your existing ones, and see how you can utilize layers to achieve better performance or do new things to wow your users, and yourself! :]

As always, if you have any questions or comments about this article or working with layers, join in on the discussion below!

CALayer in iOS with Swift: 10 Examples is a post from: Ray Wenderlich

The post CALayer in iOS with Swift: 10 Examples appeared first on Ray Wenderlich.

Mobile Design with Alli Dryer – Podcast S03 E03

$
0
0
Learn about Mobile Design with Alli Dryer!

Learn about Mobile Design with Alli Dryer!

Welcome back to season 3 of the raywenderlich.com podcast!

In this episode, learn about mobile design with Alli Dryer, creator of capptivate.co – how to learn design, how to find designers, common design mistakes, and more!

[Subscribe in iTunes] [RSS Feed]

Transcript

Note from Ray: This is our first time adding a transcript to the podcast. Let us know what you think, and if this is something you’d like to see us keep doing!

RWDevCon

Mic: Hey, Jake.

Jake: Hey Mic, how are you doing?

Mic: Not so bad, you?

Jake: Pretty good, pretty good. It’s been a good week.

Mic: You all set for RWDevCon next week?

Jake: I am still getting set yes. I’ll be ready when the time comes. Okay, how about you? Are you ready?

Mic: Just about yeah.

Jake: Got all your presentations in order?

Mic: I’m just doing the final run through of each one now. The prep work’s done; this is just the final, final, final check before I send everything over to Ray to package up and distribute off to the guys that are coming, but yeah, pretty much there.

Jake: It took a lot more prep work than I thought it would, but I think because of that it’s going to end up being a pretty good conference. I feel like when it comes I’ll be quite well prepared.

Mic: Yeah, I think it’s going to be epic. I think I put a thing on Twitter a while ago – I’ve been to a few conferences and I’ve spoken at a conference before, but I’ve never known as much work and planning and effort go into a conference that at least I’ve been to as this.

I think it’s going to be really enjoyable. I think the fact that it’s tutorials rather than just sit and watching somebody talk for 30 minutes you’re going to come away really feeling that you’ve learned something in each session which I think’s going to be great as well.

Jake: Yeah, I agree. I agree I think people will feel like they really, I mean when you go to a conference I think it’s hit and miss. Sometimes you come out feeling like wow that was packed I learned so much. Then, other times you’re like it was interesting but I might’ve enjoyed a session, but you don’t necessarily feel like I’m going to hit the ground running tomorrow when I start using this or whatever.

Mic [02:00]: Yeah, I definitely think you’re going to learn some skills in the conference that you can definitely take straight and start applying them to whatever you’re doing. I definitely think it’s the way to go for this particular conference. I’m really, really looking forward to it.

Mic and Jake’s Experience w/ Designers

In this episode of the podcast we’re going to be talking about mobile design: design inspiration, developers doing their own design work, that kind of thing. I know that you tend to do a lot of contract work Jake. Do you, more often than not, work with a designer or do you a lot of the design work yourself?

Jake: I do usually work with a designer, so it’s interesting. In the earlier days of the iOS ecosystem when I was getting started I did most of the design myself. I did a lot of games. I didn’t necessarily do the graphics but I would work with other programmers that were doing graphics or I would do the, I didn’t necessarily draw sprites or game art, but I would do backgrounds and I would do menus and layouts and stuff like that.

As the iOS ecosystem as gotten more competitive and the polish level has gone up, I found myself doing less design work because I’m not a designer. I’m pretty weak at least when it comes to actually drawing a button or picking out a color scheme or whatever. That’s certainly not my strong point. How about you?

Mic [04:00]: Yeah, in the early days, like you, did much of my design work myself. I do know me way around Photoshop and more recently Sketch. I can do design work, but I have also worked with designers and I find that preferable because what might take me a few hours to do they do it relatively, turn it around relatively quicker.

Also, those are the ideas guys. A lot of the stuff that I do I take inspiration from others. I won’t directly call it copying, but you can definitely see into my designs which apps I’ve been inspired by. But obviously if you’re working with a designer they tend to … they’re the guys that come up with that stuff which I think is another bonus of you can work with somebody that’s really skilled.

I worked with a guy based in Canada who was fantastic. He worked a lot with the contrast guys, you know Logic Pro and that kind of stuff. He’s done a lot of work with them. I’ve also worked with, when I worked on the Duck Duck Go, they had their inhouse designer who was fantastic to work with. Between doing my own design work or working with a designer I would definitely choose a designer I think every time.

Jake: Yeah, I’ve worked with a couple of design shops where basically they do, all they do is design websites and apps and stuff. They contract out their programmers. I’m independent, but they don’t necessarily have the iOS skills in house so they bring me along.

I found that in that case specifically I feel good with laying out workflows and things like that and functionality. Even things like the way an iOS app is supposed to work where the buttons go. Conventions I feel good about all of that.

A lot of times the design firm they’ll deliver this beautiful design, but they don’t necessarily have the familiarity with iOS and buttons are in weird places or they should be using a tab view navigation controller and they’re inventing some random thing. They’re doing something that looks more like an Android app.

I’ve had that too where I look at a design and I’m like this is gorgeous, but functionally it’s weird and it’s awkward. It depends on what part of design you’re talking about. Definitely when it comes right down to the point of drawing a button I would much rather have somebody who’s skilled do that for me because I just, yeah, I’m not good at that at all.

Mic: Where are you helped in your own design, so I’m talking specifically since iOS7 now? Have you found it easier with the transition to the new interface style, that flatter look? How is your morphism and is, well, just flat really because there’s no shadow. There’s no leather, no green stitching.

Jake [06:00]: Yes, I’d say yes and no. It’s much easier to use stock controls where you select the color palette and you put something together with stock controls. That looks passible and I certainly am capable of doing that. I find it harder to make things look like stand out good with this flat design.

Mic: No, I think so and I think that’s probably where working with a designer comes in because as somebody who’s not that way inclined naturally say you can only go so far. It’s working with people who specialize in that area that know the little touches that set goods to great.

Design Inspiration Resources

That’s where you’re going to stand out on the upstart, but also I think you can also draw a lot of that stuff from looking at sites that curate and collect all the different interfaces of popular iOS apps. There’s many out there. Patterns is a popular one in mobile buttons. That’s Pttrns spelled P-T-T-R-N-S which is a really popular site for collecting UI from iOS apps.

I’ve often found that if I go so far with a design myself, if I really want to, if I’m not feeling that it’s quite there yet, if I go on one of these sites then you can often find, not to copy the entire UI, but it just might be a gradient or it might just be like a really subtle effect. Then, when you apply that to your UI it really brings it out and pulls it all together, kind of takes it to that next level.

Jake: Yeah, I agree. I think Pttrns is a great source to look for inspiration. I also find myself a little bit discouraged when I look at those because they are so amazing and what I’m working on in comparison usually doesn’t quite match that. Yeah, definitely when you’re looking for ideas there’s tons of great resources out there.

Capptivate.co

Mic [08:00]: Yeah, and one of those resources is Capptivate, spelled with a double P. we’ll actually lucky today to joined by Alli Dryer who is a designer at Twitter, but also the driving force behind Cappivate.co, Hey, Alli.

Alli: Hello.

Mic: Thanks for joining us today.

Alli: Thanks for having me.

Mic: Before we get into talking a little bit about Capptivate, can you tell us how is Alli Dryer?

Alli: Sure, I am a product designer at Twitter and an architect by training. I also have a side project that you mentioned called Capptivate where I post short videos of aminations that I think could be inspiring or just really great examples of the craft.

Mic: Where did the idea for Capptivate come from?

Alli: I used to work at an agency in Dallas called Bottle Rocket Apps. We would be pitching concepts to clients. I found myself making sounds effects and waving my arms around to demonstrate how the static mocks I was presenting would come to life on screen. It’s was just not a great way of communicating. Then you would get your phone out and start trying to dig out examples. Then, you would’ve uninstalled the app and all these difficulties.

I thought it would be great if there was a website that I could just reference at these points in time or where I could collect these examples so that I could have a library to access when I wanted to make a point.

Mic: When you got this idea did you then want to turn it into a fully-fledged website. Do you also have a background in web development? Is that something you did yourself or did you farm that out to somebody else?

Alli [10:00]: I am not formally trained basically at anything to do with digital design. I’ve been teaching myself for a while. The way that I even got into designing software in the first place was by hacking WordPress templates. That’s what Capptivate is it’s just a WordPress template that I found and then I monkeyed around in the CSS until it looked how I wanted it to.

The hardest thing about building the Capptivate website was trying to figure how to post the videos on the site so that they didn’t all auto play and loop at once. I had to figure out how to package a static image in front of this booby and have the image get out of the way when you hovered over it. To do that I found a tool called Hype that let me build this package and then upload it to the website.

Mic: That is a really cool effect. That is one of the things I like when I’m on that site is nothing looks interactive until you move your mouse over it and then all of a sudden it comes alive.

Alli: It can be frustrating at times because you might accidently mouse over something you didn’t mean to, but I think overall it’s nice that it’s not as overwhelming when you first land on the page.

Mic: Yeah, because I’m pretty sure it’d be quite resource intensive in the Browser as well if all those videos loaded at the same time.

Alli: Sure.

Mic: Is it just you that puts stuff on Capptivate or do you allow others to submit?

Alli: No, it’s just me. Right now I create the content that you see on the site myself. Sometimes people will submit their apps and I can take a look at them, but right now there’s no infrastructure for anyone else to post.

Capptivate.co iOS App

Mic: I believe you recently launched the official Capptivate iOS app? How has that experience been?

Alli [12:00]: Yeah, it’s been great. I got an email from a developer, his name’s Claude Sutterland, and he wrote to me and he said, “Hey, I’ve been using your site. I think it’s great. I really wish there was an app for iOS so I could see these videos on my phone. I’d this something I can build or would you be interested in working with me on it?”

I wrote him back and I said, “Look this is not a business, so I can’t pay you, but I have been working on designs for an app and maybe I would love to work together.” we just started emailing back and forth. I showed him the designs that I was working with and we bounced ideas off of each other. He actually came up with the feature that I think it makes the app ten times more useful than the website even, which is that you can drag your finger across the screen and scroll through the animation. Instead of having to watch it loop over and over and just hope that you’re catching that small effect that’s making the difference in the animation, you can just step through it and figure out exactly where it is and what’s happening.

We had a very smooth collaboration and the app hit the store and we’ve had I think somewhere around 5,700 downloads which impressed me because I wasn’t expecting that. I’ve been really happy with it. I think the major missing piece with the Capptivate app is just that I haven’t posted as much content as I should be. It’s hard to keep up with that part of it.

Jake: One of the things I notice about Capptivate is that there’s a focus on animation. Is that the primary distinction between Capptivate and some of the other Pttrns or some of the other websites that focus on design? What are some of the distinctions?

Alli [14:00]: The site is not organized the same way as Pttrns or Mobile Patterns even though it draws heavily from both of those in terms of how it’s laid out and how you interact with it. Every time I post a video of an app animation I break it down into a series of patterns and components that I made up and I defined.

I look at things like does it squash and stretch? Does it scale? Does it flip, fade, ripple, all of those things. I analyze that and I basically categorize the post according to those small components. If you get to the site and you’re interested in seeing something that, I don’t know, does a fold. You can search for folding animations and everything that has that aspect will be surface to you.

It works slightly differently in that you’re not going to be able to find a great example of a log-in flow or a sign-in page. You will see that there are some patterns that have emerged around the way these elements move and it’s meant to be a resource in that sense.

Capptivate and raywenderlich.com

Mic: Yeah, we actually use it a lot on the websites when we’re putting the tutorials and the video series together. If we’re looking for inspiration and that’s exactly how I use the site. I go through the, I can’t remember exactly what the menus called, the drop down menu where you’ve got all those listed.

You can go through and you can click and it’ll bring back just those where they apply. We use that if we want to. If for instance a recent video series we did on collection views we were looking for particular animations about how to move the cells around.

First port of call is Capptivate; let’s have a look at how other apps do that. Also, it helps us as well because we can relate or rather our audience can relate what we’re teaching them to an app that’s in production. It’s not just something that we can say that we’ve done this, but also we were inspired by such and such an app. Therefore they’ve got that one to one correlation between what they’re learning and then they can go and play with it. It helps us on all sorts of different levels.

Alli: I’m really glad to hear that.

Learning Design

Jake: As I mentioned earlier I feel comfortable with some elements of design, but when it comes right down to actually picking out a color palette and starting to put assets together I’m pretty weak. If I want to improve my, if I want to become a great iOS designer, how would I do that? What would you recommend?

Alli: I think there are a couple of sources I would probably direct you to. There are websites that can help a lot with color palettes like Cooler and things like that. Also, if you just take some time to hone your observation skills.

When you see something that’s working take a hard look at it and think how many colors are there in this screen. What is the relationship? Are they warm colors, are they cool colors? If you want to really nerd out, which can be very fun sometimes, but I can understand that’s going pretty deep, is read a book on color theory because you can learn pretty quickly that there are some principals that designers are applying when they’re picking colors, for example, that anyone can learn.

Another really great resource that’s out now is a book called “Design Plus Code” and I think it does a very good job of giving an overview of how to take that design driven approach towards your project and gives really concrete examples of a highly productive design and code workflow. I would check that out as well.

Mic [18:00]: Many of our listeners are in perhaps a similar position to Jake and I where we have got some visual design skills, but we want to improve them. If we were looking to say spend a couple of hours a day for a few months to do that what would you recommend was the best way to start? Obviously you’ve just mentioned reading a couple of books, but is there anything else that we can do and do repeatedly to improve those skills?

Alli: One of the things that, an exercise that I did when I was first starting out in architecture school, was to literally trace elevation drawings of buildings that other architects had created. I remember very distinctly sitting down in from of a Xerox copy of a drawing of the front of Monticello in virginia, Thomas Jefferson’s house, and just taking out my ruler and my pencil and just tracing it.

I think that if you wanted to spend a little time trying to recreate screens that you see that you think are particularly well done it might be an interesting discipline to do. Okay, I’m going to sit down and recreate this affect and see what does it take? How is this design layered? What affects are being applied? Just try to make it because I think you can learn so much by pulling apart the work of other people and analyzing the component parts. Once you get comfortable with those components then you’re in a position to attempt to recombine them in brand new ways that are uniquely yours.

Mic: I think now that’s really good advice because I think for a lot of people that don’t quite understand design, I’ve learned through working with good people, much of what you explained there, Alli. When I was a little more naïve you would look at a screen and you would try and recreate that screen from start to finish, top to bottom in one go.

Breaking that screen down into smaller chunks and looking at an animation or how a particular view is formed and how everything stacks on top of each other. I’m pulling it apart. I’m looking at each part individually is a much better way to understand what’s happening. I definitely think that there’s value in that and that’s helped me personally from working with designers.

Alli [20:00]: Yeah, I think just that process of tearing it down and trying to build it back up again you really understand why certain things are happening. I also think you start to understand the relationships that happen between elements on a screen and some things about the information hierarchy. Why is this type larger? Why is the designer trying to draw your attention here and what’s the effect of that? Why does it move in this way?

Hiring Designers

Jake: One thing I’ve noticed in the communities that I run in, I attend my local Cocoa Heads meeting for example, is that it doesn’t seem like the us coders and the designers, even who are both working on mobile apps, tend to mix very much.

I know some professionals just from working on different projects, but sometimes these are people that are working at firms and can’t actually afford to hire them as designers. Do you have any advice on where I can go to find good design for affordable prices?

Alli: I think that there are a lot of people who are posting on Dribble that you can reach out to if you like their work. I also know that a lot of the designers at these big companies sometimes do freelance projects on the side. If they’re really interested in what you’re doing sometimes you can get them involved in their spare time.

Yeah, I think the best way to find a designer like that is just to get familiar with their work and contact them. If you have a compelling problem to solve I think sometimes there’s a designer just can’t resist that.

Jake: Yeah, that makes sense. I feel sometimes the exact same way as a programmer. If somebody brings you a project that’s just really cool you’re like I want to work on this. I’ll do whatever it takes.

Alli: Yeah.

Jake: I know there’s some auction sites where it’s like a bidding war where, do you recommend those or do those have pitfalls?

Alli [22:00]: I can’t personally say I’ve ever gotten involved with that type of work, so I’m not sure what you’d be getting when you bid in that auction environment. What I can say is that I think any project where the parameters aren’t well designed and the communication between team members isn’t really good, you’re just going to run into problems and frustrations.

When I’m talking to people who are interested in working with me, I always spend a lot of time trying to figure out if they are making an ask that they really understand. That sounds kind of strange, but sometimes people will say I want an app, but they don’t have a nuanced understanding of what they’re asking for.

Those are the type of clients or partners that generally steer a little clear of just because it’s hard enough to come up with a really good idea and to realize it and to build an app. The undertaking is very difficult, but if you’re going to be changing your mind midstream or things come up and the team doesn’t work well together it can just be so much harder.

I think with some of those sites of auctions, I’m not sure you have that deep level of communication prior to singing up with those people. I think that you might potentially be a little more at risk that way.

Working with Designers

Mic: Okay, so let’s assume then that I’ve got my idea for an app as a developer. I’ve contacted a designer who’s willing to work with me to realize this into something that we can ship. I’m still a little bit naïve as to working with designers because this is first time I’ve done so. What advice can you give would be teams of developers and designers advice on working better?

Obviously you just said communication is a big part, but what else can each side do to make sure that they can go from just sharing idea to shipping the app?

Alli [24:00]: I think that making sure that the designer has a really good understanding of your technical capabilities and what your interests are can really help. There are times when a designer will not have a keen awareness of what the developer is interested in building or able to build and will propose something that will require a huge amount of technical complexity that the developer might not be interested in implementing.

I think that that can damage the relationship between the two disciplines when there’s just not a clear understanding of what each person can do and is good at. I think it’s another way of saying communication, but just being really clear about what’s feasible and what the goals of the project are and working back and forth and building on things and being iterative.

Design Inspiration, Mistakes, and Communication

Mic: Okay, that’s great advice. Moving on from we’ve got this team. We’re working really well together. You’re going to crack on now with the design work. I’ve pitched you the idea, you’ve already got some ideas for me. As a designer where would go to source some inspiration or is just the idea itself enough?

Alli: No, I would definitely look at other similar apps for inspiration. I would go to the patterns sites. I would check out Dribble. I would also try and conduct some kind of research depending on the project. It could be something like talking to potential users. If it has to do with some kind of environment, I would go to the environment. I would just try to immerse myself in all the details that are specific to the project so that I could get an inspiration that way.

Mic: Okay, great, just so going back a couple of steps for developers that are going to go out on their own rather than going with a developer. Could you list a few of the common design mistakes that developers should look out for?

Alli [26:00]: Yeah, one of the things that I think is very important when you’re making an app is just to think about the needs of the user possibly ahead of the conveniences of the platform. Thinking about what the experience will be like for people when they’ve got this app in their hand and when they’re using it in their life as opposed to maybe like what’s slightly easier to build or you already have the library for could be one thing.

Then, just from an aesthetic level try to limit the number of different fonts and type that you have onscreen and being more constrained about the color, like not using a ton of different disparate elements, but rather trying to limit yourself to just a few can really help the design feel more cohesive.

Jake: I mentioned earlier I got some designs from a developer and it looked like they were more familiar with the Android platform so they didn’t position things in the right places. I had to negotiate on that. Are there things that as a developer I may not understand about the way the designer thinks that would help me communicate or better present my idea to them? Does that make sense?

Alli: Yeah.

Jake: Things that I might not understand in that same vein. Where maybe they don’t understand how a tab interface works on the iPhone. What do I need to understand from a design point of view?

Alli [28:00]: I think designers are trained to always be looking for a series of visual principles. These are things like contrast or unity or harmony or the different colors that they’re using need to have a complimentary relationship. There’s basically this entire visual and aesthetic language that designers get trained in and they’re applying.

Sometimes that training causes a designer to approach a design for a screen in a certain way where they may be ignorant of platform conventions. I think that if you are a developer and you have that awareness it’s really important to make sure that the designer understands that they need to navigate both.

Both systems matter so much in this type of design it needs to be aesthetic, all of things that go into visual design. Those principles are really important, but they’re not going to work if when the user encounters them it’s in this wild new environment that doesn’t make sense or is sometimes antithetical to the way they’re used to interacting with other apps.

I think that what you’re seeing when the designer’s not demonstrating mastery of the platform is that they’re probably focused primarily on these visual elements. That’s a really great opportunity to educate. Both parties can educate because the designer can explain I grouped these things together up here because it’s visually balanced. They can talk to you about what that means. You can explain that we wouldn’t use tabs in this setting.

Where To Go From Here?

Mic: Okay, I think some really solid advice that we can all take something from. I think that’s as good a place as any to wrap things up. Thanks again for joining us Alli.

Alli: Thanks so much for having me.

Mic: It’s been a pleasure. We’ll be recording next week’s episode from RWDevCon. If anyone is heading out to DC for the conference then do come and find us and say hi as both Jake and I will there running sessions. That’s it guys for another episode. As always if you have any questions or feedback then don’t hesitate to get in touch by at podcast@Ray Enderlich.com.

If you do enjoy the podcast then please don’t forget to leave your review on iTunes. Hope you’ve enjoyed the podcast. Thanks for listening and we’ll see you again next time.

Our Sponsor

Interested in sponsoring a podcast episode? We sell ads via Syndicate Ads, check it out!

Links and References

Contact Us

Where To Go From Here?

We hope you enjoyed this episode of our podcast. Stay tuned for a new episode next week! :]

Be sure to subscribe in iTunes to get access as soon as it comes out!

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!

Mobile Design with Alli Dryer – Podcast S03 E03 is a post from: Ray Wenderlich

The post Mobile Design with Alli Dryer – Podcast S03 E03 appeared first on Ray Wenderlich.


Securing iOS User Data: The Keychain, Touch ID, and 1Password

$
0
0
Learn how to add Touch ID & 1Password to your app!

Learn how to secure your app using Touch ID

Protecting an app with a login screen is a great way to secure user data – you can use the Keychain, which is built right in to iOS, to ensure that their data stays secure. However, Apple now offers yet another layer of protection with Touch ID, available on the iPhone 5s, 6, 6+, iPad Air 2, and iPad mini 3.

And if that weren’t enough, with the introduction of extensions in iOS 8, you can even integrate the storage and retrieval of login information using the award-winning 1Password app from AgileBits, thanks to their developers open-sourcing their extension. This means you can comfortably hand over the responsibility of handling login information to the Keychain, TouchID, or 1Password!

In this tutorial you’ll start out using the Keychain to store and verify login information. After that, you’ll explore Touch ID and then finish off by integrating the 1Password extension into your app.

Note: Touch ID and 1Password require you test on a physical device, but the Keychain can be used in the simulator

Getting Started

Please download the starter project for this tutorial here.

This is a basic note taking app that uses Core Data to store user notes; the storyboard has a login view where users can enter a username and password, and the rest of the app’s views are already connected to each other and ready to use.

Build and run to see what your app looks like in its current state:

TouchMeIn starter

At this point, tapping the Login button simply dismisses the view and displays a list of notes – you can also create new notes from this screen. Tapping Logout takes you back the login view. If the app is pushed to the background it will immediately return to the login view; this protects data from being viewed without being logged in. This is controlled by setting Application does not run in background to YES in Info.plist.

Before you do anything else, you should change the Bundle Identifier, and assign an appropriate Team.

Select TouchMeIn in the Project Navigator, and then select the TouchMeIn target. In the General tab change Bundle Identifier to use your own domain name, in reverse-domain-notation – for example com.raywenderich.TouchMeIn.

Then, from the Team menu, select the team associated with your developer account like so:
Screen Shot 2014-12-30

With all of the housekeeping done, it’s time to code! :]

Logging? No. Log In.

To get the ball rolling, you’re going to add the ability to check the user-provided credentials against hard-coded values.

Open LoginViewController.swift and add the following constants just below where the managedObjectContext variable is declared:

let usernameKey = "batman"
let passwordKey = "Hello Bruce!"

These are simply the hard-coded username and password you’ll be checking the user-provided credentials against.

Add the following function below loginAction(_:):

func checkLogin(username: String, password: String ) -> Bool {
  if ((username == usernameKey) && (password == passwordKey)) {
    return true
  } else {
    return false
  }
}

This checks the user-provided credentials against the constants you defined earlier.

Next, replace the contents of loginAction(_:) with the following:

if (checkLogin(self.usernameTextField.text, password: self.passwordTextField.text)) {
  self.performSegueWithIdentifier("dismissLogin", sender: self)
}

This calls checkLogin(_:password:), which dismisses the login view if the credentials are correct.

Build and run. Enter the username batman and the password Hello Bruce!, and tap the Login button. The login screen should dismiss as expected.

While this simple approach to authentication seems to work, it’s not terribly secure, as credentials stored as strings can easily be compromised by curious hackers with the right tools and training. As a best practice, passwords should NEVER be stored directly in the app.

To that end, you’ll employ the Keychain to store the password. Check out Chris Lowe’s Basic Security in iOS 5 – Part 1 tutorial for the lowdown on how the Keychain works.

The next step is to add a Keychain wrapper class to your app, however it’s in Objective-C, so you’ll also need to add a bridging header to be able to access the Objective-C class from within Swift.

Rapper? No. Wrapper.

You can download KeychainWrapper here; this wrapper comes from Apple’s Keychain Services Programming Guide.

Once downloaded, unzip the archive and drag KeychainWrapper.h and KeychainWrapper.m into the project, like so:

dragging in KeychainWrapper

When prompted, make sure that Copy items if needed is checked and the TouchMeIn target is checked as well:

Copy files if needed

Since you’re adding an Objective-C file to a Swift project, Xcode will offer to create the bridging header file – click Yes:

Screen Shot 2014-12-30 at 3.20.07 AM

This creates a bridging header named TouchMeIn-Bridging-Header.h. You’ll add the Objective-C headers to this file so they can be accessed by your Swift app.

To check that the bridging header is properly setup, select TouchMeIn in the Project Navigator. Then navigate to the Build Settings. In the search bar, enter Swift Compiler, then locate the Objective-C Bridging Header field, which should now contain TouchMeIn/TouchMeIn-Bridging-Header.h, as shown below:

Screen Shot 2014-12-30 at 3.30.35 AM

Note: You can read more about Bridging Headers in Porting Your App to the iPhone 6, iPhone 6 Plus and iOS 8: Top 10 Tips. To learn more about using Swift and Objective-C together, have a look at Apple’s Using Swift with Cocoa and Objective-C guide.

Open TouchMeIn-Bridging-Header.h and import the Keychain wrapper at the top of the file:

#import "KeychainWrapper.h"

Build and run to make sure you have no errors. All good? Great — now you can leverage the Keychain from within your app.

Note: If you do see some errors then please refer to Apple’s Using Swift with Cocoa and Objective-C guide for help on how to get them resolved.

Keychain, Meet Password. Password, Meet Keychain

To use the Keychain, you first need to store a username and password. After that, you check the user-provided credentials against the Keychain to see if they match.

You’ll need to track whether the user has already created some credentials so that you can change the text on the Login button from “Create” to “Login”. You’ll also store the username in the user defaults so you can perform this check without hitting the Keychain each time.

Open up LoginViewController.swift and delete the following lines:

let usernameKey = "batman"
let passwordKey = "Hello Bruce!"

In their place, add the following:

let MyKeychainWrapper = KeychainWrapper()
let createLoginButtonTag = 0
let loginButtonTag = 1
 
@IBOutlet weak var loginButton: UIButton!

MyKeychainWrapper holds a reference to the Objective-C KeychainWrapper class. The next two constants will be used to determine if the Login button is being used to create some credentials, or to log in; the loginButton outlet will be used to update the title of the button depending on that same state.

Open Main.storyboard and Ctrl-drag from the Login View Controller to the Login button, as shown below:

connect loginAction 1

From the resulting popup, choose loginButton:

choose loginAction

Next, you need to handle the following two cases for when the button is tapped: if the user hasn’t yet created their credentials, the button text will show “Create”, otherwise the button will show “Login”. You also need to check the entered credentials against the Keychain.

Open LoginViewController.swift and replace the code in loginAction(_:) with the following:

@IBAction func loginAction(sender: AnyObject) {
 
  // 1.
  if (usernameTextField.text == "" || passwordTextField.text == "") {
    var alert = UIAlertView()
    alert.title = "You must enter both a username and password!"
    alert.addButtonWithTitle("Oops!")
    alert.show()
    return;
  }
 
  // 2.
  usernameTextField.resignFirstResponder()
  passwordTextField.resignFirstResponder()
 
  // 3.
  if sender.tag == createLoginButtonTag {
 
    // 4.
    let hasLoginKey = NSUserDefaults.standardUserDefaults().boolForKey("hasLoginKey")
    if hasLoginKey == false {
      NSUserDefaults.standardUserDefaults().setValue(self.usernameTextField.text, forKey: "username")
    }
 
    // 5.
    MyKeychainWrapper.mySetObject(passwordTextField.text, forKey:kSecValueData)
    MyKeychainWrapper.writeToKeychain()
    NSUserDefaults.standardUserDefaults().setBool(true, forKey: "hasLoginKey")
    NSUserDefaults.standardUserDefaults().synchronize()
    loginButton.tag = loginButtonTag
 
    performSegueWithIdentifier("dismissLogin", sender: self)
  } else if sender.tag == loginButtonTag {
    // 6.
    if checkLogin(usernameTextField.text, password: passwordTextField.text) {
      performSegueWithIdentifier("dismissLogin", sender: self)
    } else {
      // 7.
      var alert = UIAlertView()
      alert.title = "Login Problem"
      alert.message = "Wrong username or password."
      alert.addButtonWithTitle("Foiled Again!")
      alert.show()
    }
  }
}

Here’s what’s happening in the code:

  1. If either the username or password is empty, then present an alert to the user and return from the method.
  2. Dismiss the keyboard if it’s visible.
  3. If the login button’s tag is createLoginButtonTag, then proceed to create a new login.
  4. Next, you read hasLoginKey from NSUserDefaults which indicates whether a password has been saved to the Keychain. If the username field is not empty and hasLoginKey indicates no login has already been saved, then you save the username to NSUserDefaults.
  5. You then use mySetObject and writeToKeychain to save the password text to Keychain. You then set hasLoginKey in NSUserDefaults to true to indicate that a password has been saved to the keychain. You set the login button’s tag to loginButtonTag so that it will prompt the user to log in the next time they run your app, rather than prompting the user to create a login. Finally, you dismiss loginView.
  6. If the user is logging in (as indicated by loginButtonTag), you call checkLogin(_:password:) to verify the user-provided credentials; if they match then you dismiss the login view.
  7. If the login authentication fails, then present an alert message to the user.
Note: Why not just store the password along with the username in NSUserDefaults? That would be a bad idea because values stored in NSUserDefaults are persisted using a plist file. This is essentially an XML file that resides in the app’s Library folder, and is therefore readable by anyone with physical access to the device. The Keychain, on the other hand, uses the Triple Digital Encryption Standard (3DES) to encrypt its data.

Next, replace the implementation of checkLogin(_:password:) with the following:

func checkLogin(username: String, password: String ) -> Bool {
  if password == MyKeychainWrapper.myObjectForKey("v_Data") as NSString &&
    username == NSUserDefaults.standardUserDefaults().valueForKey("username") as? NSString {
      return true
  } else {
    return false
  }
}

This checks that the username entered matches the one stored in NSUserDefaults and that the password matches the one stored in the Keychain.

Now you need to set the button title and tags appropriately depending on the state of hasLoginKey.

Add the following to viewDidLoad(), just below the call to super:

// 1.
let hasLogin = NSUserDefaults.standardUserDefaults().boolForKey("hasLoginKey")
 
    // 2.
    if hasLogin {
      loginButton.setTitle("Login", forState: UIControlState.Normal)
      loginButton.tag = loginButtonTag
      createInfoLabel.hidden = true
    } else {
      loginButton.setTitle("Create", forState: UIControlState.Normal)
      loginButton.tag = createLoginButtonTag
      createInfoLabel.hidden = false
    }
 
// 3.
let storedUsername : NSString? = NSUserDefaults.standardUserDefaults().valueForKey("username") as? NSString
usernameTextField.text = storedUsername

Taking each numbered comment in turn:

  1. You first check hasLoginKey to see if you’ve already stored a login for this user.
  2. If so, change the button’s title to Login, update its tag to loginButtonTag, and hide createInfoLabel, which contains the informative text “Start by creating a username and password“. If you don’t have a stored login for this user, set the button label to Create and display createInfoLabel to the user.
  3. Finally, you set the username field to what is saved in NSUserDefaults to make logging in a little more convenient for the user.

Build and run. Enter a username and password of your own choosing, then tap Create.

Note: If you forgot to connect the loginButton IBOutlet then you might see the error Fatal error: unexpectedly found nil while unwrapping an Optional value”. If you do, connect the outlet as described in the relevant step above.

Now tap Logout and attempt to login with the same username and password – you should see the list of notes appear.

Tap Logout and try to log in again – this time, use a different password and then tap Login. You should see the following alert:

wrong password

Congratulations – you’ve now added authentication use the Keychain. Next up, Touch ID.

Touching You, Touching Me

Note: In order to test Touch ID, you’ll need to run the app on a physical device that supports Touch ID. At the time of this writing, that includes the iPhone 5s, 6, 6+, and iPad Air 2 and iPad mini 3.

In this section, you’ll add Touch ID to your project in addition to using the Keychain. While Keychain isn’t necessary for Touch ID to work, it’s always a good idea to implement a backup authentication method for instances where Touch ID fails, or for users that don’t have a Touch ID compatible device.

Open Images.xcassets.

Download the images assets for this part of the project here. Unzip them and open the resulting folder. Locate Touch-icon-lg.png, Touch-icon-lg@2x.png, and Touch-icon-lg@3x.png, select all three and drag them into Images.xcassets so that Xcode they’re the same image, only with different resolutions:

drag to assets

Open up Main.storyboard and drag a Button from the Object Library onto the Login View Controller Scene, just below the label.

Use the Attributes Inspector to adjust the button’s attributes as follows:

  • Set Type to Custom.
  • Leave the Title empty.
  • Set Image to Touch-icon-lg.

When you’re done, the button’s attributes should look like this:

Screen Shot 2014-12-22 at 3.05.58 AM

Now adjust your button in the Size Inspector as shown below:

  • Show should be Frame Rectangle
  • X should be 267
  • Y should be 341
  • Width should be 67
  • Height should be 66

When you’re done, the Size Inspector should look like the following:

touch icon placement

Ensure your new button is selected, then click the pin button in the layout bar at the foot of the storyboard canvas and set the constraints as below:

contraints on Touch button

  • Top Space should be 16.5
  • Width should be 67
  • Height should be 67

Next, click the align button and check Horizontal Center in Container:

Horizontal center

Finally, click the Resolve Auto Layout Issues icon and select Selected Views\Update Frames, as shown below:

constraints 3

This updates the button’s frame to match its new constraints.

Your view should now look like the following:

login view

Still in Main.storyboard, open the Assistant Editor and make sure LoginViewController.swift is showing.

Now, Ctrl-drag from the button you just added to LoginViewController.swift, just below the other properties, like so:

connect touchButton

In the popup enter touchIDButton as the Name and click Connect:

naming outlet

This creates an outlet that you will use to hide the button on devices that don’t have Touch ID available.

Now you need to add an action for the button.

Ctrl-drag from the same button to LoginViewController.swift to just above checkLogin(_:password:):

connect touch action

In the popup, change Connection to Action, set Name to touchIDLoginAction, and click Connect.

Build and run to check for any errors. You can still build for the Simulator at this point since you haven’t yet added support for Touch ID. You’ll take care of that now.

Adding Local Authentication

Implementing Touch ID is as simple as importing the Local Authentication framework and calling a couple of simple yet powerful methods.

Here’s what the documentation the Local Authentication documentation has to say:

“The Local Authentication framework provides facilities for requesting authentication from users with specified security policies.”

The specified security policy in this case will be your user’s biometrics — A.K.A their fingerprint! :]

Open up LoginViewController.swift and add the following import, just below the CoreData import:

import LocalAuthentication

Now you’ll need a reference to the LAContext class, as well as an error property.

Add the following code below the rest of your properties:

var error : NSError?
var context = LAContext()

You will use error in a few places later on in this tutorial; context references an authentication context, which is the main player in Local Authentication.

At the bottom of viewDidLoad() add the following:

touchIDButton.hidden = true
 
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
  touchIDButton.hidden = false
}

Here you use canEvaluatePolicy(_:error:) to check whether the device can implement Touch ID authentication. If so, then show the Touch ID button; if not, then leave it hidden.

Build and run on the Simulator; you’ll see the Touch ID logo is hidden. Now build and run on your physical Touch ID-capable device; you’ll see the Touch ID button is displayed.

Putting Touch ID to Work

Still working in LoginViewController.swift, replace the entire touchIDLoginAction(_:) method with the following:

@IBAction func touchIDLoginAction(sender: AnyObject) {
  // 1.
  if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
 
    // 2.
    context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: "Logging in with Touch ID",
      reply: { (success: Bool, error: NSError! ) -> Void in
 
        // 3.
        dispatch_async(dispatch_get_main_queue(), {
          if success {
            self.performSegueWithIdentifier("dismissLogin", sender: self)
          }
 
          if error != nil {
 
            var message: NSString
            var showAlert: Bool
 
            // 4.
            switch(error.code) {
            case LAError.AuthenticationFailed.rawValue:
              message = "There was a problem verifying your identity."
              showAlert = true
            case LAError.UserCancel.rawValue:
              message = "You pressed cancel."
              showAlert = true
            case LAError.UserFallback.rawValue:
              message = "You pressed password."
              showAlert = true
            default:
              showAlert = true
              message = "Touch ID may not be configured"
            }
 
            var alert = UIAlertView()
            alert.title = "Error"
            alert.message = message
            alert.addButtonWithTitle("Darn!")
            if showAlert {
              alert.show()
            }
 
          }
        })
 
    })
  } else {
    // 5.
    var alert = UIAlertView()
    alert.title = "Error"
    alert.message = "Touch ID not available"
    alert.addButtonWithTitle("Darn!")
    alert.show()
  }
}

Here’s what’s going on in the code above:

  1. Once again, you’re using canEvaluatePolicy(_:error:) to check whether the device is Touch ID capable.
  2. If the device does support Touch ID, you then use evaluatePolicy(_:localizedReason:reply:) to begin the policy evaluation — that is, prompt the user for Touch ID authentication. evaluatePolicy(_:localizedReason:reply:) takes a reply block that is executed after the evaluation completes.
  3. Inside the reply block, you handling the success case first. By default, the policy evaluation happens on a private thread, so your code jumps back to the main thread so it can update the UI. If the authentication was successful, you call the segue that dismisses the login view.
  4. Now for the “failure” cases. You use a switch statement to set appropriate error messages for each error case, then present the user with an alert view.
  5. If canEvaluatePolicy(_:error:) failed, you display a generic alert. In practice, you should really evaluate and address the specific error code returned, which could include any of the following:
    • LAErrorTouchIDNotAvailable: the device isn’t Touch ID-compatible.
    • LAErrorPasscodeNotSet: there is no passcode enabled as required for Touch ID
    • LAErrorTouchIDNotEnrolled: there are no fingerprints stored.

iOS responds to LAErrorPasscodeNotSet and LAErrorTouchIDNotEnrolled on its own with relevant alerts.

Build and run on a physical device and test logging in with Touch ID.

Since LAContext handles most of the heavy lifting, it turned out to be relatively straight forward to implement Touch ID. As a bonus, you were able to have Keychain and Touch ID authentication in the same app, to handle the event that your user doesn’t have a Touch ID-enabled device.

In the third and final part of this tutorial, you’ll use 1Password’s iOS 8 extension to store and retrieve login information, along with other sensitive user data.

1Password To Rule Them All

The password manager 1Password from Agilebits runs on iOS, OS X, Windows, and Android. It stores login credentials, software licenses and other sensitive information in a virtual vault locked with a PBKDF2-encrypted master password. In this section, you’ll learn how to store user credentials in 1Password via the extension, and later retrieve them in order to authenticate a user.

Note: You’ll need 1Password installed on a physical device in order to follow along with the next section.

To begin with, you need to add some new graphics that will be used with a 1Password-specific button.

Open Images.xcassets. Then, amongst the assets you downloaded earlier, locate the following three files:

  • onepassword-button.png
  • onepassword-button@2x.png
  • onepassword-button@3x.png

Select them all and drag them as one into Images.xcassets. Then, find the three files:

  • onepassword-button-green.png
  • onepassword-button-green@2x.png
  • onepassword-button-green@3x.png

And again, select them all and drag them as one into Images.xcassets.

Open Main.storyboard and drag a Button from the Object Library on to the Login View Controller Scene, just below the Touch ID button. Using the Attributes Inspector, adjust the button’s attributes as follows:

  • Set Type to Custom
  • Leave Title empty
  • Set Image to onepassword-button

The Attributes Inspector should now look like the following:

Screen Shot 2014-12-22 at 6.40.47 PM

Using the Size Inspector, adjust the placement and size as per the following:

  • Set X to 287
  • Set Y to 426
  • Set Width to 27
  • Set Height to 33

You can check your size and placement against the image below:

one password button attributes

With the button still selected, click pin in the layout bar at the bottom of the storyboard canvas and set the button’s Top Space to 21, Width to 27, and Height to 33:

set width and height

Next, click align in the layout bar and check Horizontal Center in Container:

horizontally center

Still working with Main.storyboard, open the Assistant Editor, which itself should open up LoginViewController.swift – if it doesn’t, select the file from the jump-bar. Now Ctrl-drag from the button to LoginViewController.swift, just below the other properties, as shown below:

connect onepass outlet

Enter onepasswordSigninButton as the Name in the popup and click Connect:
name one pass button

This creates an IBOutlet that you will use to change the 1Password button image depending on whether it’s available or not.

Next, add an action for onepasswordSigninButton. Ctrl-drag from the button to LoginViewController.swift, just above checkLogin(_:password:):

connect action one pass

Change the Connection type to Action. Set the Name to canUse1Password and leave Arguments set to Sender and click Connect.

can use 1Password

Build and run. You should see your new 1Password button displayed, as shown below:

with one pass button

An Agile Extension

Now that the interface is laid out, you can start implementing the 1Password support.

Head to the 1Password Extension repository on GitHub and click Download ZIP, as indicated below:

Screen Shot 2014-12-30 at 7.03.33 PM

Unzip the downloaded file. Open the folder and drag the OnePasswordExtension.h and OnePasswordExtension.m files into your project, like so:

add one pass files

Make sure that Copy items if needed and the TouchMeIn target are both checked.

Recall that when you add Objective-C files to a Swift project, Xcode offers to make a bridging header. Since you’ve already created a bridging header in an earlier step you can simply add the 1Password header to that.

Open TouchMeIn-Bridging-Header.h and add the following import:

#import "OnePasswordExtension.h"

Open LoginViewController.swift and add the following import to the top of the file:

import Security

This simply imports the Security framework, which is required by the 1Password extension.

Now add the following properties to the top of LoginViewController:

let MyOnePassword = OnePasswordExtension()
var has1PasswordLogin: Bool = false

The former holds a reference to the main class of the 1Password extension, and the latter tracks whether a 1Password record has already been created; you set it to default as false as there won’t be one on first run.

Next update viewDidLoad() by replacing the entire if haslogin block with the following:

if hasLogin {
  loginButton.setTitle("Login", forState: UIControlState.Normal)
  loginButton.tag = loginButtonTag
  createInfoLabel.hidden = true
  onepasswordSigninButton.enabled = true
} else {
  loginButton.setTitle("Create", forState: UIControlState.Normal)
  loginButton.tag = createLoginButtonTag
  createInfoLabel.hidden = false
  onepasswordSigninButton.enabled = false
}

This disables the onepasswordSigninButton until the initial username and password have been stored in the Keychain.

Now you’ll need to access the 1Password Extension and test whether the iOS app is installed and available, and enable the 1Password button if it is.

Add the following to viewDidLoad():

onepasswordSigninButton.hidden = true
var has1Password = NSUserDefaults.standardUserDefaults().boolForKey("has1PassLogin")
 
if MyOnePassword.isAppExtensionAvailable() {
  onepasswordSigninButton.hidden = false
  if has1Password {
    onepasswordSigninButton.setImage(UIImage(named: "onepassword-button") , forState: .Normal)
  } else {
    onepasswordSigninButton.setImage(UIImage(named: "onepassword-button-green") , forState: .Normal)
  }
}

This hides onepasswordSigninButton by default, and then shows it only if the extension is installed. You then set has1Password from the has1PassLogin key stored in NSUserDefaults to indicate whether a 1Password record has already been created, and set the button image accordingly.

Next, modify canUse1Password to output a simple message to the console when you tap the 1Password button:

@IBAction func canUse1Password(sender: AnyObject) {
  println("one password")
}

Build and run your app on both the simulator and your physical device with 1Password installed. The 1Password button should be hidden on the simulator, and it should show in green on your physical device. Tap the 1Password button and the following should print to the console:

one password

Tapping into the Power of 1Password

There are a few methods you’ll use in the 1Password extension: storeLoginForURLString(_:loginDetails:passwordGenerationOptions:forViewController:sender:) lets you create some login credentials, while findLoginForURLString(_:forViewController:sender:completion:) retrieves the credentials from the 1Password vault.

Open LoginViewController.swift, and add the following new method to it:

func saveLoginTo1Password(sender: AnyObject) {
  // 1.
  var newLoginDetails : NSDictionary = [
    AppExtensionTitleKey: "Touch Me In",
    AppExtensionUsernameKey: usernameTextField.text,
    AppExtensionPasswordKey: passwordTextField.text,
    AppExtensionNotesKey: "Saved with the TouchMeIn app",
    AppExtensionSectionTitleKey: "Touch Me In app",
  ]
 
  // 2.
  var passwordGenerationOptions : NSDictionary = [
    AppExtensionGeneratedPasswordMinLengthKey: 6,
    AppExtensionGeneratedPasswordMaxLengthKey: 10
  ]
 
  // 3.
  MyOnePassword.storeLoginForURLString("TouchMeIn.Login", loginDetails: newLoginDetails, 
    passwordGenerationOptions: passwordGenerationOptions, forViewController: self, sender: sender) 
      { (loginDict : [NSObject : AnyObject]!, error : NSError!) -> Void in
 
    // 4.
    if loginDict == nil {
      if ((Int32)(error.code) != AppExtensionErrorCodeCancelledByUser) {
        println("Error invoking 1Password App Extension for login: \(error)")
      }
      return
    }
 
    // 5.
    var foundUsername = loginDict["username"] as String
    var foundPassword = loginDict["password"] as String
 
    // 6.
    if self.checkLogin(foundUsername, password: foundPassword) {
      self.performSegueWithIdentifier("dismissLogin", sender: self)
    } else {
      // 7.
      var alert = UIAlertView()
      alert.title = "Error"
      alert.message = "The info in 1Password is incorrect"
      alert.addButtonWithTitle("Darn!")
      alert.show()
    }
    // TODO - add NSUserDefaults check
  }
}

That’s a fair bit of code; here’s what’s happening in detail:

  1. You create a dictionary with the username and password provided by the user, as well as the keys required by the 1Password extension. This dictionary will be passed to the extension when saving the password.
  2. You add some optional parameters which are used by 1Password to generate a password; here you’re simply stating the minimum and maximum lengths of the password. The extension will provide its own defaults for these values if you don’t configure them here.
  3. You provide a string — TouchMeIn.Login — to identify the saved record; you also pass in newLoginDetails and passwordGenerationOptions, the two dictionaries you created in steps 1 and 2 respectively.
  4. If all goes well in step 3, you’ll receive a loginDict in return, which contains the username and password; if not, you’Il print an error to the console and return.
  5. You extract the username and password from loginDict.
  6. You call checkLogin(_:password:) with the username and password you obtained in the previous step; if the credentials match the information stored in the Keychain, then you log the user in and dismiss the login view.
  7. If the login information is incorrect, you display an alert with an error message.
Note: 1Password uses a string – URLString – as the key of the record in the vault. You can use any unique string, and although it’s tempting to use your Bundle ID, you should really create a unique human-friendly string for this purpose instead as the string will be visible to the user in the 1Password app. In your case, you use the string TouchMeIn.Login

Now you need to check whether the username is stored in the NSUserDefaults. If not, then you’ll be defensive in your coding and set the value from the text field.

Find the following line in saveLoginTo1Password(_:):

// TODO - add NSUserDefaults check

…and replace it with the following:

if NSUserDefaults.standardUserDefaults().objectForKey("username") != nil {
  NSUserDefaults.standardUserDefaults().setValue(self.usernameTextField.text, forKey: "username")
}

This sets the username key in the user defaults to the value of the username text field if it’s not already set.

Add the following code to the bottom of saveLoginTo1Password(_:):

NSUserDefaults.standardUserDefaults().setBool(true, forKey: "has1PassLogin")
NSUserDefaults.standardUserDefaults().synchronize()

This updates has1PassLogin to indicate that the user created a 1Password record. It’s a simple way to prevent the creation of a second entry in the vault. You’ll check has1PassLogin later on as well before attempting to save credentials to 1Password.

Now you need a way to look up the stored 1Password login information for your app.

Add the following method just above checkLogin(_:password:):

@IBAction func findLoginFrom1Password(sender: AnyObject) {
 
  MyOnePassword.findLoginForURLString( "TouchMeIn.Login",
    forViewController: self,
    sender: sender,
    completion: { (loginDict : [NSObject: AnyObject]!, error : NSError!) -> Void in
      // 1.
      if loginDict == nil {
        if (Int32)(error.code) != AppExtensionErrorCodeCancelledByUser {
          println("Error invoking 1Password App Extension for find login: \(error)")
        }
        return
      }
 
      // 2.
      if NSUserDefaults.standardUserDefaults().objectForKey("username") == nil {
        NSUserDefaults.standardUserDefaults().setValue(loginDict[AppExtensionUsernameKey],
          forKey: "username")
        NSUserDefaults.standardUserDefaults().synchronize()
      }
 
      // 3.
      var foundUsername = loginDict["username"] as String
      var foundPassword = loginDict["password"] as String
 
      if self.checkLogin(foundUsername, password: foundPassword) {
        self.performSegueWithIdentifier("dismissLogin", sender: self)
      } else {
        var alert = UIAlertView()
        alert.title = "Error"
        alert.message = "The info in 1Password is incorrect"
        alert.addButtonWithTitle("Darn!")
        alert.show()
      }
  })
}

This function uses the 1Password method findLoginForURLString(_:forViewController:sender:completion:) to look up the vault record for the passed-in URLString, which in this case is TouchMeIn.Login; it then executes a completion block on success which has access to the returned dictionary.

Here’s a closer look at the code:

  1. If the loginDict is nil something has gone wrong, so you print an error message to the console based on the provided NSError and return.
  2. Here you check if the username is already stored in NSUserDefaults. If it isn’t, you add it from the results returned by 1Password.
  3. This passes the retrieved values into checkLogin(_:password:). If the login matches the one stored in Keychain, you dismiss the login controller. However, if there’s a mismatch, then you display an alert to the user.

Now you’ll finish implementing canUse1Password(_:) to call either saveLoginTo1Password(_:) on first run, or findLoginFrom1Password(_:) if has1PassLogin indicates there’s stored login information for this user.

Replace the existing canUse1Password implementation with the following:

@IBAction func canUse1Password(sender: AnyObject) {
  if NSUserDefaults.standardUserDefaults().objectForKey("has1PassLogin") != nil {
    self.findLoginFrom1Password(self)
  } else {
    self.saveLoginTo1Password(self)
  }
}

Build and run. Tap the 1Password button to store your login credentials. A 1Password icon will appear in the Action sheet – tap it to launch the extension. The extension’s view is presented modally and you can login with your system password, or Touch ID if you’re using your physical device. 1Password then presents the record for your app, using the identifier you set up earlier.

You’ll see screens similar to those shown below. By default 1Password will generate a password. For this test, be sure to enter the same credentials you used to set up your login in the Keychain in earlier runs. Once that’s complete, hit the Done button. The last screenshot shows what you’ll see if you later edit the record – which shows that it has the vault name, URL and notes you provided when creating the record.

1Pass-create

Now that you’ve stored the login, subsequent taps of the 1Password button will retrieve the vault credentials and log you in if the credentials match the Keychain. Try to log in again and you should see the screens as shown below:

1Pass-login

That’s it — you’ve now implemented support for the 1Password extension – a powerful way to improve your user login experience for 1Password users!

Where to Go from Here?

You can download the completed sample application from this tutorial here.

The LoginViewController you’ve created in this tutorial provides a jumping-off point for any app that needs to manage user credentials.

You can also add a new view controller, or modify the existing LoginViewController, to allow the user to change their password from time to time. This isn’t necessary with Touch ID, since the user’s biometrics probably won’t change much in their lifetime! :] However, you could create a way to update the Keychain and then update 1Password accordingly; you’d want to prompt the user for their current password before accepting their modification.

As always, if you have any questions or comments on this tutorial, feel free to join the discussion below!

Securing iOS User Data: The Keychain, Touch ID, and 1Password is a post from: Ray Wenderlich

The post Securing iOS User Data: The Keychain, Touch ID, and 1Password appeared first on Ray Wenderlich.

Video Tutorial: Intro to Auto Layout Part 2: Constraints II

Sprite Kit Animations and Texture Atlases in Swift

$
0
0
Smoky says: Only you can start this bear!

Smoky says: Only you can start this bear!

Update note: Tutorial Team member Tony Dahbura has ported this tutorial from Objective-C to Swift. We hope you enjoy!

In this Swift Sprite Kit tutorial, you will learn how to create a simple animation of a bear walking using the new Swift language and Sprite Kit framework.

You’ll also learn how to make the animation efficient by using texture atlases, how to make your bear move in response to touch events, and how to change the direction the bear faces based on where the bear is moving.

This tutorial assumes you at least know the basics of Sprite Kit. If you are completely new to Sprite Kit, be sure to check out our Sprite Kit Swift Tutorial for Beginners first.

Let’s get started!

Create the Swift Project

Let’s start by creating a Xcode skeleton for our project. Start up Xcode, select File\New\Project…, choose the iOS\Application\Game template and click Next.

001_New_Game-480x282

Enter AnimatedBearSwift for the Product Name, Swift for Language SpriteKit for Game Technology, iPad for Devices, and click Next:

002_Project_Settings

Choose somewhere on your drive to save the project, and click Create.

Now that your project is open, select one of the iPad simulators and build and run to check out the starter project. After a brief splash screen, you should see the following:

initial_run

If you tap on the screen you will see spaceships that start spinning. Spaceships! I thought we were doing a bear? Let’s fix that next, first let’s get some art for this project.

You are going to use some already designed animation art courtesy of GameArtGuppy. Download the art from BearImages Art.

Examples of the bear images you will be using.

Examples of the bear images you will be using.

These images are saved in the maximum required resolution – for an iPad with a retina display (2X) as well as non-retina versions (1x). These files are named like bear1@2x~ipad.png and bear1~ipad.png.

You could just add these directly to your Swift Sprite Kit project at this point and create an animation based on these individual images. However, there’s another way to create animations that is more efficient – by using a texture atlas.

Texture Atlas and Bears, Oh My!

If you haven’t used a texture atlas yet, think of them as gigantic images that you put your individual animation images within. They come with a file that specifies the boundaries for each individual sprite so you can pull them out when you need them within the code.

The reason why these are such a good idea to use is because Sprite Kit and the graphics engine is optimized for them. If you use sprites within a texture atlas properly, rather than making one OpenGL ES draw call per bear sprite it just makes one per texture atlas sheet containing every bear image.

In short – it’s faster, especially when you have a lot of sprites!

Xcode will automatically generate a file that specifies the boundaries for each individual sprite so you can pull them out when you need them within the code as well as creating the single image file with the images packed into it. This is all handled automatically for you at build time.

Note: When working with a texture atlas and things seem to not be current (wrong images etc) then you should do a clean on your project to force the texture atlas to recompile by selecting Product\Clean in Xcode.

Creating a folder for a texture atlas is as simple as placing your image files in a folder and appending .atlas on the end. Xcode will notice the .atlas extension and automatically combine the images into a texture atlas for you!

The artwork you just downloaded has a folder called BearImages.atlas created with the different resolutions ready to go. This folder just contains copies of all the graphics art from the other two folders.

Load the bear art into your application by dragging the folder called BearImages.atlas onto the AnimatedBearSwift icon in your Xcode view:

Drag BearImages Atlas Folder into Xcode

After releasing from the drag, a dialog will appear giving the options on how to add this to your project, ensure that Copy items into destination group’s folder, Create groups for any added folder, and the AnimatedBear options are checked, and click Finish:

Select Proper Options After Drag

If you expand the folder in Xcode it should look like this:

Assets Folder View in Xcode

Lastly, the template provided to you by Xcode has two issues. First, it’s set up the game to be Portrait, but you want landscape. Second, it is currently using Sprite Kit’s scene editor, which you don’t need for this tutorial. Let’s fix these issues.

First, open your target setting by clicking your AnimatedBearSwift project in the Project Navigator, then selecting the AnimatedBearSwift target. In the Deployment Info section, uncheck Portrait and Upside Down so only Landscape Left and Landscape Right are checked, as shown below:

Set Device Orientation

Second, delete GameScene.sks from the project and choose Move to Trash when prompted. This file allows you to lay out sprites and other components of a scene visually, however for our bear game it’s easier to animate him programmatically, so you don’t need it.

Now let’s get that bear moving!

A Simple Animation

You’re going to start just by plopping the bear in the middle of the screen and looping the animation so he moves forever, just to make sure things are working.

Open GameViewController.swift and replace the contents with the following:

import UIKit
import SpriteKit
 
class GameViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    let scene = GameScene(size: view.bounds.size)
    let skView = view as SKView
    skView.showsFPS = true
    skView.showsNodeCount = true
    skView.ignoresSiblingOrder = true
    scene.scaleMode = .ResizeFill
    skView.presentScene(scene)
  }
 
  override func prefersStatusBarHidden() -> Bool {
    return true
  }
}

You won’t need the starter code generated by Xcode so this implementation has just what you need to start.

GameViewController is a normal UIViewController, except that its root view is a SKView, which is a view that contains a Sprite Kit scene.

Here, you’ve implemented viewDidLoad() to create a new instance of the GameScene on startup, with the same size of the view itself. Because you are using Sprite Kit the rest of your code will be in GameScene.

You’re also overriding prefersStatusBarHidden() to hide the status bar to focus all the attention on the bear.

Switch over to GameScene.swift and replace the contents with the following:

import SpriteKit
 
var bear : SKSpriteNode!
var bearWalkingFrames : [SKTexture]!
 
class GameScene: SKScene {
  override func didMoveToView(view: SKView) {
    /* Setup your scene here */
    backgroundColor = (UIColor.blackColor())
  }
 
  override func update(currentTime: CFTimeInterval) {
     /* Called before each frame is rendered */
  }
}

At this point you’ve just emptied out the project template to create a nice blank slate (and defined a few variables you’ll need later). Build and run to make sure everything builds OK – you should see a blank black screen.

iOS Simulator Screen Shot 16 Nov, 2014, 9.41.45 PM

Setting up the Texture Atlas

First, you’ll need to load the texture atlas and set up the frames to animate them. Add the following code to the end of didMoveToView:

let bearAnimatedAtlas = SKTextureAtlas(named: "BearImages")
var walkFrames = [SKTexture]()
 
let numImages = bearAnimatedAtlas.textureNames.count
for var i=1; i<=numImages/2; i++ {
  let bearTextureName = "bear\(i)"
  walkFrames.append(bearAnimatedAtlas.textureNamed(bearTextureName))
}
 
bearWalkingFrames = walkFrames

First, you’re loading the atlas from the data in the application bundle. Sprite Kit will automatically look for the correct file based on the resolution of the device. walkFrames is an array of SKTexture objects and will store each frame of the bear animation.

Then you create the list of frames by looping through your image’s names (they are named with a convention of bear1.png -> bear8.png) and try to find a sprite frame by that name in the texture atlas. Notice the numImages variable being divided by 2…Why do you only need to loop through half of the images in the texture atlas?

Solution Inside: Tell Me! SelectShow>

Next, add the following code directly after what you just added to the end of didMoveToView:

let firstFrame = bearWalkingFrames[0]
bear = SKSpriteNode(texture: firstFrame)
bear.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
addChild(bear)

Here, you’re grabbing the first frame and positioning it in the center of the screen to set up the start of the animation.

Next, add the following method to the class:

func walkingBear() {
  //This is our general runAction method to make our bear walk.
  bear.runAction(SKAction.repeatActionForever(
    SKAction.animateWithTextures(bearWalkingFrames,
      timePerFrame: 0.1,
      resize: false,
      restore: true)),
    withKey:"walkingInPlaceBear")
}

This action will cause the animation to begin with a 0.1 second delay between frames. The walkingInPlaceBear key identifies this particular action with a name in case you call this method again to restart the animation. This will be important later on to make sure animations are not stepping on each other. The withKey argument also provides a way to check on the animation to see if it is running.

The repeat action repeats whatever action it is provided forever, which results in the inner action animateWithTextures animating through the textures in the texture atlas in the order within the array.

Now all you need to do is call this method to kick off the animation! Add the following line to the end of didMoveToView:

walkingBear()

And that’s it! So build and run the project, and if all goes well you should see your bear happily strolling on the screen!

bearwalking

Changing Animation Facing Direction

Things are looking good – except you don’t want this bear meandering about on its own; that would be dangerous! It would be much better if you could control its direction by touching the screen to tell it which way to go. Following that, you’ll look at moving him all over the screen.

Still in GameScene.swift, add the following methods to the class:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
  /* Called when a touch begins */
}
 
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
  // Choose one of the touches to work with
  let touch = touches.anyObject() as UITouch
  let location = touch.locationInNode(self)
 
  var multiplierForDirection : CGFloat
  if (location.x <= CGRectGetMidX(self.frame)) {
    // walk left
    multiplierForDirection = 1.0
  } else {
    // walk right
    multiplierForDirection = -1.0
  }
 
  bear.xScale = fabs(bear.xScale) * multiplierForDirection
  walkingBear()
}

You’re only interested in when the user taps the screen, so you can leave touchesBegan empty – this is called when the user puts their finger down.

When they bring their finger off the screen and complete the tap action, that’s when touchesEnded is called. Here, the method determines which side of the screen was tapped – left or right of center. It uses this to determine which way the bear should face. The bear walks to the left by default, and you can change the direction of the bear sprite in Sprite Kit by multiplying the xScale by -1.0 to flip the image so the bear faces to the right.

Likewise, you just multiply the scale to 1.0 to go back to the default left-facing bear.

Build and run the project, and if all goes well you should see your bear happily strolling on the screen as usual. Tap on the left and right sides of the screen to get the bear to change directions.

leftrightmoves

Moving the Bear Around the Screen

Right now, it looks like the bear is walking in-place on a treadmill. The next step is to get him to meander to different places on the screen.

First, remove the call to walkingBear() at the end of didMoveToView. You want the bear to start moving when the user taps the screen, not automatically.

Next, add this helper method to the class:

func bearMoveEnded() {
  bear.removeAllActions()
}

This will remove all actions and stop the animation. You’ll call this later when the bear reaches the edge of the screen.

The changes to get the bear to move to where the user touches will all be in touchesEnded. Replace that method with the starter implementation below:

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
  // 1
  let touch = touches.anyObject() as UITouch
  let location = touch.locationInNode(self)
  var multiplierForDirection: CGFloat
 
  // 2
  let bearVelocity = self.frame.size.width / 3.0
 
  // 3
  let moveDifference = CGPointMake(location.x - bear.position.x, location.y - bear.position.y)
  let distanceToMove = sqrt(moveDifference.x * moveDifference.x + moveDifference.y * moveDifference.y)
 
  // 4
  let moveDuration = distanceToMove / bearVelocity
 
  // 5
  if (moveDifference.x < 0) {
    multiplierForDirection = 1.0
  } else {
    multiplierForDirection = -1.0
  }
 
  bear.xScale = fabs(bear.xScale) * multiplierForDirection
}

Here’s what’s going on step by step:

  1. Nothing new here – you just start by converting the touch point into local node coordinates using the usual method. You also declare a variable to hold the multiplierDirection as before to make the bear face in a different direction.
  2. Here you set up a velocity for the bear to move. You will estimate that it should take about 3 seconds for the bear to move the width of the screen. Since screen sizes can change from device to device you need to account for the width by asking the screen for its width, so since velocity is distance over time it would be the width pixels / 3 seconds.
  3. You need to figure out how far the bear needs to move along both the x and y axes, by simply subtracting the bear’s position from the touch location. Then you can calculate the distance the bear moves along a straight line (the hypotenuse of a triangle formed from the bear’s current position and the tap point). For a full tutorial on the math of game programming check out Trigonometry for Game Programming.
  4. Calculate how long it should take the bear to move along this length, by simply dividing the length moved by the desired velocity.
  5. Finally, you look to see if the bear is moving to the right or to the left by looking at the move difference. If it’s less than 0, he’s moving to the left; otherwise, to the right. You use the same technique of setting a multiplier for the xScale to flip the sprite.

Now all that’s left is to run the appropriate actions. That’s another substantial chunk of code; add the following to the end of touchesEnded:

// 1
if (bear.actionForKey("bearMoving") != nil) {
  //stop just the moving to a new location, but leave the walking legs movement running
  bear.removeActionForKey("bearMoving")
}
 
// 2
if (bear.actionForKey("walkingInPlaceBear") == nil) {
  //if legs are not moving go ahead and start them
  walkingBear()
}
 
// 3
let moveAction = (SKAction.moveTo(location, duration:(Double(moveDuration))))
 
// 4
let doneAction = (SKAction.runBlock({
            println("Animation Completed")
            self.bearMoveEnded()
        }))
 
// 5
let moveActionWithDone = (SKAction.sequence([moveAction, doneAction]))
bear.runAction(moveActionWithDone, withKey:"bearMoving")

Here’s what’s happening in this second half of touchesEnded:

  1. Stop any existing move action because you’re about to override any existing command to tell the bear to go somewhere else. Using the key value allows you start and stop any named portion of the animations running.
  2. Start the legs moving on your bear if he is not already moving his legs. This makes use of the earlier method which ensures you don’t start an animation running that was already running through the use of the key name.
  3. Create a move action specifying where to move and how long it should take.
  4. Create a done action that uses a block to call a method to stop the animation upon reaching the destination.
  5. Set up the two actions as a sequence of actions, which means they will run in order sequentially – the first runs to completion, then the second runs. Then you assign the action to the sprite and run it with the key “bearMoving”. Remember, you’re checking for this key at the top of the method to see if your bear is already en route to a new location in case the user taps a new location.

Note: Sprite Kit supports sequential and grouped actions. A sequential action means each specified action runs one after the other (sequentially). Some times you may want multiple actions to run at the same time. This is accomplished by specifying a grouped action where all the actions specified run in parallel.

You also have the flexibility to setup a series of sequential actions that contain grouped actions and vice versa! For more details see the Sprite Kit Programming Guide Chapter on Adding Actions to Nodes.

A lot of code – but was it worth it? Build and run to see! If all works well you should be able to tap the screen to move your bear around.

updated bear animation withmovement

Where To Go From Here?

Here is the final project with all of the code you’ve developed in the above Sprite Kit tutorial.

Here are a few ideas to try out for some more animation fun:

  • What if you wanted the bear to moonwalk? Hint: Try building the array of images backwards!
  • Try accelerating or slowing down the frame counts in walkingBear to see the effect.
  • Try animating multiple bears on the screen at the same time. Hint: Create multiple sprite nodes with actions.

At this point, you should know how to use animations in your projects. You’ve seen how to make loading frames efficient with sprites and texture atlases – have some fun and experiment by creating your own animations and seeing what you can do!

If you want to learn more about Sprite Kit and Swift, you should check out our book iOS Games by Tutorials. We’ll teach you everything you need to know – from physics, to tile maps, to particle systems, and even making your own 2D game art.

In the meantime, if you have any questions or comments, please join the forum discussion below!

Sprite Kit Animations and Texture Atlases in Swift is a post from: Ray Wenderlich

The post Sprite Kit Animations and Texture Atlases in Swift appeared first on Ray Wenderlich.

Video Tutorial: Intro to Auto Layout Part 3: Constraints III

Video Tutorial: Intro to Auto Layout Part 4: Intrinsic Content Size

Hello from RWDevCon!

$
0
0

Hello from RWDevCon!

Today the team and I are in Washington DC running RWDevCon: The Tutorial Conference.

This is our first time ever running this conference. Our goal is to create a conference that’s focused on interactive, hands-on tutorials, highly coordinated as a team – along with inspiration and friendship.

The conference sold out within about a month, and we have 180 attendees from across the world – countries like Finland, Germany, Iceland, Serbia, and more.

The conference is going great so far, so we thought it would be fun to post a quick status update and some pictures of the conference!

Opening Remarks

This morning, Vicki and I kicked off the conference with a discussion on Teamwork.

Vicki Wenderlich speaking at RWDevCon

Ray Wenderlich speaking at RWDevCon

This is a really important topic to me, because everything this site has accomplished over the past five years is a direct result of working together as a team, and I feel incredibly lucky to be a part of such a great group of developers.

Hands-On Tutorials

After the opening remarks, we moved onto hands-on tutorials.

Brian Moakley tutorial at RWDevCon

Each tutorial at RWDevCon is split into five parts:

  1. Opening (5 minutes). Get a birds-eye overview of the topic.
  2. Hands-on demo (30 minutes). Code along with the speaker in a live demo.
  3. Lab (15 minutes). Follow a step-by-step tutorial to learn a bit more.
  4. Challenge (15 minutes). See if you can do it on your own.
  5. Conclusion (3 minutes). Wrap up and get ready for the next tutorial.

Tammy Coron tutorial at RWDevCon

We have tutorials on Auto Layout, Adaptive Layout, WatchKit, App Architecture, Scene Kit, and more – you can see the full schedule for more details.

Friendship

To me, the most important part of any conference is the people – so we want to do all we can to help people meet each other, have fun, and make friends.

Last night, we had an opening reception for the conference where we got to make some initial introductions to attendees.

Opening reception at RWDevCon

Today, we had a nice lunch and a board game tournament!

Board Game Tournament at RWDevCon

This evening, we’re going to have a party at Penn Social, for a fun evening of food, games, and beer! :]

Inspiration

After a hard-days work on tutorials, we will be moving to “inspiration talks.”

Today, we’ll be having talks on “Craftmanship”, “Identity”, “Commitment”, and more.

A slide from Vicki's talk on Identity.

A slide from Vicki’s talk on Identity.

These are short 18-minute talks on topics designed to share some battle-won advice, or challenge you to try something new or embark on a new path you might not have thought about otherwise.

What about Recording?

A common question I get about RWDevCon is “Are you recording these talks?”

We will be recording inspiration talks – those will translate well to video, and we will release them for free on the site after the conference.

However, will not be recording the tutorials because they are really designed for an in-person hands-on experience and wouldn’t translate well to video.

Just like your app has one format for an iPhone and another for an iPad, we feel that in-person tutorials lead to one way of doing things, and online video tutorials lead to another way of doing things, and want to make sure we do the right thing for each format.

So you will see some of the same ideas from this conference in the video tutorial section on our site, but it will likely be done in a different format :]

Where To Go From Here?

That’s it for this status update – just wanted to let you see how things were going so far.

I’ll probably put out another post next week with a post-mortem on the entire experience. In the meantime, if you want see more RWDevCon pictures and news, check out our RWDevCon twitter account!

Also, if you didn’t make it this year, we hope to see you at another RWDevCon in the future! Be sure to sign up to our mailing list if you want to be notified if/when it runs again.

Anyway, just wanted to say hi and let you all know how it’s going so far – talk more later!

Hello from RWDevCon! is a post from: Ray Wenderlich

The post Hello from RWDevCon! appeared first on Ray Wenderlich.

RWDevCon with Ray Wenderlich and John Wilker – Podcast S03 E04

$
0
0
Find out how RWDevCon went with Ray Wenderlich and John Wilker!

Find out how RWDevCon went with Ray Wenderlich and John Wilker!

Welcome back to season 3 of the raywenderlich.com podcast!

In this episode, find out what happened at our RWDevCon Tutorial Conference with myself and John Wilker!

[Subscribe in iTunes] [RSS Feed]

Transcript

Mic: We’re here in the capital at the Liaison Capitol Hill, where RWDevCon just finished. We’re going to be joined by Ray and John Wilker, shortly. John Wilker is the organizer of 360iDev and has helped Ray put on RWDevCon. Before we introduce those guys into the conversation, I want to get your opinion. I know, obviously, Ray’s staring at you now and you also work for Ray, so putting Ray out of the picture, turn around if you need to, genuine impressions?

Jake: It was great. This was the first conference I’ve been a speaker at. I’ve been to just a handful of other conferences. I’ve been to a 360 a couple years back. I had a really good experience both as an attendee, just talking to other people and meeting people and talking about the topics of the conference. As a speaker, it was a really good experience. Ray put us all through our paces as speakers. He had a specific structure we should follow to prepare and it really helped me. I certainly would not have put the same amount of time and effort into it without some guidance, and just because I’d never done it before I didn’t know what to expect.

When I finally got up I was nervous but I felt prepared and I got a lot of good feedback on my session. I was pleased with my own performance and I had a great experience, so how about you? What was your impression? You’d spoken before, right?

Mic: I’ve only spoken once but this was a very different experience, because usually, in my experience with other conferences, the organizer will put out a call for talks, people will respond with ideas and that will be, once you’re accepted, that will be the last year hear from the organizer, until the day you turn up.

Whereas anybody that knows Ray and works for Ray, that was never going to be the case for RWDevCon. I think we had 2 to 2-1/2 months of really hardcore preparation to get ready for the conference but then I think that was evident in a lot of the feedback from talking personally to attendees and other raywenderlich.com team members was that that really increased the quality.

When you’re getting that kind of feedback it makes you feel better and gives you more confidence because my two sessions were the last two on the second day, so I had that huge buildup, then the more people were talking to me and giving me this really good feedback and knowing that all the effort was worth it, then yeah. Actually when I woke up yesterday morning I was excited not nervous, which is really unusual for me. Yeah, I really enjoyed the whole experience.

Jake: During the buildup in the amount of work, when I committed to this I didn’t realize and I thought I don’t know if I’m going to do this again. After I spoke and got the feedback, I thought I should speak more, this was great.

Mic: It’s definitely worth it, I think. I’ve learned a lot, but also in the run up to all this when we were … It’s hard to explain for those that weren’t involved in the speaker process to know just how much work has gone into this. We also got some training on how to be a better speaker, and I think putting the conference to one side, you’re still taking a lot that we all got a lot from that.

Jake: Yes.

Mic: Yeah, really, I’d definitely do it again. Yeah. Definitely got the confidence now to do it again. I think that’s probably a good time to introduce both Ray and John into the conversation. Guys, thanks for joining us.

John: My pleasure.

Ray: Yeah, my pleasure.

Mic: I think my first question to you, Ray, would be why. At what point did you think, “let’s put on a conference”?

Ray: It was about 360 or WWDC time, I was at all-conf with John and we were sitting together and I’d been thinking about running a conference because [00:04:00] Matt Galloway, who’s one of the tutorial team members, sent me an email and he’s like, “You know what, Ray? We should run a conference!” It was always in the back of my mind and I was like you know what? We really should because we have so many great authors and editors on our team we could put together some really great content and we could put our own spin on a conference because we’re so tutorial focused.

I thought what would it be like if we could have a conference focused on the hands-on aspect of it and that’s where we started. I talked with John and I was like hey, what do you think, could you team up with me on this because I’m not interested so much in the logistics, I’m interested in the content. That’s what gets me passionate and John is good at logistics because he’s done it for years. He was like, “Yeah!”

Mic: Did you already know John prior to this then?

Ray: Oh yeah, of course. John … He asked me many years back I think they had a last minute cancellation for an iOS 101 workshop at one of the 360iDev’s and it was 2 days before the conference and he emails me and he’s like, “Hey, do you think you could come down and give a day long workshop on iOS?” I guess he read the blog or something.

I was like, sounds like fun and I’ve been to 360iDev ever since, it’s one of my favorite conferences and yeah, we’ve always hit it off.

Jake: John, from your perspective, you’ve been doing this for years. You’ve got tons of experience. How is Ray … First of all, how did it go? How well did it come off?

John: It came off really well. There’s a lot that nobody ever sees on the inside, and that’s usually my watermark of how it went. It went great. There was no catastrophes that nobody saw so that was a plus.

Jake: How was it different from the other experiences you’ve had with conferences?

John: It was cool. As Ray said, I do some tutorials at 360iDev but it’s the first day only and that’s it and then we start standard talks. It was neat to see a conference that was all tutorial based. It was an interesting logistic planning issue as well, making sure that a conference could support tutorials all day every day for the 2 days.

Jake: I’m a little bit curious just [00:06:00] … You’ve been doing this a long time and you’re obviously passionate about it. What got you into doing conferences and what do you get out of putting these on?

John: I’ve realized that more than even writing code, organizing things was what I loved to do and so my old business partner and I decided let’s do an iOS conference because there wasn’t one and we both loved and had our iPhones. It made sense. I want more cool things on my iPhone, so help facilitate that.

Mic: Ray, can you give us a little bit of insight why RWDevCon is different from other iOS developer conferences?

Ray: Sure. Like we’ve been talking about, there’s this hands-on focus, so the way our tutorials work is you come in, the instructor gives a quick 5-minute overview of the subject and then they go straight into a demo where you actually code along with the instructor, which is different from a lot of conferences where you’re passively watching things.

After the demo you move onto a lab and a challenge portion where you’re working through the projects on your own, so it’s very hands-on because for me, I can only learn by doing it myself and I think that really helps with that.

There’s another thing we do that’s a little different. I’ve taught a lot of day-long workshops in the past and after a certain point in the day I always see people get tired and they’re not really working very hard on the hands-on stuff anymore. In the afternoon we switch over to inspiration talks, which are more non-technical talks but focused on trying to give you a new idea or kick you in a new direction in life because personally when I go to a conference, I get a lot out of that kind of thing. I always want to come home with something new and with a new idea because sometimes in life you need a little jolt to try something different.

Mic: How do you think the tutorial format went down with the attendees?

Ray: I think people really loved it, I got a lot of feedback on that. They say it definitely worked and that was one of their favorite parts of the conference.

Mic: Same question for the inspirations.

Ray: People liked that as well because like I said, people were tired and they were like [00:08:00], yeah, it was nice to switch over and I’ve heard a lot of people say they actually got a lot out of it and we had some parties in the evening and I saw people talking about some of the ideas there and that’s exactly what I hoped to see.

Jake: John, what’s the role of the conference in our community? How important are conferences to keeping the community together and teaching us to grow together?

John: I think they’re really important. Any more you can learn pretty much anything you want online. You can watch videos, you can read books, you can do all that. The conference is, on aside of the educational aspect which is still a bit part of them, gives everybody a chance to come together in one place and put real people to Twitter avatars and things like that. I think there’s a huge value, not just in the education aspect but just in keeping the community so everybody knows everybody and recognizes each other.

Jake: I’m assuming you’ve met a lot of really interesting people over the years doing all these conferences. Is there a highlight for you? Somebody you met that you wouldn’t have otherwise been able to get in contact with?

John: One of the highlights is probably Mike Lee. He came to the first one, I didn’t know him at all other than just his role in the community at the time, and he attended and the first one he announced that he was going to Apple and then 2 years later he announced, he was by the way now I’m leaving Apple and joining the community, doing Indie stuff again. It’s been cool that he’s been involved. I think he’s only ever missed one since we’ve started doing 360iDev. He was in Amsterdam and couldn’t travel back over here, so it was a pretty legitimate reason.

Yeah, I think that was definitely one of the highlights for me.

Mic: Everything was only finished late last night. A lot of people came back after the closing remarks, probably for a lot longer than anybody expected, which is great. It bled out into the bar as well and everybody was reflecting on how great everything was. The Razeware guys, we went out for a meal last night and we tried to make some sense of everything that had gone on the last 2 days. Given that we’ve had a little bit of time [00:10:00] now, was it everything that you expected it to be?

Ray: Oh yeah, definitely, because this was my first time ever doing this and so I’ve been a little bit nervous about it and saying are we really going to be able to pull this off, especially since so much planning and organization had gone into it, but there was this moment about 2 weeks ago where I suddenly stopped being nervous. I was like you know what, we have our materials ready, we were prepared, we’re actually going to pull this off.

I was nervous about a few things here but everything seemed to go really smooth and what made me the most happy was just the feedback that I got from attendees. People were saying how much they loved it, how they wanted to come back again if we were to do it again next year and how much they got out of it. They seemed really genuinely loving the experience. All the work we put into it made it worth it once I heard that kind of feedback.

Mic: Now you mentioned in the closing remarks you’d already thought of a couple of things that if you were to do it again you might change. Can you give us an idea what those were?

Ray: Sure. The first one, a lot of people said that they wished there was a little more time for the tutorials because when you’re teaching someone a live demo, you have to take your time and go through code a little slowly and we could have used 15 more minutes for each of those. That was the number one thing.

The second thing was to help with that, the way [00:12:00] we practiced is everybody had a partner who was also a speaker who looked at things, and then I also reviewed everybody’s material, but it might have been better if we had a board of volunteers of a mix of different levels of experience to give us a little more feedback to make sure we’re going at the right pace for things.

The third thing was we had course materials and they were a little bit confusing how they were laid out, so next time we can do a little better at making sure that’s all clear.

Jake: For myself I wondered about the tutorial format because you can’t cover as much material when you actually have people write code and do stuff, and the sessions were an hour and a half something total. Yeah, it was interesting because personally as an attendee of other sessions I really appreciated what I learned and I think your intuition was right on that walking out of a conference, usually you go through 10 or 20 sessions in a couple of days. It’s not something where the next day you could actually sit down and use what you learned. You’ve got to kind of mull it over and then go back …

It’s like WWDC, you’ve got to watch those 3 or 4 times to absorb what you’re looking at. That was my experience. [At RWDevCon], it was useful immediately. It seemed like from the people I was talking to, and being a speaker, I don’t know that people would run up to me and told me what they didn’t like, but that was universally the feedback I got that everybody appreciated it and anything they were asking for was more time and I looked through my evaluation forms and I got a bunch of those comments. I just wanted to spend more time.

It seemed really good. What was a personal highlight for you, Ray, from the conference?

Ray: For me it was getting a chance to all come together as a community in a couple different ways. One was the raywenderlich.com team, because for me this was the first time that we’ve had such a large group of folks from our site together and there’s been a lot of people on our site that I’ve never met in person, despite years of working together, including Mic who is sitting right next to me and Matthijs is over here. [00:14:00]

It’s really cool to all come together as a team. That’s huge. Then also getting to meet people who have known about our website, and so many people have come up to me and they’ve said, you know, your guys’ site has helped me get started with iPhone development and I’ve been reading it ever since the very beginning and seeing people who know the site like that and getting to hang out with them and share some drinks, some funny stories, it’s been incredible.

Mic: John, I’ve got a question for you – seeing Ray stress and panic and run around like a headless chicken on all this effort, on everything that’s gone into the planning of this conference, so being on the inside of that and seeing how much work has gone into it. What’s your motivation for not only putting on your own conference but then to help other people put on their conferences?

John: The same for both. There’s a ton of stress and generally freaking out that goes into it up until the first session starts. Then you get that high of it’s all going and so at that point once it starts, anything … You’re kind of locked in. Everything is either going to go well or not and you play it by ear. Then there’s the high of the last session and going to the bar afterwards and hanging out with everybody and it’s done. You’ve accomplished the job you set out to do. I enjoy that feeling of once everything starts.

The lead up to it is very stressful and I’ll probably be grey earlier than I should have, but otherwise getting to it and doing it is the fun part that I love.

Mic: I have a similar question, as Jake asked Ray – what was your highlight?

John: Meeting people that don’t normally come to my conference, which is always nice. I see people at 360iDev and obviously they’ve come to 360iDev and so I know them. It was cool to meet people in the community that I haven’t met yet, which is part of why I go to WWDC and hang out and do AltConf too is I want to meet more of the community and my little piece of it is just one little piece. It was very cool to meet people that yeah, that have never made it to highdev [00:16:00] yet and I still got to meet and hang out with them.

Mic: Fantastic. Jake, as a raywenderlich.com team member and a speaker, and an inspirational speaker, what was your personal highlight?

Jake: For me it was when Marin Todorov did a Ray Wenderlich folklore session. His inspiration session was on Ray Wenderlich folklore and he went through … He’s been on the Ray Wenderlich team from the very beginning, and so he went through some of the major milestones like when we did our first book, and I had been there for a lot of it. I hadn’t been there quite as long as Marin but almost. I had had my perspective on what had happened, and it was interesting to see Marin’s perspective because there are so many things that happened that just came together and I didn’t really understand how.

He was really funny, first of all and he had all these great stories. He did this great job of showing us how we came to where we are now with a hundred people on the team and just all the success and all the books and all the things that I’ve participated in quite a few of them, and it was really interesting to see that perspective.

My impression was, especially with Marin, he communicated how proud he is to be a part of it, and I feel the same way and you could tell that there’s a lot of heart that comes from the team and that was really moving and it was funny and it was great. That for me was the highlight. How about you, what was your highlight?

Mic: I think the highlight for me was just this overwhelming sense of community. I’ve worked with Ray probably for about 2-1/2 maybe almost 3 years. We’ve never met and yet I’ve somehow ended up going from editorial team member to working for him full time. We all hung out in Slack, we email, we have communications with individual [00:18:00] offices when we work on books or tutorials. There is a sense of community but it’s virtual because it’s all … It’s not face to face. This is the first time we’ve met, we’ve been doing the podcast for 3 seasons.

Jake: I need to let everybody know, Mic is much taller in real life than the pictures.

Mic: When I arrived, you kind of expected it to be a slightly socially awkward moment where are people going to be like they are in slack? Are we going to get on? What personalities are these people. It was like you’ve known them. It was like these are people that you’ve worked with. They could’ve been in the same office building or whatever and it was … Everybody settled in straight away and it was just great. It was overwhelming, that for me has been the highlight.

Jake: I’d like to … My experience … I don’t do this a lot, go to conferences, but when I have there’s a certain amount of energy you have to put in in order to overcome that barrier to meet somebody that you’ve never met. Even if you know you have a common interest, you’re there, you’re passionate about IOS or you’re there at a conference together so you know you’ve got something in common. There’s this barrier you have to get past before you become comfortable. Everybody I talked to, certainly the team but even the attendees that I’d never met before, it was so easy to talk to people. It was so comfortable, consistently I was surprised by how easy it was to … I met a lot of people and it was great.

Mic: One thing I noticed was, and I think the tone may have been set with Ray’s keynote about when he was talking about friendship, is there was no cliques. There was no little groups of people that disappeared off. Everybody mingled with everybody else.

One of the other things that I think that was a result of was people are already … People who buy the books or read the site tutorials already have a sense of feeling that they know these guys, so these are people that [00:20:00] that are inspired by their books so they know that when they come, these aren’t just random people that have never heard of giving a talk. These are people that they’ve got this already almost emotional connection with, so I think it’s just all about this community and it was great to see that. Everybody was welcome. Everybody was involved. Everybody was mingling and chatting and there was no awkward things. It was fantastic, it really was fantastic. That’s genuine, not just because Ray’s staring at me.

Guys, I think that’s probably a good place to wrap it up. We’re as I said earlier, in a hotel room, we’ve got planes to catch, we’re actually borrowing Saul Mora’s podcast gear which we really appreciate a lot and we’ll get that back to you before you leave.

Just one thing, we’re seeing while we’ve been here, that when people really like things they leave feedback and we got a ton of feedback. We’d really like to stress that for the podcast. We may receive 1 or 2 emails a week. They are genuinely positive but we’d like to hear more. We’d like to hear more from you guys to really understand if we’re going in the right direction, should we change, if there’s things that we should change. You may have noted we recently did a transcript which may help some of you guys that don’t quite understand my European accent.

Jake: It’s accurate. They capture the British accent, the difference.

Mic: The colloquialisms, which is great. Get in touch with us at podcast@raywenderlich.com and also please rate and review on iTunes. Thanks for listening guys, hope you enjoyed this and we’ll see you all next time.

Our Sponsor

Interested in sponsoring a podcast episode? We sell ads via Syndicate Ads, check it out!

Links and References

Contact Us

Where To Go From Here?

We hope you enjoyed this episode of our podcast. Stay tuned for a new episode next week! :]

Be sure to subscribe in iTunes to get access as soon as it comes out!

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!

RWDevCon with Ray Wenderlich and John Wilker – Podcast S03 E04 is a post from: Ray Wenderlich

The post RWDevCon with Ray Wenderlich and John Wilker – Podcast S03 E04 appeared first on Ray Wenderlich.


Video Tutorial: Intro to Auto Layout Part 5: Multipliers

Modern Core Graphics with Swift: Part 1

$
0
0

FinalApp

Imagine you’ve finished your app and it works just fine, but the interface lacks style. You could draw several sizes of all your custom control images in Photoshop and hope that Apple doesn’t come out with a @4x retina screen…

Or, you could think ahead and use Core Graphics to create one image in code that scales crisply for any size device.

Core Graphics is Apple’s vector drawing framework – it’s a big, powerful API and there’s a lot to learn. But never fear – this three-part series will ease you into it by starting out simple, and by the end you’ll be able to create stunning graphics ready to use in your apps.

This is a brand new series, with a modern approach to teaching Core Graphics. The series is fully up-to-date with Xcode 6 and Swift, and covers cool new features like @IBDesignable and @IBInspectable that make learning Core Graphics fun and easy.

So grab your favorite beverage and let’s begin!

Introducing Flo – One glass at a time

You’ll be creating a complete app to track your drinking habits.

Specifically, it makes it easy to track how much water you drink. “They” tell us that drinking eight glasses of water a day is healthy, but it’s easy to lose track after a few glasses. This is where Flo comes in; every time you polish off a refreshing glass of water, tap the counter. You’ll also see a graph of your previous seven days’ consumption.

1-CompletedApp

In the first part of this series, you’ll create three controls using UIKit’s drawing methods.

Then in part two , you’ll have a deeper look at Core Graphics contexts and draw the graph.

In part three , you’ll create a patterned background and award yourself a homemade Core Graphics medal. :]

Getting Started

Your first task is to create your very own Flo app. There is no download to get you going, because you’ll learn more if you build it from the ground up.

Create a new project (File\New\Project…), select the template iOS\Application\Single View Application and click Next.

Fill out the project options. Set the Product Name to Flo, the Language to Swift and Devices to iPhone, and click Next.

1-Xcode

On the final screen, uncheck Create Git repository and click Create.

You now have a starter project with a storyboard and a view controller.

Custom Drawing on Views

Whenever you want to do some custom drawing, you just need to take three steps:

  1. Create a UIView subclass.
  2. Override drawRect(_:) and add some Core Graphics drawing code.
  3. There is no step 3 – that’s it! :]

Let’s try this out by making a custom-drawn plus button.

1-AddButtonFinal

Create a new file (File\New\File…), choose iOS\Source\Cocoa Touch Class and name the new class PushButtonView. Make it a subclass of UIButton, and ensure the language is Swift. Click Next and then Create.

UIButton is a subclass of UIView, so all methods in UIView, such as drawRect(_:), are also available in UIButton.

In Main.storyboard, drag a UIButton into the view controller’s view, and select the button in the Document Outline.

In the Identity Inspector, change the class to use your own PushButtonView.

1-PushButtonViewClass

In the Size Inspector, set X=250, Y=350, Width=100, and Height=100:

1-PlusButtonCoordinates

Auto Layout Constraints

Now you’ll set up the Auto Layout constraints (text instructions follow):

1-AutoLayout

  • With the button selected, control-drag from the center of the button slightly left (still within the button), and choose Width from the popup menu.
  • Similarly, with the button selected, control-drag from the center of the button slightly up (still within the button), and choose Height from the popup menu.
  • Control-drag left from inside the button to outside the button, and choose Center Vertically in Container.
  • Finally control-drag up from inside the button to outside the button and choose Center Horizontally in Container.

This will create the four required auto layout constraints, and you can now see them in the Size Inspector:

1-AutoLayoutConstraints

In the Attributes Inspector, remove the default title “Button”.

1-RemoveTitle2

You can build and run at this point if you’d like, but right now you’ll just see a blank screen at that point. Let’s fix that up!

Drawing the Button

Recall the button you’re trying to make is circular:

1-AddButtonFinal

To draw a shape in Core Graphics, you define a path that tells Core Graphics the line to trace (like two straight lines for the plus) or the line to fill (like the circle which should be filled here). If you’re familiar with Illustrator or the vector shapes in Photoshop, then you’ll easily understand paths.

There are three fundamentals to know about paths:

  • A path can be stroked and filled.
  • A stroke outlines the path in the current stroke color.
  • A fill will fill up a closed path with the current fill color.

One easy way to create a Core Graphics path is through a handy class called UIBezierPath. This lets you easily create paths with a user-friendly API, whether you want to create paths based on lines, curves, rectangles, or a series of connected points.

Let’s try using UIBezierPath to create a path, and then fill it with a green color. To do this, open PushButtonView.swift and change the commented out drawRect(_:) code to:

override func drawRect(rect: CGRect) {
  var path = UIBezierPath(ovalInRect: rect)
  UIColor.greenColor().setFill()
  path.fill()
}

First you created an oval-shaped UIBezierPath that is the size of the rectangle passed to it. In this case, it’ll be the 100×100 button you defined in the storyboard, so the “oval” will actually be a circle.

Paths themselves don’t draw anything. You can define paths without an available drawing context. To draw the path, you gave the current context a fill color, and then fill the path.

Build and run the application, and you’ll see the green circle.

1-SimGreenButton2

So far, you’ve discovered how easy it is to make custom-shaped views. You’ve done this by creating a UIButton subclass, overriding drawRect(_:) and adding the UIButton to your storyboard.

custom-views-not-bad

Behind the Scenes in Core Graphics

Each UIView has a graphics context, and all drawing for the view renders into this context before being transferred to the device’s hardware.

iOS updates the context by calling drawRect(_:) whenever the view needs to be updated. This happens when:

  • The view is new to the screen.
  • Other views on top of it are moved.
  • The view’s hidden property is changed.
  • Your app explicitly calls the setNeedsDisplay() or setNeedsDisplayInRect() methods on the view.

Note: Any drawing done in drawRect(_:) goes into the view’s graphics context. Be aware that if you start drawing outside of drawRect(_:), as you’ll do in the final part of this tutorial, you’ll have to create your own graphics context.

You haven’t used Core Graphics yet in this tutorial because UIKit has wrappers around many of the Core Graphics functions. A UIBezierPath, for example, is a wrapper for a CGMutablePath, which is the lower-level Core Graphics API.

Note: Never call drawRect(_:) directly. If your view is not being updated, then call setNeedsDisplay() on the view.

setNeedsDisplay() does not itself call drawRect(_:), but it flags the view as ‘dirty’, triggering a redraw using drawRect(_:) on the next screen update cycle. Even if you call setNeedsDisplay() five times in the same method you’ll only ever actually call drawRect(_:) once.

@IBDesignable – Interactive Drawing

Creating code to draw a path and then running the app to see what it looks like can be about as exciting as watching paint dry, but you’ve got options. Live Rendering, a terrific new feature in Xcode 6, allows you to give a view to the @IBDesignable attribute. As you update the view in drawRect(_:), it’ll immediately update on the storyboard.

Still in PushButtonView.swift, just before the class declaration, add:

@IBDesignable

This opens up Live Rendering to you.

Now set up your screen so that you have the storyboard and the code side by side.

Do this by selecting PushButtonView.swift to show the code, then at the top right, click the Assistant Editor — the icon that looks like two intertwined rings. The storyboard should then show on the right hand pane. If it doesn’t, you’ll have to choose the storyboard in the breadcrumb trail at the top of the pane:

1-Breadcrumbs

Close the document outline at the left of the storyboard to free up some room. Do this either by dragging the edge of the document outline pane or clicking the button at the bottom of the storyboard:

1-DocumentOutline

When you’re all done, your screen should look like this:

1-SideBySide

In PushButtonView‘s drawRect(_:), change

UIColor.greenColor().setFill()

to

UIColor.blueColor().setFill()

and you’ll immediately see the change in the storyboard. Pretty cool!

LiveRendering

Now let’s create the lines for the plus sign.

Drawing Into the Context

Core Graphics uses a “painter’s model.”

When you draw into a context, it’s exactly like making a painting. You lay down a path and fill it, and then lay down another path on top and fill it. You can’t change the pixels that have been laid down, but you can “paint” over them.

This image from Apple’s documentation describes how this works. Just as it is when you’re painting on a canvas, the order in which you draw is critical.

1-PaintersModel

Your plus sign is going on top of the blue circle, so first you code the blue circle and then the plus sign.

You could draw two rectangles for the plus sign, but it’s easier to draw a path and then stroke it with the desired thickness.

Add this code to the end of drawRect(_:) to draw the horizontal dash of the plus sign:

//set up the width and height variables
//for the horizontal stroke
let plusHeight: CGFloat = 3.0
let plusWidth: CGFloat = 45.0
 
//create the path
var plusPath = UIBezierPath()
 
//set the path's line width to the height of the stroke
plusPath.lineWidth = plusHeight
 
//move the initial point of the path
//to the start of the horizontal stroke
plusPath.moveToPoint(CGPoint(
  x:bounds.width/2 - plusWidth/2,
  y:bounds.height/2))
 
//add a point to the path at the end of the stroke
plusPath.addLineToPoint(CGPoint(
  x:bounds.width/2 + plusWidth/2,
  y:bounds.height/2))
 
//set the stroke color
UIColor.whiteColor().setStroke()
 
//draw the stroke
plusPath.stroke()

In this block, you set up a UIBezierPath, give it a start position (left side of the circle) and draw to the end position (right side of the circle). Then you stroke the path outline in white. At this point, you should see this in the Storyboard:

In your storyboard, you’ll now have a blue circle with a dash in the middle of it:

Dash

Note: Remember that a path simply consists of points. Here’s an easy way to grasp the concept: when creating the path imagine that you have a pen in hand. Put two dots on a page, then place the pen at the starting point, and then draw a line to the next point by drawing a line.

That’s essentially what you do with the above code by using moveToPoint(_:) and addLineToPoint(_:).

Now run the application on either an iPad 2 or an iPhone 6 Plus simulator, and you’ll notice the dash is not as crisp as it should be. It has a pale blue line encircling it.

1-PixelledLine

Points and Pixels

Back in the days of the very first iPhones, points and pixels occupied the same space and were the same size, making them essentially the same thing. When retina iPhones came into existence, suddenly there were quadruple the amounts of pixels on the screen for the same number of points.

Similarly, the iPhone 6 Plus has once again increased the amount of pixels for the same points.

Note: The following is conceptual – the actual hardware pixels may differ. For example, after rendering 3x, the iPhone 6 Plus downsamples to display the full image on the screen. To learn more about iPhone 6 Plus downsampling, check out this great post.

Here’s a grid of 12×12 pixels, where points are shown in gray and white. The first (iPad 2) is a direct mapping of points to pixels. The second (iPhone 6) is a 2x retina screen, where there are 4 pixels to a point, and the third (iPhone 6 Plus) is a 3x retina screen, where there are 9 pixels to a point.

1-Pixels

The line you’ve just drawn is 3 points high. Lines stroke from the center of the path, so 1.5 points will draw on either side of the center line of the path.

This picture shows drawing a 3-point line on each of the devices. You can see that the iPad 2 and the iPhone 6 Plus result in the line being drawn across half a pixel — which of course can’t be done. So, iOS anti-aliases the half-filled pixels with a color half way between the two colors, and the line looks fuzzy.

1-PixelLineDemonstrated

In reality, the iPhone 6 Plus has so many pixels, that you probably won’t notice the fuzziness, although you should check this for your own app on the device. But if you’re developing for non-retina screens like the iPad 2 or iPad mini, you should do anything you can to avoid anti-aliasing.

If you have oddly sized straight lines, you’ll need to position them at plus or minus 0.5 points to prevent anti-aliasing. If you look at the diagrams above, you’ll see that a half point on the iPad 2 will move the line up half a pixel, on the iPhone 6, up one whole pixel, and on the iPhone 6 Plus, up one and a half pixels.

In drawRect(_:), replace the moveToPoint and addLineToPoint code lines with:

//move the initial point of the path
//to the start of the horizontal stroke
plusPath.moveToPoint(CGPoint(
  x:bounds.width/2 - plusWidth/2 + 0.5,
  y:bounds.height/2 + 0.5))
 
//add a point to the path at the end of the stroke
plusPath.addLineToPoint(CGPoint(
  x:bounds.width/2 + plusWidth/2 + 0.5,
  y:bounds.height/2 + 0.5))

iOS will now render the lines sharply on all three devices because you’re now shifting the path by half a point.

Note: For pixel perfect lines, you can draw and fill a UIBezierPath(rect:) instead of a line, and use the view’s contentScaleFactor to calculate the width and height of the rectangle. Unlike strokes that draw outwards from the center of the path, fills only draw inside the path.

Add the vertical stroke of the plus just after the previous two lines of code, and before setting the stroke color in drawRect(_:). I bet you can figure out how to do this on your own, since you’ve already drawn a horizontal stroke:

Solution Inside: Solution SelectShow>

You should now see the live rendering of the plus button in your storyboard. This completes the drawing for the plus button.

1-FinishedPlus

@IBInspectable – Custom Storyboard Properties

So you know that frantic moment when you tap a button more than needed, just to make sure it registers? Well, you need to provide a way for the user to reverse such overzealous tapping — you need a minus button.

A minus button is identical to the plus button except that it has no vertical bar and sports a different color. You’ll use the same PushButtonView class for the minus button, and declare what sort of button it is and its color when you add it to your storyboard.

@IBInspectable is an attribute you can add to a property that makes it readable by Interface Builder.

At the top of the PushButtonView class, add these two properties:

@IBInspectable var fillColor: UIColor = UIColor.greenColor()
@IBInspectable var isAddButton: Bool = true

Change the fill color code at the top of drawRect(_:) from

UIColor.blueColor().setFill()

to:

fillColor.setFill()

The button will turn green in your storyboard view.

Surround the vertical line code in drawRect(_:) with an if statement:

//Vertical Line
 
if isAddButton {
  //vertical line code moveToPoint(_:) and addLineToPoint(_:)
}
//existing code
//set the stroke color
UIColor.whiteColor().setStroke()
 
//draw the stroke
plusPath.stroke()

This makes it so you only draw the vertical line if isAddButton is set – this way the button can be either a plus or a minus button.

The completed PushButtonView looks like this:

import UIKit
 
@IBDesignable
class PushButtonView: UIButton {
 
  @IBInspectable var fillColor: UIColor = UIColor.greenColor()
  @IBInspectable var isAddButton: Bool = true
 
  override func drawRect(rect: CGRect) {
 
    var path = UIBezierPath(ovalInRect: rect)
    fillColor.setFill()
    path.fill()
 
    //set up the width and height variables
    //for the horizontal stroke
    let plusHeight: CGFloat = 3.0
    let plusWidth: CGFloat = 45.0
 
    //create the path
    var plusPath = UIBezierPath()
 
    //set the path's line width to the height of the stroke
    plusPath.lineWidth = plusHeight
 
    //move the initial point of the path
    //to the start of the horizontal stroke
    plusPath.moveToPoint(CGPoint(
      x:bounds.width/2 - plusWidth/2 + 0.5,
      y:bounds.height/2 + 0.5))
 
    //add a point to the path at the end of the stroke
    plusPath.addLineToPoint(CGPoint(
      x:bounds.width/2 + plusWidth/2 + 0.5,
      y:bounds.height/2 + 0.5))
 
    //Vertical Line
    if isAddButton {
      //move to the start of the vertical stroke
      plusPath.moveToPoint(CGPoint(
        x:bounds.width/2 + 0.5,
        y:bounds.height/2 - plusWidth/2 + 0.5))
 
      //add the end point to the vertical stroke
      plusPath.addLineToPoint(CGPoint(
        x:bounds.width/2 + 0.5,
        y:bounds.height/2 + plusWidth/2 + 0.5))
    }
 
    //set the stroke color
    UIColor.whiteColor().setStroke()
 
    //draw the stroke
    plusPath.stroke()
 
  }
 
}

In your storyboard, select the push button view. The two properties you declared with @IBInspectable appear at the top of the Attributes Inspector:

Change Fill Color to the RGB(87, 218, 213), and change the Is Add Button to off.

1-InspectableFillColor

The changes will take place immediately in the storyboard:

1-InspectableMinusButton

Pretty cool, eh? Now change Is Add Button back to on to return the button to a plus button.

A Second Button

Add a new UIButton to the storyboard, select it, and update the size and position in the Size Inspector to set X=275, Y=480, Width=50, and Height=50:

1-PushButtonMinusCoords

In the Identity Inspector, change the UIButton class to PushButtonView.

1-PushButtonMinusClass

The green plus button will be drawn under your old plus button.

In the Attributes Inspector, change Fill Color to a RGB(238, 77, 77) and change Is Add Button to off.

Remove the default title Button.

1-MinusButtonColor

Add the auto layout constraints for the new view similarly to how you did before:

  • With the button selected, control-drag from the center of the button slightly left (still within the button), and choose Width from the popup menu.
  • Similarly, with the button selected, control-drag from the center of the button slightly up (still within the button), and choose Height from the popup menu.
  • Control-drag up from inside the button to outside the button and choose Center Horizontally in Container.
  • Control-drag up from the bottom button to the top button, and choose Vertical Spacing.

Build and run the application. You now have a reusable customizable view that you can add to any app. It’s also crisp and sharp on any size device. Here it is on the iPhone 4S.

1-SimPushButtons

Arcs with UIBezierPath

The next customized view you’ll create is this one:

1-CompletedCounterView

This looks like a filled shape, but the arc is actually just a fat stroked path. The outlines are another stroked path consisting of two arcs.

Create a new file, File\New\File…, choose Cocoa Touch Class, and name the new class CounterView. Make it a subclass of UIView, and ensure the language is Swift. Click Next, and then click Create.

Replace the code with:

import UIKit
 
let NoOfGlasses = 8
let π:CGFloat = CGFloat(M_PI)
 
@IBDesignable class CounterView: UIView {
 
  @IBInspectable var counter: Int = 5 
  @IBInspectable var outlineColor: UIColor = UIColor.blueColor()
  @IBInspectable var counterColor: UIColor = UIColor.orangeColor()
 
  override func drawRect(rect: CGRect) {
 
  }
}

Note: Now that Apple allows Unicode characters in constant and variable names, you can use π as a constant for pi that will make your code more readable. You type in π by pressing Alt and P at the same time.

Here you create two constants. NoOfGlasses is the target number of glasses to drink per day. When this figure is reached, the counter will be at its maximum.

You also create three @IBInspectable properties that you can update in the storyboard. The variable counter keeps track of the number of glasses consumed, and it’s useful to have the ability to change it in the storyboard, especially for testing the counter view.

Go to Main.storyboard and add a UIView above the plus PushButtonView.

In the Size Inspector, set X=185, Y=70, Width=230, and Height=230:

1-CounterViewCoords

Add the auto layout constraints for the new view similarly to how you did before:

  • With the view selected, control-drag from the center of the button slightly left (still within the view), and choose Width from the popup menu.
  • Similarly, with the view selected, control-drag from the center of the button slightly up (still within the view), and choose Height from the popup menu.
  • Control-drag up from inside the view to outside the view and choose Center Horizontally in Container.
  • Control-drag down from the view to the top button, and choose Vertical Spacing.

In the Identity Inspector, change the class of the UIView to CounterView. Any drawing that you code in drawRect(_:) will now show up in the view.

Impromptu Math Lesson

We interrupt this tutorial for a brief, and hopefully un-terrifying look back at high school level math. As Douglas Adams would say – Don’t Panic! :]

Drawing in the context is based on this unit circle. A unit circle is a circle with a radius of 1.0.

1-FloUnitCircle

The red arrow shows where your arc will start and end, drawing in a clockwise direction. You’ll draw an arc from the position 3π / 4 radians — that’s the equivalent of 135º, clockwise to π / 4 radians – that’s 45º.

Radians are generally used in programming instead of degrees, and it’s useful to be able to think in radians so that you don’t have to convert to degrees every time you want to work with circles. Later on you’ll need to figure out the arc length, which is when radians will come into play.

An arc’s length in a unit circle (where the radius is 1.0) is the same as the angle’s measurement in radians. For example, looking at the diagram above, the length of the arc from 0º to 90º is π/2. To calculate the length of the arc in a real situation, take the unit circle arc length and multiply it by the actual radius.

To calculate the length of the red arrow above, you would simply need to calculate the number of radians it spans:

          2π – end of arrow (3π/4) + point of arrow (π/4) = 3π/2

In degrees that would be:

          360º – 135º + 45º = 270º

celebrate-all-the-maths

Back to Drawing Arcs

In CounterView.swift, add this code to drawRect(_:) to draw the arc:

// 1
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
 
// 2
let radius: CGFloat = max(bounds.width, bounds.height)
 
// 3
let arcWidth: CGFloat = 76
 
// 4
let startAngle: CGFloat = 3 * π / 4
let endAngle: CGFloat = π / 4
 
// 5
var path = UIBezierPath(arcCenter: center,
  radius: bounds.width/2 - arcWidth/2,
  startAngle: startAngle,
  endAngle: endAngle,
  clockwise: true)
 
// 6
path.lineWidth = arcWidth
counterColor.setStroke()
path.stroke()

Imagine drawing this with a compass — you’d put the point of the compass in the center, open the arm to the radius you need, load it with a thick pen and spin it to draw your arc.

In this code, center is the point of the compass, the radius is the width that the compass is open (less half the width of the pen) and the arc width is the width of the pen.

The following explains what each section does:

  1. Define the center point of the view where you’ll rotate the arc around.
  2. Calculate the radius based on the max dimension of the view.
  3. Define the thickness of the arc.
  4. Define the start and end angles for the arc.
  5. Create a path based on the center point, radius, and angles you just defined.
  6. Set the line width and color before finally stroking the path.

Note: When you’re drawing arcs, this is generally all you need to know, but if you want to dive further into drawing arcs, then Ray’s (older) Core Graphics Tutorial on Arcs and Paths will help.

In the storyboard and when you run your application, this is what you’ll see:

1-SimArcStroke

Outlining the Arc

When the user indicates they’ve enjoyed a glass of water, an outline on the counter shows the progress towards the goal of eight glasses.

This outline will consist of two arcs, one outer and one inner, and two lines connecting them.

In CounterView.swift , add this code to the end of drawRect(_:):

//Draw the outline
 
//1 - first calculate the difference between the two angles
//ensuring it is positive
let angleDifference: CGFloat = 2 * π - startAngle + endAngle
 
//then calculate the arc for each single glass
let arcLengthPerGlass = angleDifference / CGFloat(NoOfGlasses)
 
//then multiply out by the actual glasses drunk
let outlineEndAngle = arcLengthPerGlass * CGFloat(counter) + startAngle
 
//2 - draw the outer arc
var outlinePath = UIBezierPath(arcCenter: center, 
                                  radius: bounds.width/2 - 2.5,
                              startAngle: startAngle, 
                                endAngle: outlineEndAngle,
                               clockwise: true)
 
//3 - draw the inner arc
outlinePath.addArcWithCenter(center, 
                     radius: bounds.width/2 - arcWidth + 2.5,
                 startAngle: outlineEndAngle, 
                   endAngle: startAngle, 
                  clockwise: false)
 
//4 - close the path
outlinePath.closePath()
 
outlineColor.setStroke()
outlinePath.lineWidth = 5.0
outlinePath.stroke()

A few things to go through here:

  1. outlineEndAngle is the angle where the arc should end, calculated using the current counter value.
  2. outlinePath is the outer arc. The radius is given to UIBezierPath() to calculate the actual length of the arc, as this arc is not a unit circle.
  3. Adds an inner arc to the first arc. This has the same angles but draws in reverse (clockwise is set to false). Also, this draws a line between the inner and outer arc automatically.
  4. Closing the path automatically draws a line at the other end of the arc.

With the counter property in CounterView.swift set to 5, your CounterView should now look like this in the storyboard:

1-ArcOutline

Open Main.storyboard, select the CounterView and in the Attributes Inspector, change the Counter property to check out your drawing code. You’ll find that it is completely interactive. Try adjusting the counter to be more than eight and less than zero. You’ll fix that up later on.

Change the Counter Color to RGB(87, 218, 213), and change the Outline Color to RGB(34, 110, 100).

1-CounterView

Making it All Work

Congrats! You have the controls, now all you have to do is wire them up so the plus button increments the counter, and the minus button decrements the counter.

In Main.storyboard, drag a UILabel to the center of the Counter View, and make sure it is a subview of the Counter View.

In the Size Inspector, set X=93, Y=93, Width=44, and Height=44:

1-LabelCoords

In the Attributes Inspector, change Alignment to center, font size to 36 and the default Label title to 8.

1-LabelAttributes

Go to ViewController.swift and add these properties to the top of the class:

//Counter outlets
@IBOutlet var counterView: CounterView!
@IBOutlet weak var counterLabel: UILabel!

Still in ViewController.swift, add this method to the end of the class:

@IBAction func btnPushButton(button: PushButtonView) {
  if button.isAddButton {
    counterView.counter++
  } else {
    if counterView.counter > 0 {
      counterView.counter--
    }
  }
  counterLabel.text = String(counterView.counter)
}

Here you increment or decrement the counter depending on the button’s isAddButton property, make sure the counter doesn’t drop below zero — nobody can drink negative water. :] You also update the counter value in the label.

Speaking of labels, where’s the value? It’s not there because you’ve not put it there. Add this code to the end of viewDidLoad():

counterLabel.text = String(counterView.counter)

This shows the counter value in the label.

In Main.storyboard, connect the CounterView outlet and UILabel outlet. Connect the method to the Touch Up Inside event of the two PushButtonViews.

1-ConnectingOutlets2

Run the application and see if your buttons now update the counter label. They should.

But wait, why isn’t the counter view updating?

Think way back to the beginning of this tutorial, and how you only call drawRect(_:) when other views on top of it are moved, or its hidden property is changed, or the view is new to the screen, or your app calls the setNeedsDisplay() or setNeedsDisplayInRect() methods on the view.

However, the Counter View needs to be updated whenever the counter property is updated, otherwise the user will think your app is busted.

Go to CounterView.swift and change the counter property declaration to:

@IBInspectable var counter: Int = 5 {
  didSet {
    if counter <=  NoOfGlasses {
      //the view needs to be refreshed
      setNeedsDisplay()
    }
  }
}

This code makes it so that the view refreshes only when the counter is less than or equal to the user’s targeted glasses, as the outline only goes up to 8.

Run your app again. Everything should now be working properly.

1-Part1Finished

Where to Go From Here?

You’ve covered basic drawing in this tutorial, and you should now be able to change the shape of views in your UIs. But wait – there’s more! In Part 2 of this tutorial , you’ll explore Core Graphics contexts in more depth and create a graph of your water consumption over time.

You can download the project with all the code up to this point.

If you have any questions or comments please join the forum discussion below.

Modern Core Graphics with Swift: Part 1 is a post from: Ray Wenderlich

The post Modern Core Graphics with Swift: Part 1 appeared first on Ray Wenderlich.

Video Tutorial: Intro to Auto Layout Part 6: Interface Builder and Constraints

RWDevCon 2015 Post-Mortem

$
0
0
Check out the post-conference report for RWDevCon!

Check out the post-conference report for RWDevCon!

As you probably know, we just finished running our first ever RWDevCon: The Tutorial Conference.

The conference was a huge hit! We received an overwhelmingly positive response on our evaluation forms, with a whopping 4.59 overall rating.

I thought you might like to see to see some pictures and feedback from the attendees, so that’s what this post is all about.

I already wrote a post covering the first half of day one, so this post will pick up where that left off.

Then we’ll get into the post-mortem, with three things that went well, and three things that we can improve upon in the future. Let’s dive in!

Day 2 Conference Highlights

Let’s start by covering some of the highlights from where the day one post left off.

After day 1, we all headed to a nearby bar for some food, beer, and fun!

01_PennSocial

The next morning, we were up bright and early for some more tutorials. We had tutorials on WatchKit, Adaptive UI, View Controller Transition Animations, and more.

11_Tutorial

After a hard day’s work on tutorials, we switched over to inspiration talks. We had inspiration talks on subjects like Contributing, Finishing, and “Math Isn’t Scary”.

12_Inspiration

At the end of the day, we unleashed a surprise – everyone who attended the conference got an advance copy of an upcoming book by Marin Todorov – iOS Animations by Tutorials!

10_Books

It was a perfect way to end the conference – and now Marin is a new celebrity :]

13_Celebrities

What Went Well

Overall, people seemed to really enjoy the conference and left some amazing positive feedback.

“This was the best developer conference I have been to in quite some time! The combination of technology, friendship, and passion is unique and I think you are creating something awesome!” –Soren B.

“This was a great mix of intro to the concept, guided demo and self study. It was definitely one of the best formats I’ve participated in. A winner.” –Tim M.

“Great value and materials for cost! Hard to beat. Really liked full hands-on approach w/ early access to project files – all very well organized. Better food and more fun than WWDC!” –Conference Evaluation Form

Here are three things I think went particularly well about the conference.

1) Hands-On Tutorials

I think the hands-on focus of the conference was a huge hit, and definitely something that sets us apart from other conferences. I was pleased to see everyone following along with the demos and performing the exercises, and people said they really felt like they were learning and walking away with new skills.

02_Tutorials

“The hands-on tutorials were worth their weight in gold. Some of the concepts I spend hours reading about were cleared up in 30 minutes.” –Conference Evaluation Form

“The demos and labs have been SO polished. Basically my ideal iOS conference because I came away learning so much!” –Conference Evaluation Form

2) Inspiration Talks

It was also really nice to take a break after the tutorials and move onto something completely different – non-technical talks with the goal to give a new idea or inspire.

04_More

“The inspiration talks surprised me the most. I really enjoyed those. I loved all of the technical sessions but the inspiration talks were unexpected.” –Conference Evaluation Form

“Honestly the inspiration talks were excellent.” –Conference Evaluation Form

3) Friendship

I was blown away by how friendly and encouraging everyone at the conference was. People went out of their way to welcome each other and make everyone feel comfortable and part of the same team.

06_Friendship

“I loved speaking with attendees and team members – met a lot of great people and had some interesting discussions on the topics covered.” –Session Evaluation Form

“Great learning experience. Wonderful camaraderie. Have it again!!!” –Session Evaluation Form

What Could Be Improved

As with anything, there are always some things you can improve. Here are the top 3 in my opinion.

1) More Time

The #1 area for improvement was more time. If we had more time for tutorials, we could go a little bit slower so it’s easier to follow along, and have more time for the labs and challenges.

15_More

It would also be great to perhaps add another day to the conference, as that would allow us to cover more topics and have more time for socializing.

2) Better Tailoring

Each speaker was assigned a fellow speaker as a partner, and practiced their talks both with their partner and myself.

This helped really polish the talks, but overall I think the beginner tutorials could have been “more beginner”, and the advanced tutorials “more advanced.”

03_LiveBlogging

I think we could have tailored the material better for the intended audience if we had a group of volunteers at the appropriate level (other than speakers) listen to the tutorials in advance and give us feedback on if we’re teaching at the right pace/level.

3) Materials Polish

We delivered the conference materials to attendees in advance of the conference.

05_Code

That went great, but the materials could have been organized and polished a bit more consistently. In the future, we’d like to assign a tech editor to that.

Where To Go From Here?

Everyone keeps asking us – will there be a RWDevCon 2016?

14_John

We can’t promise anything yet, but we can say – based on the amazing time we had, and the great feedback we’ve received from attendees, we are strongly leaning that way :]

Thank you for attending RWDecCon!

The conference sold out quickly last time, so we’ll give newsletter subscribers a first shot at tickets. If you’d like to be notified if/when tickets become available, sign up here!

Thanks again for everyone who helped make the first ever RWDevCon a success – speakers, attendees, sponsors, and more!

RWDevCon 2015 Post-Mortem is a post from: Ray Wenderlich

The post RWDevCon 2015 Post-Mortem appeared first on Ray Wenderlich.

What’s New in Swift 1.2

$
0
0
Get the heads up on what's new in Swift 1.2!

Get the heads up on what’s new in Swift 1.2!

Just when we thought Apple was busy working on more WatchKit and Xcode 6.2 betas – Xcode 6.3 beta and Swift 1.2 landed in our collective laps on Monday!

There are a ton of exciting changes in Swift 1.2. It’s still very much a beta, and only the inner circle at Apple know when it will be finalized, so it’s not a good idea to use it in any apps you’re planning on shipping anytime soon.

But it’s still good to keep up-to-date with the changes so you’re when they go live. In this article, I’ll highlight the most significant changes in Swift 1.2:

  • Speed improvements
  • Improved if-let optional binding
  • Changes to as when casting
  • Native Swift sets
  • Changes to initializing constants
  • Objective-C interop and bridging

And to tie it all together, I’ll try out the new Swift migrator tool to see just how well it converts a project written in Swift 1.1 to 1.2, and I’ll let you know how Swift 1.2 affects our tutorials books.

Remember, Swift 1.2 is beta software. The final Swift 1.2 release is still a good way away, and you can’t submit apps built with Xcode 6.3 beta to the store. So again, this isn’t something you’ll probably want to use yet, but it’s nice to keep track of what Apple has in mind for the language and the platform.

Let’s look into the future and get a sneak peek of what’s in store for us Swift developers!

Speed Improvements

Swift 1.2 brings several speed improvements to make both your apps and development even swifter!

  • Incremental Builds: Xcode no longer needs to recompile every single file in the project every time you build and run. The build system’s dependency manager is still pretty conservative, but you should see faster build times. This should be especially significant for larger projects.
  • Performance Enhancements: The standard library and runtime have been optimized; for example, the release notes point to speed ups in multidimensional array handling. You’ll need to benchmark your own apps since performance characteristics are so different between apps.

Swift code is already faster than Objective-C code in some cases, so it’s good to see continued improvement here!

if-let improvements

The aptly-named “pyramid of doom” is no more! Before Swift 1.2, you could only do optional binding on one thing at a time:

// Before Swift 1.2
if let data = widget.dataStream {
  if data.isValid {
    if let source = sourceIP() {
      if let dest = destIP() {
        // do something
      }
    }
  }
}

Now, you can test multiple optionals at once:

// After Swift 1.2
if let data = widget.data where data.isValid, let source = sourceIP(), dest = destIP() {
  // do something
}

Note the new where keyword that lets you test for boolean conditions inline. This makes your conditionals much more compact and will avoid all that extra indentation.

Upcasts and downcasts

The as keyword used to do both upcasts and downcasts:

// Before Swift 1.2
var aView: UIView = someView()
 
var object = aView as NSObject // upcast 
 
var specificView = aView as UITableView // downcast

The upcast, going from a derived class to a base class, can be checked at compile time and will never fail.

However, downcasts can fail since you can’t always be sure about the specific class. If you have a UIView, it’s possible it’s a UITableView or maybe a UIButton. If your downcast goes to the correct type – great! But if you happen to specify the wrong type, you’ll get a runtime error and the app will crash.

cast

In Swift 1.2, downcasts must be either optional with as? or “forced failable” with as!. If you’re sure about the type, then you can force the cast with as! similar to how you would use an implicitly-unwrapped optional:

// After Swift 1.2
var aView: UIView = someView()
 
var tableView = aView as! UITableView

The exclamation point makes it absolutely clear that you know what you’re doing and that there’s a chance things will go terribly wrong if you’ve accidentally mixed up your types!

As always, as? with optional binding is the safest way to go:

// This isn't new to Swift 1.2, but is still the safest way
var aView: UIView = someView()
 
if let tableView = aView as? UITableView {
  // do something with tableView
}

Sets

Note: If you’re unfamiliar with sets, or data structures in general, check out our tutorial Collection Data Structures In Swift.

Doge

Swift 1.0 shipped with native strings, arrays and dictionaries bridged to NSString, NSArray and NSDictionary. The data structure nerds asked, “what happened to sets?”

Better late than never, native Swift sets are here! Like arrays and dictionaries, the new Set type is a struct with value semantics.

Like arrays, sets are generic collections so you need to provide the type of object to store; however, they store unique elements so you won’t see any duplicates.

If you’ve used NSSet before, you’ll feel right at home:

var dogs = Set<String>()
 
dogs.insert("Shiba Inu")
dogs.insert("Doge")
dogs.insert("Husky puppy")
dogs.insert("Schnoodle")
dogs.insert("Doge")
 
// prints "There are 4 known dog breeds"
println("There are \(dogs.count) known dog breeds")

You can do everything you would expect with Swift sets: check if a set contains a value, enumerate all the values, perform a union with another set, and so on.

This is a great addition to the standard library and fills a big abstraction hole where you still had to drop down to NSSet, which doesn’t feel very native in Swift. Hopefully, other missing types such as NSDate are next on the list to be Swift-ified. ;]

let constants

Constants are great for safety and ensuring things that shouldn’t change don’t change. Our Swift Style Guide even suggests this rule of thumb: “define everything as a constant and only change it to a variable when the compiler complains!”

One of the biggest problems with constants was that they had to be given a value when declared. There were some workarounds, ranging from just using var to using a closure expression to assign a value.

But in Swift 1.2, you can now declare a constant with let and assign its value some time in the future. You do have to give it a value before you access the constant of course, but this means you can now set the value conditionally:

let longEdge: CGFloat
 
if isLandscape {
  longEdge = image.calculateWidth()
} else {
  longEdge = image.calculateHeight()
}
 
// you can access longEdge from this point on

Any time using let is made easier, it’s a thumbs up for clearer, cleaner code with fewer possible side effects.

Objective-C interop

As Swift matures, the default classes will slowly shift towards the native Swift implementations. And that’s already happening!

In Swift 1.2, Objective-C classes that have native Swift equivalents (NSString, NSArray, NSDictionary etc.) are no longer automatically bridged. That means passing an NSString to a function that expects a String will now fail!

func mangleString(input: String) {
  // do something with input
}
 
let someString: NSString = "hello"
 
mangleString(someString) // compile error!

If you want to do this, you’ll need to be explicit with the type conversion:

mangleString(someString as String)

Again, Swift is becoming the first-class citizen here: basically, Swift strings will work whenever some kind of string (String or NSString) is expected. The only change here is when you have those old NSString instances to work with.

NSString quaint

Dealing with optionals

If you’ve been following all the changes to Swift until now, you’ve seen arguments change from UIView to UIView! to UIView? and back again. We were spoiled with being able to message nil in Objective-C, but Swift is much stricter about things.

If you’re maintaining Objective-C code, there are some new qualifiers for you to use when specifying the type for arguments, variables, properties, etc.:

  • nonnull – will never be nil
  • nullable – can be nil
  • null_unspecified – unknown whether it can be nil or not (the current default)

For example, consider how these Objective-C declarations would map to Swift:

  • nonnull NSString *string – a regular object, String
  • nullable NSString *string – this is an optional, String?
  • null_unspecified NSString *string – unknown, thus implicitly unwrapped, String!

If you don’t have Objective-C code to maintain, you’ll still benefit from Apple adding these qualifiers to the Cocoa headers. That will make your Swift experience that much cleaner with fewer implicitly-unwrapped values.

Swift migrator

Xcode 6.3 beta includes a Swift migrator to help automate some of these changes to Swift 1.2.

I made a copy of the official RWDevCon app project and opened it in Xcode 6.3 beta. How far would I get with a build and run?

migration-12-errors

There’s a new menu option: Edit\Convert\To Swift 1.2…. First you select a target and then it’s very similar to how refactoring works – Xcode will churn away and then come back with a preview. You’ll see the old code and new code side-by-side with the changes highlighted.

In this project, all the automated conversion did was suggest changing as to as! all over the place. There was one case of nil coalescing that the migrator didn’t understand:

let date = (data["metadata"] as NSDictionary?)?["lastUpdated"] as? NSDate ?? beginningOfTimeDate

That’s a pretty complicated expression! The migrator got confused and assumed there were two expressions there. The solution? A semicolon, of course!

let date = (data["metadata"] as! NSDictionary?)?["lastUpdated"] as? NSDate ??; beginningOfTimeDate

That’s some kind of syntax error that doesn’t compile. The other additions of as! made sense (and showed how much I rely on forced downcasts!) but this one line needed to be fixed manually.

Note: Your mileage may vary with the migrator on your own projects, of course. Feel free to share your experiences on our forums!

What does this mean for tutorial updates?

We’re very excited about these updates to Swift. However, we won’t start updating our tutorials, books, and videos until the gold master (GM) release of Xcode 6.3 and Swift 1.2 at the earliest.

To be updated sometime after the GM release of Xcode 6.3 and Swift 1.2 - stay tuned!

To be updated sometime after the GM release of Xcode 6.3 and Swift 1.2 – stay tuned!

The team at Apple will continue to tweak things in future beta releases and in our experience, it’s best to wait until the GM to get a good sense of what will ship. We’ll still be keeping an eye on each new beta, and we can’t wait to have our tutorials be compatible with the final release version of Swift 1.2!

If you’ve purchased PDF versions of iOS 8 by Tutorials, Swift by Tutorials, or any book released after and including iOS 7 by Tutorials, you’ll receive updated versions free of charge as and when they’re released. You’ll also see a note added to the top of our free tutorials stating which version of Xcode they’re compatible with.

Where to go from here?

Remember, Swift 1.2 is bundled with Xcode 6.3, which is still a beta – that means you can’t ship your apps with it yet! But you can try things out on a copy of your project and get a feel for how much work you’ll have to do after the final release.

In the meantime, why not try out the beta and see what you think about the changes to the language? You can always contribute back and file a radar with Apple to report a bug or make a feature request – there’s nothing quite like the thrill of having your radar marked as a duplicate, except perhaps when it gets marked as “fixed” and you see your radar number in the next set of release notes. ;]

What do you like or dislike about Swift 1.2 so far? Let us know in the forum discussion below!

What’s New in Swift 1.2 is a post from: Ray Wenderlich

The post What’s New in Swift 1.2 appeared first on Ray Wenderlich.

Viewing all 4370 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>